Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21854657 authored by Mingming Yin's avatar Mingming Yin Committed by Naresh Tanniru
Browse files

hal: add support for compress passthrough

- Add support for compress passthrough.

Change-Id: I4934470ac8b23cb8de9b2d7d1b0014afe74b5a27
parent dac705fe
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -227,6 +227,12 @@ endif

ifeq ($(strip $(AUDIO_FEATURE_ENABLED_HDMI_PASSTHROUGH)),true)
    LOCAL_CFLAGS += -DHDMI_PASSTHROUGH_ENABLED
    LOCAL_SRC_FILES += audio_extn/passthru.c
endif

ifeq ($(strip $(AUDIO_FEATURE_ENABLED_KEEP_ALIVE)),true)
    LOCAL_CFLAGS += -DKEEP_ALIVE_ENABLED
    LOCAL_SRC_FILES += audio_extn/keep_alive.c
endif

ifeq ($(strip $(AUDIO_FEATURE_ENABLED_SOURCE_TRACKING)),true)
+43 −3
Original line number Diff line number Diff line
@@ -403,11 +403,22 @@ void audio_extn_dolby_send_ddp_endp_params(struct audio_device *adev);
#define audio_extn_dolby_update_passt_stream_configuration(adev, out)      (0)
#define audio_extn_dolby_is_passt_convert_supported(adev, out)             (0)
#define audio_extn_dolby_is_passt_supported(adev, out)                     (0)
#define audio_extn_dolby_is_passthrough_stream(flags)                      (0)
#define audio_extn_dolby_is_passthrough_stream(out)                        (0)
#define audio_extn_dolby_get_passt_buffer_size(info)                       (0)
#define audio_extn_dolby_set_passt_volume(out, mute)                       (0)
#define audio_extn_dolby_set_passt_latency(out, latency)                   (0)
#define AUDIO_OUTPUT_FLAG_COMPRESS_PASSTHROUGH  0x4000
#define audio_extn_passthru_is_supported_format(f) (0)
#define audio_extn_passthru_should_drop_data(o) (0)
#define audio_extn_passthru_on_start(o) do {} while(0)
#define audio_extn_passthru_on_stop(o) do {} while(0)
#define audio_extn_passthru_on_pause(o) do {} while(0)
#define audio_extn_passthru_is_enabled() (0)
#define audio_extn_passthru_is_active() (0)
#define audio_extn_passthru_set_parameters(a, p) (-ENOSYS)
#define audio_extn_passthru_init(a) do {} while(0)
#define audio_extn_passthru_should_standby(o) (1)

#define AUDIO_OUTPUT_FLAG_COMPRESS_PASSTHROUGH  0x1000
#else
bool audio_extn_dolby_is_passt_convert_supported(struct audio_device *adev,
                                                 struct stream_out *out);
@@ -415,10 +426,22 @@ bool audio_extn_dolby_is_passt_supported(struct audio_device *adev,
                                         struct stream_out *out);
void audio_extn_dolby_update_passt_stream_configuration(struct audio_device *adev,
                                                 struct stream_out *out);
bool audio_extn_dolby_is_passthrough_stream(int flags);
bool audio_extn_dolby_is_passthrough_stream(struct stream_out *out);
int audio_extn_dolby_get_passt_buffer_size(audio_offload_info_t* info);
int audio_extn_dolby_set_passt_volume(struct stream_out *out, int mute);
int audio_extn_dolby_set_passt_latency(struct stream_out *out, int latency);
bool audio_extn_passthru_is_supported_format(audio_format_t format);
bool audio_extn_passthru_should_drop_data(struct stream_out * out);
void audio_extn_passthru_on_start(struct stream_out *out);
void audio_extn_passthru_on_stop(struct stream_out *out);
void audio_extn_passthru_on_pause(struct stream_out *out);
int audio_extn_passthru_set_parameters(struct audio_device *adev,
                                       struct str_parms *parms);
bool audio_extn_passthru_is_enabled();
bool audio_extn_passthru_is_active();
void audio_extn_passthru_init(struct audio_device *adev);
bool audio_extn_passthru_should_standby(struct stream_out *out);

#endif

#ifndef HFP_ENABLED
@@ -535,4 +558,21 @@ void audio_extn_perf_lock_release(int *handle);
#else
void audio_utils_set_hdmi_channel_status(struct stream_out *out, char * buffer, size_t bytes);
#endif

#ifndef KEEP_ALIVE_ENABLED
#define audio_extn_keep_alive_init(a) do {} while(0)
#define audio_extn_keep_alive_start() do {} while(0)
#define audio_extn_keep_alive_stop() do {} while(0)
#define audio_extn_keep_alive_is_active() (false)
#define audio_extn_keep_alive_set_parameters(adev, parms) (0)
#else
void audio_extn_keep_alive_init(struct audio_device *adev);
void audio_extn_keep_alive_start();
void audio_extn_keep_alive_stop();
bool audio_extn_keep_alive_is_active();
int audio_extn_keep_alive_set_parameters(struct audio_device *adev,
                                         struct str_parms *parms);
#endif


#endif /* AUDIO_EXTN_H */
+28 −3
Original line number Diff line number Diff line
@@ -478,10 +478,35 @@ void audio_extn_dolby_update_passt_stream_configuration(
    }
}

bool audio_extn_dolby_is_passthrough_stream(int flags) {
bool audio_extn_dolby_is_passthrough_stream(struct stream_out *out) {

    if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_PASSTHROUGH)
    //check passthrough system property
    if (!property_get_bool("audio.offload.passthrough", false)) {
        return false;
    }

    //check supported device, currently only on HDMI.
    if (out->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
        //passthrough flag
        if (out->flags & AUDIO_OUTPUT_FLAG_COMPRESS_PASSTHROUGH)
            return true;
        //direct flag, check supported formats.
        if (out->flags & AUDIO_OUTPUT_FLAG_DIRECT) {
            if (audio_extn_passthru_is_supported_format(out->format)) {
                if (platform_is_edid_supported_format(out->dev->platform,
                        out->format)) {
                    return true;
                } else if (audio_extn_is_dolby_format(out->format) &&
                            platform_is_edid_supported_format(out->dev->platform,
                                AUDIO_FORMAT_AC3)){
                    //return true for EAC3/EAC3_JOC formats
                    //if sink supports only AC3
                    return true;
                }
            }
        }
    }

    return false;
}

+293 −0
Original line number Diff line number Diff line
/*
* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define LOG_TAG "keep_alive"
/*#define LOG_NDEBUG 0*/
#include <stdlib.h>
#include <cutils/log.h>
#include "audio_hw.h"
#include "audio_extn.h"
#include "platform_api.h"
#include <platform.h>

#define SILENCE_MIXER_PATH "silence-playback hdmi"
#define SILENCE_DEV_ID 5            /* index into machine driver */
#define SILENCE_INTERVAL_US 2000000

typedef enum {
    STATE_DEINIT = -1,
    STATE_IDLE,
    STATE_ACTIVE,
} state_t;

typedef enum {
    REQUEST_WRITE,
} request_t;

typedef struct {
    pthread_mutex_t lock;
    pthread_cond_t  cond;
    pthread_t thread;
    state_t state;
    struct listnode cmd_list;
    struct pcm *pcm;
    bool done;
    void * userdata;
} keep_alive_t;

struct keep_alive_cmd {
    struct listnode node;
    request_t req;
};

static keep_alive_t ka;

static struct pcm_config silence_config = {
    .channels = 2,
    .rate = DEFAULT_OUTPUT_SAMPLING_RATE,
    .period_size = DEEP_BUFFER_OUTPUT_PERIOD_SIZE,
    .period_count = DEEP_BUFFER_OUTPUT_PERIOD_COUNT,
    .format = PCM_FORMAT_S16_LE,
    .start_threshold = DEEP_BUFFER_OUTPUT_PERIOD_SIZE / 4,
    .stop_threshold = INT_MAX,
    .avail_min = DEEP_BUFFER_OUTPUT_PERIOD_SIZE / 4,
};

static void * keep_alive_loop(void * context);

void audio_extn_keep_alive_init(struct audio_device *adev)
{
    ka.userdata = adev;
    ka.state = STATE_IDLE;
    ka.pcm = NULL;
    pthread_mutex_init(&ka.lock, (const pthread_mutexattr_t *) NULL);
    pthread_cond_init(&ka.cond, (const pthread_condattr_t *) NULL);
    list_init(&ka.cmd_list);
    if (pthread_create(&ka.thread,  (const pthread_attr_t *) NULL,
                       keep_alive_loop, NULL) < 0) {
        ALOGW("Failed to create keep_alive_thread");
        /* can continue without keep alive */
        ka.state = STATE_DEINIT;
    }
}

static void send_cmd_l(request_t r)
{
    if (ka.state == STATE_DEINIT)
        return;

    struct keep_alive_cmd *cmd =
        (struct keep_alive_cmd *)calloc(1, sizeof(struct keep_alive_cmd));

    cmd->req = r;
    list_add_tail(&ka.cmd_list, &cmd->node);
    pthread_cond_signal(&ka.cond);
}

static int close_silence_stream()
{
    if (!ka.pcm)
        return -ENODEV;

    pcm_close(ka.pcm);
    ka.pcm = NULL;
    return 0;
}

static int open_silence_stream()
{
    unsigned int flags = PCM_OUT|PCM_MONOTONIC;

    if (ka.pcm)
        return -EEXIST;

    ALOGD("opening silence device %d", SILENCE_DEV_ID);
    struct audio_device * adev = (struct audio_device *)ka.userdata;
    ka.pcm = pcm_open(adev->snd_card, SILENCE_DEV_ID,
                      flags, &silence_config);
    ALOGD("opened silence device %d", SILENCE_DEV_ID);
    if (ka.pcm == NULL || !pcm_is_ready(ka.pcm)) {
        ALOGE("%s: %s", __func__, pcm_get_error(ka.pcm));
        if (ka.pcm != NULL) {
            pcm_close(ka.pcm);
            ka.pcm = NULL;
        }
        return -1;
    }
    return 0;
}

/* must be called with adev lock held */
void audio_extn_keep_alive_start()
{
    struct audio_device * adev = (struct audio_device *)ka.userdata;

    if (ka.state == STATE_DEINIT)
        return;

    if (audio_extn_passthru_is_active())
        return;

    pthread_mutex_lock(&ka.lock);

    if (ka.state == STATE_ACTIVE)
        goto exit;

    ka.done = false;
    //todo: platform_send_audio_calibration is replaced by audio_extn_utils_send_audio_calibration
    //check why audio cal needs to be set
    //platform_send_audio_calibration(adev->platform, SND_DEVICE_OUT_HDMI);
    audio_route_apply_and_update_path(adev->audio_route, SILENCE_MIXER_PATH);

    if (open_silence_stream() == 0) {
        send_cmd_l(REQUEST_WRITE);
        while (ka.state != STATE_ACTIVE) {
            pthread_cond_wait(&ka.cond, &ka.lock);
        }
    }

exit:
    pthread_mutex_unlock(&ka.lock);
}

/* must be called with adev lock held */
void audio_extn_keep_alive_stop()
{
    struct audio_device * adev = (struct audio_device *)ka.userdata;

    if (ka.state == STATE_DEINIT)
        return;

    pthread_mutex_lock(&ka.lock);

    if (ka.state == STATE_IDLE)
        goto exit;

    ka.done = true;
    while (ka.state != STATE_IDLE) {
        pthread_cond_wait(&ka.cond, &ka.lock);
    }
    close_silence_stream();
    audio_route_reset_and_update_path(adev->audio_route, SILENCE_MIXER_PATH);

exit:
    pthread_mutex_unlock(&ka.lock);
}

bool audio_extn_keep_alive_is_active()
{
    return ka.state == STATE_ACTIVE;
}

int audio_extn_keep_alive_set_parameters(struct audio_device *adev __unused,
                                         struct str_parms *parms)
{
    char value[32];
    int ret;

    ret = str_parms_get_str(parms, "connect", value, sizeof(value));
    if (ret >= 0) {
        int val = atoi(value);
        if (val & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
            if (!audio_extn_passthru_is_active()) {
                ALOGV("start keep alive");
                audio_extn_keep_alive_start();
            }
        }
    }

    ret = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_DISCONNECT, value,
                            sizeof(value));
    if (ret >= 0) {
        int val = atoi(value);
        if (val & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
            ALOGV("stop keep_alive");
            audio_extn_keep_alive_stop();
        }
    }
    return 0;
}


static void * keep_alive_loop(void * context __unused)
{
    struct audio_device *adev = (struct audio_device *)ka.userdata;
    struct keep_alive_cmd *cmd = NULL;
    struct listnode *item;
    uint8_t * silence = NULL;
    int32_t bytes = 0, count = 0, i;
    struct stream_out * p_out = NULL;

    while (true) {
        pthread_mutex_lock(&ka.lock);
        if (list_empty(&ka.cmd_list)) {
            pthread_cond_wait(&ka.cond, &ka.lock);
            pthread_mutex_unlock(&ka.lock);
            continue;
        }

        item = list_head(&ka.cmd_list);
        cmd = node_to_item(item, struct keep_alive_cmd, node);
        list_remove(item);

        if (cmd->req != REQUEST_WRITE) {
            free(cmd);
            pthread_mutex_unlock(&ka.lock);
            continue;
        }

        free(cmd);
        ka.state = STATE_ACTIVE;
        pthread_cond_signal(&ka.cond);
        pthread_mutex_unlock(&ka.lock);

        if (!silence) {
            /* 50 ms */
            bytes =
                (silence_config.rate * silence_config.channels * sizeof(int16_t)) / 20;
            silence = (uint8_t *)calloc(1, bytes);
        }

        while (!ka.done) {
            ALOGV("write %d bytes of silence", bytes);
            pcm_write(ka.pcm, (void *)silence, bytes);
            /* This thread does not have to write silence continuously.
             * Just something to keep the connection alive is sufficient.
             * Hence a short burst of silence periodically.
             */
            usleep(SILENCE_INTERVAL_US);
        }

        pthread_mutex_lock(&ka.lock);
        ka.state = STATE_IDLE;
        pthread_cond_signal(&ka.cond);
        pthread_mutex_unlock(&ka.lock);
    }
    return 0;
}
+186 −0
Original line number Diff line number Diff line
/*
* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define LOG_TAG "passthru"
/*#define LOG_NDEBUG 0*/
#include <stdlib.h>
#include <cutils/atomic.h>
#include <cutils/str_parms.h>
#include <cutils/log.h>
#include "audio_hw.h"
#include "audio_extn.h"
#include "platform_api.h"
#include <platform.h>

static const audio_format_t audio_passthru_formats[] = {
    AUDIO_FORMAT_AC3,
    AUDIO_FORMAT_E_AC3,
    AUDIO_FORMAT_DTS,
    AUDIO_FORMAT_DTS_HD
};

/*
 * This atomic var is incremented/decremented by the offload stream to notify
 * other pcm playback streams that a pass thru session is about to start or has
 * finished. This hint can be used by the other streams to move to standby or
 * start calling pcm_write respectively.
 * This behavior is necessary as the DSP backend can only be configured to one
 * of PCM or compressed.
 */
static volatile int32_t compress_passthru_active;

bool audio_extn_passthru_is_supported_format(audio_format_t format)
{
    int32_t num_passthru_formats = sizeof(audio_passthru_formats) /
                                    sizeof(audio_passthru_formats[0]);
    int32_t i;

    for (i = 0; i < num_passthru_formats; i++) {
        if (format == audio_passthru_formats[i]) {
            return true;
        }
    }
    return false;
}

/*
 * must be called with stream lock held
 * This function decides based on some rules whether the data
 * coming on stream out must be rendered or dropped.
 */
bool audio_extn_passthru_should_drop_data(struct stream_out * out)
{
    /* Make this product specific */
    if (!(out->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL)) {
        ALOGI("drop data as end device 0x%x is unsupported", out->devices);
        return true;
    }

    if (out->usecase != USECASE_AUDIO_PLAYBACK_OFFLOAD) {
        if (android_atomic_acquire_load(&compress_passthru_active) > 0) {
            ALOGI("drop data as pass thru is active");
            return true;
        }
    }

    return false;
}

/* called with adev lock held */
void audio_extn_passthru_on_start(struct stream_out * out)
{

    uint64_t max_period_us = 0;
    uint64_t temp;
    struct audio_usecase * usecase;
    struct listnode *node;
    struct stream_out * o;
    struct audio_device *adev = out->dev;

    if (android_atomic_acquire_load(&compress_passthru_active) > 0) {
        ALOGI("pass thru is already active");
        return;
    }

    ALOGV("inc pass thru count to notify other streams");
    android_atomic_inc(&compress_passthru_active);

    ALOGV("keep_alive_stop");
    audio_extn_keep_alive_stop();

    while (true) {
        /* find max period time among active playback use cases */
        list_for_each(node, &adev->usecase_list) {
            usecase = node_to_item(node, struct audio_usecase, list);
            if (usecase->type == PCM_PLAYBACK &&
                usecase->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
                o = usecase->stream.out;
                temp = o->config.period_size * 1000000LL / o->sample_rate;
                if (temp > max_period_us)
                    max_period_us = temp;
            }
        }

        if (max_period_us) {
            pthread_mutex_unlock(&adev->lock);
            usleep(2*max_period_us);
            max_period_us = 0;
            pthread_mutex_lock(&adev->lock);
        } else
            break;
    }
}

/* called with adev lock held */
void audio_extn_passthru_on_stop(struct stream_out * out)
{
    struct audio_device * adev = out->dev;
    if (android_atomic_acquire_load(&compress_passthru_active) > 0) {
        /*
         * its possible the count is already zero if pause was called before
         * stop output stream
         */
        android_atomic_dec(&compress_passthru_active);
    }

    if (out->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
        ALOGI("passthru on aux digital, start keep alive");
        audio_extn_keep_alive_start();
    }
}

void audio_extn_passthru_on_pause(struct stream_out * out __unused)
{
    if (android_atomic_acquire_load(&compress_passthru_active) == 0)
        return;

    android_atomic_dec(&compress_passthru_active);
}

int audio_extn_passthru_set_parameters(struct audio_device *adev __unused,
                                       struct str_parms *parms __unused)
{
    return 0;
}

bool audio_extn_passthru_is_active()
{
    return android_atomic_acquire_load(&compress_passthru_active) > 0;
}

bool audio_extn_passthru_is_enabled() { return true; }

void audio_extn_passthru_init(struct audio_device *adev __unused)
{
}

bool audio_extn_passthru_should_standby(struct stream_out * out __unused)
{
    return true;
}
Loading