blob: 8795d527814195a55e0e7d7db467588fde0dedc1 [file] [log] [blame]
/*
* Copyright (c) 2016-2020, 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 "audio_hw_cin"
/*#define LOG_NDEBUG 0*/
#define LOG_NDDEBUG 0
#include <cutils/properties.h>
#include <cutils/str_parms.h>
#include <log/log.h>
#include <pthread.h>
#include "audio_hw.h"
#include "platform.h"
#include "platform_api.h"
#include <hardware/audio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "audio_extn.h"
#include "audio_defs.h"
#include "sound/compress_params.h"
#ifdef DYNAMIC_LOG_ENABLED
#include <log_xml_parser.h>
#define LOG_MASK HAL_MOD_FILE_COMPR_IN
#include <log_utils.h>
#endif
/* default timestamp metadata definition if not defined in kernel*/
#ifndef COMPRESSED_TIMESTAMP_FLAG
#define COMPRESSED_TIMESTAMP_FLAG 0
struct snd_codec_metadata {
uint64_t timestamp;
};
#define compress_config_set_timstamp_flag(config) (-ENOSYS)
#else
#ifdef AUDIO_GKI_ENABLED
/* (config).codec->reserved[1] is for flags */
#define compress_config_set_timstamp_flag(config) \
(config)->codec->reserved[1] |= COMPRESSED_TIMESTAMP_FLAG
#else
#define compress_config_set_timstamp_flag(config) \
(config)->codec->flags |= COMPRESSED_TIMESTAMP_FLAG
#endif /* AUDIO_GKI_ENABLED */
#endif /* COMPRESSED_TIMESTAMP_FLAG */
#define COMPRESS_RECORD_NUM_FRAGMENTS 8
struct cin_private_data {
struct compr_config compr_config;
struct compress *compr;
bool usecase_acquired;
};
typedef struct cin_private_data cin_private_data_t;
static unsigned int cin_usecases_state;
static const audio_usecase_t cin_usecases[] = {
USECASE_AUDIO_RECORD_COMPRESS2,
USECASE_AUDIO_RECORD_COMPRESS3,
USECASE_AUDIO_RECORD_COMPRESS4,
USECASE_AUDIO_RECORD_COMPRESS5,
USECASE_AUDIO_RECORD_COMPRESS6
};
static pthread_mutex_t cin_lock = PTHREAD_MUTEX_INITIALIZER;
bool cin_applicable_stream(struct stream_in *in)
{
if (in->flags & (AUDIO_INPUT_FLAG_COMPRESS | AUDIO_INPUT_FLAG_TIMESTAMP))
return true;
return false;
}
/* all cin_xxx calls must be made on an input
* only after validating that input against cin_attached_usecase
* except below calls
* 1. cin_applicable_stream(in)
* 2. cin_configure_input_stream(in, in_config)
*/
bool cin_attached_usecase(struct stream_in *in)
{
unsigned int i = 0;
audio_usecase_t uc_id = in->usecase;
for (i = 0; i < sizeof(cin_usecases)/
sizeof(cin_usecases[0]); i++) {
if (uc_id == cin_usecases[i] && in->cin_extn != NULL)
return true;
}
return false;
}
static audio_usecase_t get_cin_usecase(void)
{
audio_usecase_t ret_uc = USECASE_INVALID;
unsigned int i, num_usecase = sizeof(cin_usecases) / sizeof(cin_usecases[0]);
char value[PROPERTY_VALUE_MAX] = {0};
property_get("vendor.audio.record.multiple.enabled", value, NULL);
if (!(atoi(value) || !strncmp("true", value, 4)))
num_usecase = 1; /* If prop is not set, limit the num of record usecases to 1 */
ALOGV("%s: num_usecase: %d", __func__, num_usecase);
pthread_mutex_lock(&cin_lock);
for (i = 0; i < num_usecase; i++) {
if (!(cin_usecases_state & (0x1 << i))) {
cin_usecases_state |= 0x1 << i;
ret_uc = cin_usecases[i];
break;
}
}
pthread_mutex_unlock(&cin_lock);
ALOGV("%s: picked usecase: %d", __func__, ret_uc);
return ret_uc;
}
static void free_cin_usecase(audio_usecase_t uc_id)
{
unsigned int i;
ALOGV("%s: free usecase %d", __func__, uc_id);
pthread_mutex_lock(&cin_lock);
for (i = 0; i < sizeof(cin_usecases) /
sizeof(cin_usecases[0]); i++) {
if (uc_id == cin_usecases[i]) {
cin_usecases_state &= ~(0x1 << i);
break;
}
}
pthread_mutex_unlock(&cin_lock);
}
bool cin_format_supported(audio_format_t format)
{
if (format == AUDIO_FORMAT_IEC61937)
return true;
else
return false;
}
int cin_acquire_usecase(struct stream_in *in)
{
audio_usecase_t usecase = USECASE_INVALID;
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
if (cin_data->usecase_acquired) {
ALOGW("%s: in %p, usecase already acquired!", __func__, in);
return 0;
}
usecase = get_cin_usecase();
if (usecase == USECASE_INVALID) {
ALOGE("%s: in %p, failed to acquire usecase, max count reached!", __func__, in);
return -EBUSY;
}
in->usecase = usecase;
cin_data->usecase_acquired = true;
return 0;
}
size_t cin_get_buffer_size(struct stream_in *in)
{
size_t sz = 0;
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
sz = cin_data->compr_config.fragment_size;
if ((in->flags & AUDIO_INPUT_FLAG_TIMESTAMP) ||
(in->flags & AUDIO_INPUT_FLAG_PASSTHROUGH))
sz -= sizeof(struct snd_codec_metadata);
ALOGV("%s: in %p, flags 0x%x, cin_data %p, size %zd",
__func__, in, in->flags, cin_data, sz);
return sz;
}
int cin_open_input_stream(struct stream_in *in)
{
int ret = -EINVAL;
struct audio_device *adev = in->dev;
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
ALOGV("%s: in %p, cin_data %p", __func__, in, cin_data);
if (!cin_data->usecase_acquired) {
ALOGE("%s: in %p, invalid state: usecase not acquired yet!", __func__, in);
return ret;
}
cin_data->compr = compress_open(adev->snd_card, in->pcm_device_id,
COMPRESS_OUT, &cin_data->compr_config);
if (cin_data->compr == NULL || !is_compress_ready(cin_data->compr)) {
ALOGE("%s: %s", __func__,
cin_data->compr ? compress_get_error(cin_data->compr) : "null");
if (cin_data->compr) {
compress_close(cin_data->compr);
cin_data->compr = NULL;
}
ret = -EIO;
} else {
ret = 0;
}
return ret;
}
void cin_stop_input_stream(struct stream_in *in)
{
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
ALOGV("%s: in %p, cin_data %p", __func__, in, cin_data);
if (cin_data->compr) {
compress_stop(cin_data->compr);
}
}
void cin_close_input_stream(struct stream_in *in)
{
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
ALOGV("%s: in %p, cin_data %p", __func__, in, cin_data);
if (cin_data->compr) {
compress_close(cin_data->compr);
cin_data->compr = NULL;
}
if (cin_data->usecase_acquired) {
free_cin_usecase(in->usecase);
cin_data->usecase_acquired = false;
}
}
void cin_free_input_stream_resources(struct stream_in *in)
{
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
ALOGV("%s: in %p, cin_data %p", __func__, in, cin_data);
if (cin_data) {
free(cin_data->compr_config.codec);
free(cin_data);
}
}
int cin_read(struct stream_in *in, void *buffer,
size_t bytes, size_t *bytes_read)
{
int ret = -EINVAL;
size_t read_size = bytes;
size_t mdata_size = (sizeof(struct snd_codec_metadata));
cin_private_data_t *cin_data = (cin_private_data_t *) in->cin_extn;
if (cin_data->compr) {
/* start stream if not already done */
if (!is_compress_running(cin_data->compr))
compress_start(cin_data->compr);
if (!(in->flags & (AUDIO_INPUT_FLAG_TIMESTAMP | AUDIO_INPUT_FLAG_PASSTHROUGH)))
mdata_size = 0;
if (buffer && read_size) {
read_size = compress_read(cin_data->compr, buffer, read_size);
if (read_size == bytes) {
/* set ret to 0 if compress_read succeeded*/
ret = 0;
*bytes_read = bytes;
/* data from DSP comes in 24_8 format, convert it to 8_24 */
if (in->format == AUDIO_FORMAT_PCM_8_24_BIT) {
if (audio_extn_utils_convert_format_24_8_to_8_24(
(char *)buffer + mdata_size, bytes) != bytes)
ret = -EIO;
}
} else {
ret = errno;
ALOGE("%s: failed error = %d, read = %zd, err_str %s", __func__,
ret, read_size, compress_get_error(cin_data->compr));
}
}
}
ALOGV("%s: in %p, flags 0x%x, buf %p, bytes %zd, read_size %zd, ret %d",
__func__, in, in->flags, buffer, bytes, read_size, ret);
return ret;
}
int cin_configure_input_stream(struct stream_in *in, struct audio_config *in_config)
{
struct audio_config config = {.format = 0};
int ret = 0, buffer_size = 0, meta_size = sizeof(struct snd_codec_metadata);
cin_private_data_t *cin_data = NULL;
uint32_t compr_passthr = 0, flags = 0;
if (!COMPRESSED_TIMESTAMP_FLAG &&
(in->flags & (AUDIO_INPUT_FLAG_TIMESTAMP | AUDIO_INPUT_FLAG_PASSTHROUGH))) {
ALOGE("%s: timestamp mode not supported!", __func__);
return -EINVAL;
}
cin_data = (cin_private_data_t *) calloc(1, sizeof(cin_private_data_t));
in->cin_extn = (void *)cin_data;
if (!cin_data) {
ALOGE("%s, allocation for private data failed!", __func__);
return -ENOMEM;
}
cin_data->compr_config.codec = (struct snd_codec *)
calloc(1, sizeof(struct snd_codec));
if (!cin_data->compr_config.codec) {
ALOGE("%s, allocation for codec data failed!", __func__);
ret = -ENOMEM;
goto err_config;
}
config.sample_rate = in->sample_rate;
config.channel_mask = in->channel_mask;
config.format = in->format;
in->config.channels = audio_channel_count_from_in_mask(in->channel_mask);
buffer_size = audio_extn_utils_get_input_buffer_size(config.sample_rate, config.format,
in->config.channels, in_config->offload_info.duration_us / 1000, false);
cin_data->compr_config.fragment_size = buffer_size;
cin_data->compr_config.codec->id = get_snd_codec_id(in->format);
cin_data->compr_config.fragments = COMPRESS_RECORD_NUM_FRAGMENTS;
cin_data->compr_config.codec->sample_rate = in->sample_rate;
cin_data->compr_config.codec->ch_in = in->config.channels;
cin_data->compr_config.codec->ch_out = in->config.channels;
cin_data->compr_config.codec->format = hal_format_to_alsa(in->format);
if (cin_data->compr_config.codec->id == SND_AUDIOCODEC_PCM)
compr_passthr = LEGACY_PCM;
else if (cin_data->compr_config.codec->id == SND_AUDIOCODEC_IEC61937)
compr_passthr = PASSTHROUGH_IEC61937;
else
compr_passthr = PASSTHROUGH_GEN;
if (in->flags & AUDIO_INPUT_FLAG_FAST) {
ALOGD("%s: Setting latency mode to true", __func__);
flags |= audio_extn_utils_get_perf_mode_flag();
}
#ifdef AUDIO_GKI_ENABLED
/* out->compr_config.codec->reserved[0] is for compr_passthr */
cin_data->compr_config.codec->reserved[0] = compr_passthr;
/* out->compr_config.codec->reserved[1] is for flags */
cin_data->compr_config.codec->reserved[1] = flags;
#else
cin_data->compr_config.codec->compr_passthr = compr_passthr;
cin_data->compr_config.codec->flags = flags;
#endif
if ((in->flags & AUDIO_INPUT_FLAG_TIMESTAMP) ||
(in->flags & AUDIO_INPUT_FLAG_PASSTHROUGH)) {
compress_config_set_timstamp_flag(&cin_data->compr_config);
cin_data->compr_config.fragment_size += meta_size;
}
ALOGD("%s: format %d flags 0x%x SR %d CM 0x%x buf_size %d in %p",
__func__, in->format, in->flags, in->sample_rate, in->channel_mask,
cin_data->compr_config.fragment_size, in);
return ret;
err_config:
cin_free_input_stream_resources(in);
return ret;
}