blob: a8d379321a3b99b52be8cc235f9bf0f2c631a2b2 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "audio_hw_primary"
/*#define LOG_NDEBUG 0*/
/*#define VERY_VERY_VERBOSE_LOGGING*/
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(a...) do { } while(0)
#endif
#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/time.h>
#include <stdlib.h>
#include <math.h>
#include <dlfcn.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <cutils/log.h>
#include <cutils/str_parms.h>
#include <cutils/atomic.h>
#include <cutils/sched_policy.h>
#include <cutils/properties.h>
#include <hardware/audio_effect.h>
#include <system/thread_defs.h>
#include <audio_effects/effect_aec.h>
#include <audio_effects/effect_ns.h>
#include "audio_hw.h"
#include "sound/compress_params.h"
#define MIXER_CTL_COMPRESS_PLAYBACK_VOLUME "Compress Playback Volume"
#define MIXER_CTL_HEADPHONE_JACK_SWITCH "Headphone Jack Switch"
#define MIXER_CTL_CODEC_VMIXER_CODEC_SWITCH "Codec VMixer Codec Switch"
#define MIXER_CTL_SPK_VMIXER_SPK_SWITCH "SPK VMixer SPK Switch"
/* TODO: the following PCM device profiles could be read from a config file */
static struct pcm_device_profile pcm_device_playback = {
.config = {
.channels = PLAYBACK_DEFAULT_CHANNEL_COUNT,
.rate = PLAYBACK_DEFAULT_SAMPLING_RATE,
.period_size = PLAYBACK_PERIOD_SIZE,
.period_count = PLAYBACK_PERIOD_COUNT,
.format = PCM_FORMAT_S16_LE,
.start_threshold = PLAYBACK_START_THRESHOLD(PLAYBACK_PERIOD_SIZE, PLAYBACK_PERIOD_COUNT),
.stop_threshold = PLAYBACK_STOP_THRESHOLD(PLAYBACK_PERIOD_SIZE, PLAYBACK_PERIOD_COUNT),
.silence_threshold = 0,
.silence_size = UINT_MAX,
.avail_min = PLAYBACK_AVAILABLE_MIN,
},
.card = SOUND_CARD,
.id = 9,
.type = PCM_PLAYBACK,
.devices = AUDIO_DEVICE_OUT_WIRED_HEADSET|AUDIO_DEVICE_OUT_WIRED_HEADPHONE|
AUDIO_DEVICE_OUT_SPEAKER,
};
static struct pcm_device_profile pcm_device_capture = {
.config = {
.channels = CAPTURE_DEFAULT_CHANNEL_COUNT,
.rate = CAPTURE_DEFAULT_SAMPLING_RATE,
.period_size = CAPTURE_PERIOD_SIZE,
.period_count = CAPTURE_PERIOD_COUNT,
.format = PCM_FORMAT_S16_LE,
.start_threshold = CAPTURE_START_THRESHOLD,
.stop_threshold = 0,
.silence_threshold = 0,
.avail_min = 0,
},
.card = SOUND_CARD,
.id = 0,
.type = PCM_CAPTURE,
.devices = AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET|AUDIO_DEVICE_IN_BACK_MIC,
};
static struct pcm_device_profile pcm_device_capture_low_latency = {
.config = {
.channels = CAPTURE_DEFAULT_CHANNEL_COUNT,
.rate = CAPTURE_DEFAULT_SAMPLING_RATE,
.period_size = CAPTURE_PERIOD_SIZE_LOW_LATENCY,
.period_count = CAPTURE_PERIOD_COUNT_LOW_LATENCY,
.format = PCM_FORMAT_S16_LE,
.start_threshold = CAPTURE_START_THRESHOLD,
.stop_threshold = 0,
.silence_threshold = 0,
.avail_min = 0,
},
.card = SOUND_CARD,
.id = 0,
.type = PCM_CAPTURE_LOW_LATENCY,
.devices = AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET|AUDIO_DEVICE_IN_BACK_MIC,
};
static struct pcm_device_profile pcm_device_capture_loopback_aec = {
.config = {
.channels = CAPTURE_DEFAULT_CHANNEL_COUNT,
.rate = CAPTURE_DEFAULT_SAMPLING_RATE,
.period_size = CAPTURE_PERIOD_SIZE,
.period_count = CAPTURE_PERIOD_COUNT,
.format = PCM_FORMAT_S16_LE,
.start_threshold = CAPTURE_START_THRESHOLD,
.stop_threshold = 0,
.silence_threshold = 0,
.avail_min = 0,
},
.card = SOUND_CARD,
.id = 1,
.type = PCM_CAPTURE,
.devices = SND_DEVICE_IN_LOOPBACK_AEC,
};
static struct pcm_device_profile pcm_device_playback_sco = {
.config = {
.channels = SCO_DEFAULT_CHANNEL_COUNT,
.rate = SCO_DEFAULT_SAMPLING_RATE,
.period_size = SCO_PERIOD_SIZE,
.period_count = SCO_PERIOD_COUNT,
.format = PCM_FORMAT_S16_LE,
.start_threshold = SCO_START_THRESHOLD,
.stop_threshold = SCO_STOP_THRESHOLD,
.silence_threshold = 0,
.avail_min = SCO_AVAILABLE_MIN,
},
.card = SOUND_CARD,
.id = 2,
.type = PCM_PLAYBACK,
.devices =
AUDIO_DEVICE_OUT_BLUETOOTH_SCO|AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET|
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT,
};
static struct pcm_device_profile pcm_device_capture_sco = {
.config = {
.channels = SCO_DEFAULT_CHANNEL_COUNT,
.rate = SCO_DEFAULT_SAMPLING_RATE,
.period_size = SCO_PERIOD_SIZE,
.period_count = SCO_PERIOD_COUNT,
.format = PCM_FORMAT_S16_LE,
.start_threshold = CAPTURE_START_THRESHOLD,
.stop_threshold = 0,
.silence_threshold = 0,
.avail_min = 0,
},
.card = SOUND_CARD,
.id = 2,
.type = PCM_CAPTURE,
.devices = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
};
static struct pcm_device_profile pcm_device_hotword_streaming = {
.config = {
.channels = 1,
.rate = 16000,
.period_size = CAPTURE_PERIOD_SIZE,
.period_count = CAPTURE_PERIOD_COUNT,
.format = PCM_FORMAT_S16_LE,
.start_threshold = CAPTURE_START_THRESHOLD,
.stop_threshold = 0,
.silence_threshold = 0,
.avail_min = 0,
},
.card = SOUND_CARD,
.id = 0,
.type = PCM_HOTWORD_STREAMING,
.devices = AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET|AUDIO_DEVICE_IN_BACK_MIC
};
static struct pcm_device_profile * const pcm_devices[] = {
&pcm_device_playback,
&pcm_device_capture,
&pcm_device_capture_low_latency,
&pcm_device_playback_sco,
&pcm_device_capture_sco,
&pcm_device_capture_loopback_aec,
&pcm_device_hotword_streaming,
NULL,
};
static const char * const use_case_table[AUDIO_USECASE_MAX] = {
[USECASE_AUDIO_PLAYBACK] = "playback",
[USECASE_AUDIO_PLAYBACK_MULTI_CH] = "playback multi-channel",
[USECASE_AUDIO_PLAYBACK_OFFLOAD] = "compress-offload-playback",
[USECASE_AUDIO_CAPTURE] = "capture",
[USECASE_AUDIO_CAPTURE_HOTWORD] = "capture-hotword",
[USECASE_VOICE_CALL] = "voice-call",
};
#define STRING_TO_ENUM(string) { #string, string }
static unsigned int audio_device_ref_count;
static struct pcm_config pcm_config_deep_buffer = {
.channels = 2,
.rate = DEEP_BUFFER_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,
};
struct string_to_enum {
const char *name;
uint32_t value;
};
static const struct string_to_enum out_channels_name_to_enum_table[] = {
STRING_TO_ENUM(AUDIO_CHANNEL_OUT_STEREO),
STRING_TO_ENUM(AUDIO_CHANNEL_OUT_5POINT1),
STRING_TO_ENUM(AUDIO_CHANNEL_OUT_7POINT1),
};
static void dummybuf_thread_close(struct audio_device *adev);
static bool is_supported_format(audio_format_t format)
{
if (format == AUDIO_FORMAT_MP3 ||
((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_AAC))
return true;
return false;
}
static int get_snd_codec_id(audio_format_t format)
{
int id = 0;
switch (format & AUDIO_FORMAT_MAIN_MASK) {
case AUDIO_FORMAT_MP3:
id = SND_AUDIOCODEC_MP3;
break;
case AUDIO_FORMAT_AAC:
id = SND_AUDIOCODEC_AAC;
break;
default:
ALOGE("%s: Unsupported audio format", __func__);
}
return id;
}
/* Array to store sound devices */
static const char * const device_table[SND_DEVICE_MAX] = {
[SND_DEVICE_NONE] = "none",
/* Playback sound devices */
[SND_DEVICE_OUT_HANDSET] = "handset",
[SND_DEVICE_OUT_SPEAKER] = "speaker",
[SND_DEVICE_OUT_HEADPHONES] = "headphones",
[SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES] = "speaker-and-headphones",
[SND_DEVICE_OUT_VOICE_HANDSET] = "voice-handset",
[SND_DEVICE_OUT_VOICE_SPEAKER] = "voice-speaker",
[SND_DEVICE_OUT_VOICE_HEADPHONES] = "voice-headphones",
[SND_DEVICE_OUT_HDMI] = "hdmi",
[SND_DEVICE_OUT_SPEAKER_AND_HDMI] = "speaker-and-hdmi",
[SND_DEVICE_OUT_BT_SCO] = "bt-sco-headset",
[SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES] = "voice-tty-full-headphones",
[SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = "voice-tty-vco-headphones",
[SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = "voice-tty-hco-handset",
/* Capture sound devices */
[SND_DEVICE_IN_HANDSET_MIC] = "handset-mic",
[SND_DEVICE_IN_SPEAKER_MIC] = "speaker-mic",
[SND_DEVICE_IN_HEADSET_MIC] = "headset-mic",
[SND_DEVICE_IN_HANDSET_MIC_AEC] = "handset-mic",
[SND_DEVICE_IN_SPEAKER_MIC_AEC] = "voice-speaker-mic",
[SND_DEVICE_IN_HEADSET_MIC_AEC] = "headset-mic",
[SND_DEVICE_IN_VOICE_SPEAKER_MIC] = "voice-speaker-mic",
[SND_DEVICE_IN_VOICE_HEADSET_MIC] = "voice-headset-mic",
[SND_DEVICE_IN_HDMI_MIC] = "hdmi-mic",
[SND_DEVICE_IN_BT_SCO_MIC] = "bt-sco-mic",
[SND_DEVICE_IN_CAMCORDER_MIC] = "camcorder-mic",
[SND_DEVICE_IN_VOICE_DMIC_1] = "voice-dmic-1",
[SND_DEVICE_IN_VOICE_SPEAKER_DMIC_1] = "voice-speaker-dmic-1",
[SND_DEVICE_IN_VOICE_TTY_FULL_HEADSET_MIC] = "voice-tty-full-headset-mic",
[SND_DEVICE_IN_VOICE_TTY_VCO_HANDSET_MIC] = "voice-tty-vco-handset-mic",
[SND_DEVICE_IN_VOICE_TTY_HCO_HEADSET_MIC] = "voice-tty-hco-headset-mic",
[SND_DEVICE_IN_VOICE_REC_HEADSET_MIC] = "voice-rec-headset-mic",
[SND_DEVICE_IN_VOICE_REC_MIC] = "voice-rec-mic",
[SND_DEVICE_IN_VOICE_REC_DMIC_1] = "voice-rec-dmic-1",
[SND_DEVICE_IN_VOICE_REC_DMIC_NS_1] = "voice-rec-dmic-ns-1",
[SND_DEVICE_IN_LOOPBACK_AEC] = "loopback-aec",
};
static struct mixer_card *adev_get_mixer_for_card(struct audio_device *adev, int card)
{
struct mixer_card *mixer_card;
struct listnode *node;
list_for_each(node, &adev->mixer_list) {
mixer_card = node_to_item(node, struct mixer_card, adev_list_node);
if (mixer_card->card == card)
return mixer_card;
}
return NULL;
}
static struct mixer_card *uc_get_mixer_for_card(struct audio_usecase *usecase, int card)
{
struct mixer_card *mixer_card;
struct listnode *node;
list_for_each(node, &usecase->mixer_list) {
mixer_card = node_to_item(node, struct mixer_card, uc_list_node[usecase->id]);
if (mixer_card->card == card)
return mixer_card;
}
return NULL;
}
static void free_mixer_list(struct audio_device *adev)
{
struct mixer_card *mixer_card;
struct listnode *node;
struct listnode *next;
list_for_each_safe(node, next, &adev->mixer_list) {
mixer_card = node_to_item(node, struct mixer_card, adev_list_node);
list_remove(node);
audio_route_free(mixer_card->audio_route);
free(mixer_card);
}
}
static int mixer_init(struct audio_device *adev)
{
int i;
int card;
int retry_num;
struct mixer *mixer;
struct audio_route *audio_route;
char mixer_path[PATH_MAX];
struct mixer_card *mixer_card;
struct listnode *node;
list_init(&adev->mixer_list);
for (i = 0; pcm_devices[i] != NULL; i++) {
card = pcm_devices[i]->card;
if (adev_get_mixer_for_card(adev, card) == NULL) {
retry_num = 0;
do {
mixer = mixer_open(card);
if (mixer == NULL) {
if (++retry_num > RETRY_NUMBER) {
ALOGE("%s unable to open the mixer for--card %d, aborting.",
__func__, card);
goto error;
}
usleep(RETRY_US);
}
} while (mixer == NULL);
sprintf(mixer_path, "/system/etc/mixer_paths_%d.xml", card);
audio_route = audio_route_init(card, mixer_path);
if (!audio_route) {
ALOGE("%s: Failed to init audio route controls for card %d, aborting.",
__func__, card);
goto error;
}
mixer_card = calloc(1, sizeof(struct mixer_card));
mixer_card->card = card;
mixer_card->mixer = mixer;
mixer_card->audio_route = audio_route;
list_add_tail(&adev->mixer_list, &mixer_card->adev_list_node);
}
}
return 0;
error:
free_mixer_list(adev);
return -ENODEV;
}
static const char *get_snd_device_name(snd_device_t snd_device)
{
const char *name = NULL;
if (snd_device >= SND_DEVICE_MIN && snd_device < SND_DEVICE_MAX)
name = device_table[snd_device];
ALOGE_IF(name == NULL, "%s: invalid snd device %d", __func__, snd_device);
return name;
}
static const char *get_snd_device_display_name(snd_device_t snd_device)
{
const char *name = get_snd_device_name(snd_device);
if (name == NULL)
name = "SND DEVICE NOT FOUND";
return name;
}
static struct pcm_device_profile *get_pcm_device(usecase_type_t uc_type, audio_devices_t devices)
{
int i;
devices &= ~AUDIO_DEVICE_BIT_IN;
for (i = 0; pcm_devices[i] != NULL; i++) {
if ((pcm_devices[i]->type == uc_type) &&
(devices & pcm_devices[i]->devices))
break;
}
return pcm_devices[i];
}
static struct audio_usecase *get_usecase_from_id(struct audio_device *adev,
audio_usecase_t uc_id)
{
struct audio_usecase *usecase;
struct listnode *node;
list_for_each(node, &adev->usecase_list) {
usecase = node_to_item(node, struct audio_usecase, adev_list_node);
if (usecase->id == uc_id)
return usecase;
}
return NULL;
}
static struct audio_usecase *get_usecase_from_type(struct audio_device *adev,
usecase_type_t type)
{
struct audio_usecase *usecase;
struct listnode *node;
list_for_each(node, &adev->usecase_list) {
usecase = node_to_item(node, struct audio_usecase, adev_list_node);
if (usecase->type & type)
return usecase;
}
return NULL;
}
/* always called with adev lock held */
static int set_voice_volume_l(struct audio_device *adev, float volume)
{
int err = 0;
(void)volume;
if (adev->mode == AUDIO_MODE_IN_CALL) {
/* TODO */
}
return err;
}
static snd_device_t get_output_snd_device(struct audio_device *adev, audio_devices_t devices)
{
audio_mode_t mode = adev->mode;
snd_device_t snd_device = SND_DEVICE_NONE;
ALOGV("%s: enter: output devices(%#x), mode(%d)", __func__, devices, mode);
if (devices == AUDIO_DEVICE_NONE ||
devices & AUDIO_DEVICE_BIT_IN) {
ALOGV("%s: Invalid output devices (%#x)", __func__, devices);
goto exit;
}
if (mode == AUDIO_MODE_IN_CALL) {
if (devices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE ||
devices & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
if (adev->tty_mode == TTY_MODE_FULL)
snd_device = SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES;
else if (adev->tty_mode == TTY_MODE_VCO)
snd_device = SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES;
else if (adev->tty_mode == TTY_MODE_HCO)
snd_device = SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET;
else
snd_device = SND_DEVICE_OUT_VOICE_HEADPHONES;
} else if (devices & AUDIO_DEVICE_OUT_ALL_SCO) {
snd_device = SND_DEVICE_OUT_BT_SCO;
} else if (devices & AUDIO_DEVICE_OUT_SPEAKER) {
snd_device = SND_DEVICE_OUT_VOICE_SPEAKER;
} else if (devices & AUDIO_DEVICE_OUT_EARPIECE) {
snd_device = SND_DEVICE_OUT_HANDSET;
}
if (snd_device != SND_DEVICE_NONE) {
goto exit;
}
}
if (popcount(devices) == 2) {
if (devices == (AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
AUDIO_DEVICE_OUT_SPEAKER)) {
snd_device = SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES;
} else if (devices == (AUDIO_DEVICE_OUT_WIRED_HEADSET |
AUDIO_DEVICE_OUT_SPEAKER)) {
snd_device = SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES;
} else {
ALOGE("%s: Invalid combo device(%#x)", __func__, devices);
goto exit;
}
if (snd_device != SND_DEVICE_NONE) {
goto exit;
}
}
if (popcount(devices) != 1) {
ALOGE("%s: Invalid output devices(%#x)", __func__, devices);
goto exit;
}
if (devices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE ||
devices & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
snd_device = SND_DEVICE_OUT_HEADPHONES;
} else if (devices & AUDIO_DEVICE_OUT_SPEAKER) {
snd_device = SND_DEVICE_OUT_SPEAKER;
} else if (devices & AUDIO_DEVICE_OUT_ALL_SCO) {
snd_device = SND_DEVICE_OUT_BT_SCO;
} else if (devices & AUDIO_DEVICE_OUT_EARPIECE) {
snd_device = SND_DEVICE_OUT_HANDSET;
} else {
ALOGE("%s: Unknown device(s) %#x", __func__, devices);
}
exit:
ALOGV("%s: exit: snd_device(%s)", __func__, device_table[snd_device]);
return snd_device;
}
static snd_device_t get_input_snd_device(struct audio_device *adev, audio_devices_t out_device)
{
audio_source_t source;
audio_mode_t mode = adev->mode;
audio_devices_t in_device;
audio_channel_mask_t channel_mask;
snd_device_t snd_device = SND_DEVICE_NONE;
struct stream_in *active_input = NULL;
struct audio_usecase *usecase;
usecase = get_usecase_from_type(adev, PCM_CAPTURE|VOICE_CALL);
if (usecase != NULL) {
active_input = (struct stream_in *)usecase->stream;
}
source = (active_input == NULL) ?
AUDIO_SOURCE_DEFAULT : active_input->source;
in_device = ((active_input == NULL) ?
AUDIO_DEVICE_NONE : active_input->devices)
& ~AUDIO_DEVICE_BIT_IN;
channel_mask = (active_input == NULL) ?
AUDIO_CHANNEL_IN_MONO : active_input->main_channels;
ALOGV("%s: enter: out_device(%#x) in_device(%#x)",
__func__, out_device, in_device);
if (mode == AUDIO_MODE_IN_CALL) {
if (out_device == AUDIO_DEVICE_NONE) {
ALOGE("%s: No output device set for voice call", __func__);
goto exit;
}
if (adev->tty_mode != TTY_MODE_OFF) {
if (out_device & AUDIO_DEVICE_OUT_WIRED_HEADPHONE ||
out_device & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
switch (adev->tty_mode) {
case TTY_MODE_FULL:
snd_device = SND_DEVICE_IN_VOICE_TTY_FULL_HEADSET_MIC;
break;
case TTY_MODE_VCO:
snd_device = SND_DEVICE_IN_VOICE_TTY_VCO_HANDSET_MIC;
break;
case TTY_MODE_HCO:
snd_device = SND_DEVICE_IN_VOICE_TTY_HCO_HEADSET_MIC;
break;
default:
ALOGE("%s: Invalid TTY mode (%#x)", __func__, adev->tty_mode);
}
goto exit;
}
}
if (out_device & AUDIO_DEVICE_OUT_EARPIECE ||
out_device & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
snd_device = SND_DEVICE_IN_HANDSET_MIC;
} else if (out_device & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
snd_device = SND_DEVICE_IN_VOICE_HEADSET_MIC;
} else if (out_device & AUDIO_DEVICE_OUT_ALL_SCO) {
snd_device = SND_DEVICE_IN_BT_SCO_MIC ;
} else if (out_device & AUDIO_DEVICE_OUT_SPEAKER) {
snd_device = SND_DEVICE_IN_VOICE_SPEAKER_MIC;
}
} else if (source == AUDIO_SOURCE_CAMCORDER) {
if (in_device & AUDIO_DEVICE_IN_BUILTIN_MIC ||
in_device & AUDIO_DEVICE_IN_BACK_MIC) {
snd_device = SND_DEVICE_IN_CAMCORDER_MIC;
}
} else if (source == AUDIO_SOURCE_VOICE_RECOGNITION) {
if (in_device & AUDIO_DEVICE_IN_BUILTIN_MIC) {
if (adev->dualmic_config == DUALMIC_CONFIG_1) {
if (channel_mask == AUDIO_CHANNEL_IN_FRONT_BACK)
snd_device = SND_DEVICE_IN_VOICE_REC_DMIC_1;
else if (adev->ns_in_voice_rec)
snd_device = SND_DEVICE_IN_VOICE_REC_DMIC_NS_1;
}
if (snd_device == SND_DEVICE_NONE) {
snd_device = SND_DEVICE_IN_VOICE_REC_MIC;
}
} else if (in_device & AUDIO_DEVICE_IN_WIRED_HEADSET) {
snd_device = SND_DEVICE_IN_VOICE_REC_HEADSET_MIC;
}
} else if (source == AUDIO_SOURCE_VOICE_COMMUNICATION || source == AUDIO_SOURCE_MIC) {
if (out_device & AUDIO_DEVICE_OUT_SPEAKER)
in_device = AUDIO_DEVICE_IN_BACK_MIC;
if (active_input) {
if (active_input->enable_aec) {
if (in_device & AUDIO_DEVICE_IN_BACK_MIC) {
snd_device = SND_DEVICE_IN_SPEAKER_MIC_AEC;
} else if (in_device & AUDIO_DEVICE_IN_BUILTIN_MIC) {
if (out_device & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
snd_device = SND_DEVICE_IN_SPEAKER_MIC_AEC;
} else {
snd_device = SND_DEVICE_IN_HANDSET_MIC_AEC;
}
} else if (in_device & AUDIO_DEVICE_IN_WIRED_HEADSET) {
snd_device = SND_DEVICE_IN_HEADSET_MIC_AEC;
}
}
/* TODO: set echo reference */
}
} else if (source == AUDIO_SOURCE_DEFAULT) {
goto exit;
}
if (snd_device != SND_DEVICE_NONE) {
goto exit;
}
if (in_device != AUDIO_DEVICE_NONE &&
!(in_device & AUDIO_DEVICE_IN_VOICE_CALL) &&
!(in_device & AUDIO_DEVICE_IN_COMMUNICATION)) {
if (in_device & AUDIO_DEVICE_IN_BUILTIN_MIC) {
snd_device = SND_DEVICE_IN_HANDSET_MIC;
} else if (in_device & AUDIO_DEVICE_IN_BACK_MIC) {
snd_device = SND_DEVICE_IN_SPEAKER_MIC;
} else if (in_device & AUDIO_DEVICE_IN_WIRED_HEADSET) {
snd_device = SND_DEVICE_IN_HEADSET_MIC;
} else if (in_device & AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET) {
snd_device = SND_DEVICE_IN_BT_SCO_MIC ;
} else if (in_device & AUDIO_DEVICE_IN_AUX_DIGITAL) {
snd_device = SND_DEVICE_IN_HDMI_MIC;
} else {
ALOGE("%s: Unknown input device(s) %#x", __func__, in_device);
ALOGW("%s: Using default handset-mic", __func__);
snd_device = SND_DEVICE_IN_HANDSET_MIC;
}
} else {
if (out_device & AUDIO_DEVICE_OUT_EARPIECE) {
snd_device = SND_DEVICE_IN_HANDSET_MIC;
} else if (out_device & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
snd_device = SND_DEVICE_IN_HEADSET_MIC;
} else if (out_device & AUDIO_DEVICE_OUT_SPEAKER) {
snd_device = SND_DEVICE_IN_SPEAKER_MIC;
} else if (out_device & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
snd_device = SND_DEVICE_IN_HANDSET_MIC;
} else if (out_device & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET) {
snd_device = SND_DEVICE_IN_BT_SCO_MIC;
} else {
ALOGE("%s: Unknown output device(s) %#x", __func__, out_device);
ALOGW("%s: Using default handset-mic", __func__);
snd_device = SND_DEVICE_IN_HANDSET_MIC;
}
}
exit:
ALOGV("%s: exit: in_snd_device(%s)", __func__, device_table[snd_device]);
return snd_device;
}
static int set_hdmi_channels(struct audio_device *adev, int channel_count)
{
struct mixer_ctl *ctl;
const char *mixer_ctl_name = "";
(void)adev;
(void)channel_count;
/* TODO */
return 0;
}
static int edid_get_max_channels(struct audio_device *adev)
{
int max_channels = 2;
struct mixer_ctl *ctl;
(void)adev;
/* TODO */
return max_channels;
}
/* Delay in Us */
static int64_t render_latency(audio_usecase_t usecase)
{
(void)usecase;
/* TODO */
return 0;
}
static int enable_snd_device(struct audio_device *adev,
struct audio_usecase *uc_info,
snd_device_t snd_device,
bool update_mixer)
{
struct mixer_card *mixer_card;
struct listnode *node;
const char *snd_device_name = get_snd_device_name(snd_device);
if (snd_device_name == NULL)
return -EINVAL;
if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES) {
ALOGV("Request to enable combo device: enable individual devices\n");
enable_snd_device(adev, uc_info, SND_DEVICE_OUT_SPEAKER, update_mixer);
enable_snd_device(adev, uc_info, SND_DEVICE_OUT_HEADPHONES, update_mixer);
return 0;
}
adev->snd_dev_ref_cnt[snd_device]++;
if (adev->snd_dev_ref_cnt[snd_device] > 1) {
ALOGV("%s: snd_device(%d: %s) is already active",
__func__, snd_device, snd_device_name);
return 0;
}
ALOGV("%s: snd_device(%d: %s)", __func__,
snd_device, snd_device_name);
list_for_each(node, &uc_info->mixer_list) {
mixer_card = node_to_item(node, struct mixer_card, uc_list_node[uc_info->id]);
audio_route_apply_path(mixer_card->audio_route, snd_device_name);
if (update_mixer)
audio_route_update_mixer(mixer_card->audio_route);
}
return 0;
}
static int disable_snd_device(struct audio_device *adev,
struct audio_usecase *uc_info,
snd_device_t snd_device,
bool update_mixer)
{
struct mixer_card *mixer_card;
struct listnode *node;
const char *snd_device_name = get_snd_device_name(snd_device);
if (snd_device_name == NULL)
return -EINVAL;
if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES) {
ALOGV("Request to disable combo device: disable individual devices\n");
disable_snd_device(adev, uc_info, SND_DEVICE_OUT_SPEAKER, update_mixer);
disable_snd_device(adev, uc_info, SND_DEVICE_OUT_HEADPHONES, update_mixer);
return 0;
}
if (adev->snd_dev_ref_cnt[snd_device] <= 0) {
ALOGE("%s: device ref cnt is already 0", __func__);
return -EINVAL;
}
adev->snd_dev_ref_cnt[snd_device]--;
if (adev->snd_dev_ref_cnt[snd_device] == 0) {
ALOGV("%s: snd_device(%d: %s)", __func__,
snd_device, snd_device_name);
list_for_each(node, &uc_info->mixer_list) {
mixer_card = node_to_item(node, struct mixer_card, uc_list_node[uc_info->id]);
audio_route_reset_path(mixer_card->audio_route, snd_device_name);
if (update_mixer)
audio_route_update_mixer(mixer_card->audio_route);
}
}
return 0;
}
static int select_devices(struct audio_device *adev,
audio_usecase_t uc_id)
{
snd_device_t out_snd_device = SND_DEVICE_NONE;
snd_device_t in_snd_device = SND_DEVICE_NONE;
struct audio_usecase *usecase = NULL;
struct audio_usecase *vc_usecase = NULL;
struct listnode *node;
struct stream_in *active_input = NULL;
struct stream_out *active_out;
struct mixer_card *mixer_card;
ALOGV("%s: usecase(%d)", __func__, uc_id);
if (uc_id == USECASE_AUDIO_CAPTURE_HOTWORD)
return 0;
usecase = get_usecase_from_type(adev, PCM_CAPTURE|VOICE_CALL);
if (usecase != NULL) {
active_input = (struct stream_in *)usecase->stream;
}
usecase = get_usecase_from_id(adev, uc_id);
if (usecase == NULL) {
ALOGE("%s: Could not find the usecase(%d)", __func__, uc_id);
return -EINVAL;
}
active_out = (struct stream_out *)usecase->stream;
if (usecase->type == VOICE_CALL) {
out_snd_device = get_output_snd_device(adev, active_out->devices);
in_snd_device = get_input_snd_device(adev, active_out->devices);
usecase->devices = active_out->devices;
} else {
/*
* If the voice call is active, use the sound devices of voice call usecase
* so that it would not result any device switch. All the usecases will
* be switched to new device when select_devices() is called for voice call
* usecase.
*/
if (adev->in_call) {
vc_usecase = get_usecase_from_id(adev, USECASE_VOICE_CALL);
if (usecase == NULL) {
ALOGE("%s: Could not find the voice call usecase", __func__);
} else {
in_snd_device = vc_usecase->in_snd_device;
out_snd_device = vc_usecase->out_snd_device;
}
}
if (usecase->type == PCM_PLAYBACK) {
usecase->devices = active_out->devices;
in_snd_device = SND_DEVICE_NONE;
if (out_snd_device == SND_DEVICE_NONE) {
out_snd_device = get_output_snd_device(adev, active_out->devices);
if (active_out == adev->primary_output &&
active_input &&
active_input->source == AUDIO_SOURCE_VOICE_COMMUNICATION) {
select_devices(adev, active_input->usecase);
}
}
} else if (usecase->type == PCM_CAPTURE) {
usecase->devices = ((struct stream_in *)usecase->stream)->devices;
out_snd_device = SND_DEVICE_NONE;
if (in_snd_device == SND_DEVICE_NONE) {
if (active_input->source == AUDIO_SOURCE_VOICE_COMMUNICATION &&
adev->primary_output && !adev->primary_output->standby) {
in_snd_device = get_input_snd_device(adev, adev->primary_output->devices);
} else {
in_snd_device = get_input_snd_device(adev, AUDIO_DEVICE_NONE);
}
}
}
}
if (out_snd_device == usecase->out_snd_device &&
in_snd_device == usecase->in_snd_device) {
return 0;
}
ALOGV("%s: out_snd_device(%d: %s) in_snd_device(%d: %s)", __func__,
out_snd_device, get_snd_device_display_name(out_snd_device),
in_snd_device, get_snd_device_display_name(in_snd_device));
/* Disable current sound devices */
if (usecase->out_snd_device != SND_DEVICE_NONE) {
pthread_mutex_lock(&adev->tfa9895_lock);
disable_snd_device(adev, usecase, usecase->out_snd_device, false);
pthread_mutex_unlock(&adev->tfa9895_lock);
}
if (usecase->in_snd_device != SND_DEVICE_NONE) {
disable_snd_device(adev, usecase, usecase->in_snd_device, false);
}
/* Enable new sound devices */
if (out_snd_device != SND_DEVICE_NONE) {
enable_snd_device(adev, usecase, out_snd_device, false);
}
if (in_snd_device != SND_DEVICE_NONE) {
enable_snd_device(adev, usecase, in_snd_device, false);
}
list_for_each(node, &usecase->mixer_list) {
mixer_card = node_to_item(node, struct mixer_card, uc_list_node[usecase->id]);
audio_route_update_mixer(mixer_card->audio_route);
}
usecase->in_snd_device = in_snd_device;
usecase->out_snd_device = out_snd_device;
if (out_snd_device != SND_DEVICE_NONE)
if (usecase->devices & (AUDIO_DEVICE_OUT_WIRED_HEADSET | AUDIO_DEVICE_OUT_WIRED_HEADPHONE))
if (adev->htc_acoustic_set_rt5506_amp != NULL)
adev->htc_acoustic_set_rt5506_amp(adev->mode, usecase->devices);
return 0;
}
static ssize_t read_frames(struct stream_in *in, void *buffer, ssize_t frames);
static int do_in_standby_l(struct stream_in *in);
#ifdef PREPROCESSING_ENABLED
static void get_capture_reference_delay(struct stream_in *in,
size_t frames __unused,
struct echo_reference_buffer *buffer)
{
ALOGVV("%s: enter:)", __func__);
/* read frames available in kernel driver buffer */
unsigned int kernel_frames;
struct timespec tstamp;
long buf_delay;
long kernel_delay;
long delay_ns;
struct pcm_device *ref_device;
long rsmp_delay = 0;
ref_device = node_to_item(list_tail(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
if (pcm_get_htimestamp(ref_device->pcm, &kernel_frames, &tstamp) < 0) {
buffer->time_stamp.tv_sec = 0;
buffer->time_stamp.tv_nsec = 0;
buffer->delay_ns = 0;
ALOGW("read get_capture_reference_delay(): pcm_htimestamp error");
return;
}
/* adjust render time stamp with delay added by current driver buffer.
* Add the duration of current frame as we want the render time of the last
* sample being written. */
kernel_delay = (long)(((int64_t)kernel_frames * 1000000000) / ref_device->pcm_profile->config.rate);
buffer->time_stamp = tstamp;
buffer->delay_ns = kernel_delay;
ALOGVV("get_capture_reference_delay_time_stamp Secs: [%10ld], nSecs: [%9ld], kernel_frames: [%5d],"
" delay_ns: [%d] , frames:[%zd]",
buffer->time_stamp.tv_sec , buffer->time_stamp.tv_nsec, kernel_frames, buffer->delay_ns, frames);
}
static void get_capture_delay(struct stream_in *in,
size_t frames __unused,
struct echo_reference_buffer *buffer)
{
ALOGVV("%s: enter:)", __func__);
/* read frames available in kernel driver buffer */
unsigned int kernel_frames;
struct timespec tstamp;
long buf_delay;
long rsmp_delay;
long kernel_delay;
long delay_ns;
struct pcm_device *pcm_device;
pcm_device = node_to_item(list_head(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
if (pcm_get_htimestamp(pcm_device->pcm, &kernel_frames, &tstamp) < 0) {
buffer->time_stamp.tv_sec = 0;
buffer->time_stamp.tv_nsec = 0;
buffer->delay_ns = 0;
ALOGW("read get_capture_delay(): pcm_htimestamp error");
return;
}
/* read frames available in audio HAL input buffer
* add number of frames being read as we want the capture time of first sample
* in current buffer */
/* frames in in->read_buf are at driver sampling rate while frames in in->proc_buf are
* at requested sampling rate */
buf_delay = (long)(((int64_t)(in->read_buf_frames) * 1000000000) / in->config.rate +
((int64_t)(in->proc_buf_frames) * 1000000000) / in->requested_rate );
/* add delay introduced by resampler */
rsmp_delay = 0;
if (in->resampler) {
rsmp_delay = in->resampler->delay_ns(in->resampler);
}
kernel_delay = (long)(((int64_t)kernel_frames * 1000000000) / in->config.rate);
delay_ns = kernel_delay + buf_delay + rsmp_delay;
buffer->time_stamp = tstamp;
buffer->delay_ns = delay_ns;
ALOGVV("get_capture_delay_time_stamp Secs: [%10ld], nSecs: [%9ld], kernel_frames:[%5d],"
" delay_ns: [%d], kernel_delay:[%ld], buf_delay:[%ld], rsmp_delay:[%ld], "
"in->read_buf_frames:[%zd], in->proc_buf_frames:[%zd], frames:[%zd]",
buffer->time_stamp.tv_sec , buffer->time_stamp.tv_nsec, kernel_frames,
buffer->delay_ns, kernel_delay, buf_delay, rsmp_delay,
in->read_buf_frames, in->proc_buf_frames, frames);
}
static int32_t update_echo_reference(struct stream_in *in, size_t frames)
{
ALOGVV("%s: enter:), in->config.channels(%d)", __func__,in->config.channels);
struct echo_reference_buffer b;
b.delay_ns = 0;
struct pcm_device *pcm_device;
pcm_device = node_to_item(list_head(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
ALOGVV("update_echo_reference, in->config.channels(%d), frames = [%zd], in->ref_buf_frames = [%zd], "
"b.frame_count = [%zd]",
in->config.channels, frames, in->ref_buf_frames, frames - in->ref_buf_frames);
if (in->ref_buf_frames < frames) {
if (in->ref_buf_size < frames) {
in->ref_buf_size = frames;
in->ref_buf = (int16_t *)realloc(in->ref_buf, pcm_frames_to_bytes(pcm_device->pcm, frames));
ALOG_ASSERT((in->ref_buf != NULL),
"update_echo_reference() failed to reallocate ref_buf");
ALOGVV("update_echo_reference(): ref_buf %p extended to %d bytes",
in->ref_buf, pcm_frames_to_bytes(pcm_device->pcm, frames));
}
b.frame_count = frames - in->ref_buf_frames;
b.raw = (void *)(in->ref_buf + in->ref_buf_frames * in->config.channels);
get_capture_delay(in, frames, &b);
if (in->echo_reference->read(in->echo_reference, &b) == 0)
{
in->ref_buf_frames += b.frame_count;
ALOGVV("update_echo_reference(): in->ref_buf_frames:[%zd], "
"in->ref_buf_size:[%zd], frames:[%zd], b.frame_count:[%zd]",
in->ref_buf_frames, in->ref_buf_size, frames, b.frame_count);
}
} else
ALOGW("update_echo_reference(): NOT enough frames to read ref buffer");
return b.delay_ns;
}
static int set_preprocessor_param(effect_handle_t handle,
effect_param_t *param)
{
uint32_t size = sizeof(int);
uint32_t psize = ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) +
param->vsize;
int status = (*handle)->command(handle,
EFFECT_CMD_SET_PARAM,
sizeof (effect_param_t) + psize,
param,
&size,
&param->status);
if (status == 0)
status = param->status;
return status;
}
static int set_preprocessor_echo_delay(effect_handle_t handle,
int32_t delay_us)
{
struct {
effect_param_t param;
uint32_t data_0;
int32_t data_1;
} buf;
memset(&buf, 0, sizeof(buf));
buf.param.psize = sizeof(uint32_t);
buf.param.vsize = sizeof(uint32_t);
buf.data_0 = AEC_PARAM_ECHO_DELAY;
buf.data_1 = delay_us;
return set_preprocessor_param(handle, &buf.param);
}
static void push_echo_reference(struct stream_in *in, size_t frames)
{
ALOGVV("%s: enter:)", __func__);
/* read frames from echo reference buffer and update echo delay
* in->ref_buf_frames is updated with frames available in in->ref_buf */
int32_t delay_us = update_echo_reference(in, frames)/1000;
int32_t size_in_bytes = 0;
int i;
audio_buffer_t buf;
if (in->ref_buf_frames < frames)
frames = in->ref_buf_frames;
buf.frameCount = frames;
buf.raw = in->ref_buf;
for (i = 0; i < in->num_preprocessors; i++) {
if ((*in->preprocessors[i].effect_itfe)->process_reverse == NULL)
continue;
ALOGVV("%s: effect_itfe)->process_reverse() BEGIN i=(%d) ", __func__, i);
(*in->preprocessors[i].effect_itfe)->process_reverse(in->preprocessors[i].effect_itfe,
&buf,
NULL);
ALOGVV("%s: effect_itfe)->process_reverse() END i=(%d) ", __func__, i);
set_preprocessor_echo_delay(in->preprocessors[i].effect_itfe, delay_us);
}
in->ref_buf_frames -= buf.frameCount;
ALOGVV("%s: in->ref_buf_frames(%zd), in->config.channels(%d) ",
__func__, in->ref_buf_frames, in->config.channels);
if (in->ref_buf_frames) {
memcpy(in->ref_buf,
in->ref_buf + buf.frameCount * in->config.channels,
in->ref_buf_frames * in->config.channels * sizeof(int16_t));
}
}
static void put_echo_reference(struct audio_device *adev,
struct echo_reference_itfe *reference)
{
ALOGV("%s: enter:)", __func__);
int32_t prev_generation = adev->echo_reference_generation;
struct stream_out *out = adev->primary_output;
if (adev->echo_reference != NULL &&
reference == adev->echo_reference) {
/* echo reference is taken from the low latency output stream used
* for voice use cases */
adev->echo_reference = NULL;
android_atomic_inc(&adev->echo_reference_generation);
if (out != NULL && out->usecase == USECASE_AUDIO_PLAYBACK) {
// if the primary output is in standby or did not pick the echo reference yet
// we can safely get rid of it here.
// otherwise, out_write() or out_standby() will detect the change in echo reference
// generation and release the echo reference owned by the stream.
if ((out->echo_reference_generation != prev_generation) || out->standby)
release_echo_reference(reference);
} else {
release_echo_reference(reference);
}
ALOGV("release_echo_reference");
}
}
static struct echo_reference_itfe *get_echo_reference(struct audio_device *adev,
audio_format_t format __unused,
uint32_t channel_count,
uint32_t sampling_rate)
{
ALOGV("%s: enter:)", __func__);
put_echo_reference(adev, adev->echo_reference);
/* echo reference is taken from the low latency output stream used
* for voice use cases */
if (adev->primary_output!= NULL && adev->primary_output->usecase == USECASE_AUDIO_PLAYBACK &&
!adev->primary_output->standby) {
struct audio_stream *stream =
&adev->primary_output->stream.common;
uint32_t wr_channel_count = audio_channel_count_from_out_mask(stream->get_channels(stream));
uint32_t wr_sampling_rate = stream->get_sample_rate(stream);
ALOGV("Calling create_echo_reference");
int status = create_echo_reference(AUDIO_FORMAT_PCM_16_BIT,
channel_count,
sampling_rate,
AUDIO_FORMAT_PCM_16_BIT,
wr_channel_count,
wr_sampling_rate,
&adev->echo_reference);
if (status == 0)
android_atomic_inc(&adev->echo_reference_generation);
}
return adev->echo_reference;
}
#ifdef HW_AEC_LOOPBACK
static int get_hw_echo_reference(struct stream_in *in)
{
struct pcm_device_profile *ref_pcm_profile;
struct pcm_device *ref_device;
struct audio_device *adev = in->dev;
in->hw_echo_reference = false;
if (adev->primary_output!= NULL &&
!adev->primary_output->standby &&
adev->primary_output->usecase == USECASE_AUDIO_PLAYBACK &&
adev->primary_output->devices == AUDIO_DEVICE_OUT_SPEAKER) {
struct audio_stream *stream = &adev->primary_output->stream.common;
// TODO: currently there is no low latency mode for aec reference.
ref_pcm_profile = get_pcm_device(PCM_CAPTURE, pcm_device_capture_loopback_aec.devices);
if (ref_pcm_profile == NULL) {
ALOGE("%s: Could not find PCM device id for the usecase(%d)",
__func__, pcm_device_capture_loopback_aec.devices);
return -EINVAL;
}
ref_device = (struct pcm_device *)calloc(1, sizeof(struct pcm_device));
ref_device->pcm_profile = ref_pcm_profile;
ALOGV("%s: ref_device rate:%d, ch:%d", __func__, ref_pcm_profile->config.rate, ref_pcm_profile->config.channels);
ref_device->pcm = pcm_open(ref_device->pcm_profile->card, ref_device->pcm_profile->id, PCM_IN | PCM_MONOTONIC, &ref_device->pcm_profile->config);
if (ref_device->pcm && !pcm_is_ready(ref_device->pcm)) {
ALOGE("%s: %s", __func__, pcm_get_error(ref_device->pcm));
pcm_close(ref_device->pcm);
ref_device->pcm = NULL;
return -EIO;
}
list_add_tail(&in->pcm_dev_list, &ref_device->stream_list_node);
in->hw_echo_reference = true;
ALOGV("%s: hw_echo_reference is true", __func__);
}
return 0;
}
#endif
static int get_playback_delay(struct stream_out *out,
size_t frames,
struct echo_reference_buffer *buffer)
{
unsigned int kernel_frames;
int status;
int primary_pcm = 0;
struct pcm_device *pcm_device;
pcm_device = node_to_item(list_head(&out->pcm_dev_list),
struct pcm_device, stream_list_node);
status = pcm_get_htimestamp(pcm_device->pcm, &kernel_frames, &buffer->time_stamp);
if (status < 0) {
buffer->time_stamp.tv_sec = 0;
buffer->time_stamp.tv_nsec = 0;
buffer->delay_ns = 0;
ALOGV("get_playback_delay(): pcm_get_htimestamp error,"
"setting playbackTimestamp to 0");
return status;
}
kernel_frames = pcm_get_buffer_size(pcm_device->pcm) - kernel_frames;
/* adjust render time stamp with delay added by current driver buffer.
* Add the duration of current frame as we want the render time of the last
* sample being written. */
buffer->delay_ns = (long)(((int64_t)(kernel_frames + frames)* 1000000000)/
out->config.rate);
ALOGVV("get_playback_delay_time_stamp Secs: [%10ld], nSecs: [%9ld], kernel_frames: [%5u], delay_ns: [%d],",
buffer->time_stamp.tv_sec, buffer->time_stamp.tv_nsec, kernel_frames, buffer->delay_ns);
return 0;
}
#define GET_COMMAND_STATUS(status, fct_status, cmd_status) \
do { \
if (fct_status != 0) \
status = fct_status; \
else if (cmd_status != 0) \
status = cmd_status; \
} while(0)
static int in_configure_reverse(struct stream_in *in)
{
int32_t cmd_status;
uint32_t size = sizeof(int);
effect_config_t config;
int32_t status = 0;
int32_t fct_status = 0;
int i;
ALOGV("%s: enter: in->num_preprocessors(%d)", __func__, in->num_preprocessors);
if (in->num_preprocessors > 0) {
config.inputCfg.channels = in->main_channels;
config.outputCfg.channels = in->main_channels;
config.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
config.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
config.inputCfg.samplingRate = in->requested_rate;
config.outputCfg.samplingRate = in->requested_rate;
config.inputCfg.mask =
( EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT );
config.outputCfg.mask =
( EFFECT_CONFIG_SMP_RATE | EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT );
for (i = 0; i < in->num_preprocessors; i++)
{
if ((*in->preprocessors[i].effect_itfe)->process_reverse == NULL)
continue;
fct_status = (*(in->preprocessors[i].effect_itfe))->command(
in->preprocessors[i].effect_itfe,
EFFECT_CMD_SET_CONFIG_REVERSE,
sizeof(effect_config_t),
&config,
&size,
&cmd_status);
ALOGV("%s: calling EFFECT_CMD_SET_CONFIG_REVERSE",__func__);
GET_COMMAND_STATUS(status, fct_status, cmd_status);
}
}
return status;
}
#define MAX_NUM_CHANNEL_CONFIGS 10
static void in_read_audio_effect_channel_configs(struct stream_in *in __unused,
struct effect_info_s *effect_info)
{
/* size and format of the cmd are defined in hardware/audio_effect.h */
effect_handle_t effect = effect_info->effect_itfe;
uint32_t cmd_size = 2 * sizeof(uint32_t);
uint32_t cmd[] = { EFFECT_FEATURE_AUX_CHANNELS, MAX_NUM_CHANNEL_CONFIGS };
/* reply = status + number of configs (n) + n x channel_config_t */
uint32_t reply_size =
2 * sizeof(uint32_t) + (MAX_NUM_CHANNEL_CONFIGS * sizeof(channel_config_t));
int32_t reply[reply_size];
int32_t cmd_status;
ALOG_ASSERT((effect_info->num_channel_configs == 0),
"in_read_audio_effect_channel_configs() num_channel_configs not cleared");
ALOG_ASSERT((effect_info->channel_configs == NULL),
"in_read_audio_effect_channel_configs() channel_configs not cleared");
/* if this command is not supported, then the effect is supposed to return -EINVAL.
* This error will be interpreted as if the effect supports the main_channels but does not
* support any aux_channels */
cmd_status = (*effect)->command(effect,
EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS,
cmd_size,
(void*)&cmd,
&reply_size,
(void*)&reply);
if (cmd_status != 0) {
ALOGV("in_read_audio_effect_channel_configs(): "
"fx->command returned %d", cmd_status);
return;
}
if (reply[0] != 0) {
ALOGW("in_read_audio_effect_channel_configs(): "
"command EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS error %d num configs %d",
reply[0], (reply[0] == -ENOMEM) ? reply[1] : MAX_NUM_CHANNEL_CONFIGS);
return;
}
/* the feature is not supported */
ALOGV("in_read_audio_effect_channel_configs()(): "
"Feature supported and adding %d channel configs to the list", reply[1]);
effect_info->num_channel_configs = reply[1];
effect_info->channel_configs =
(channel_config_t *) malloc(sizeof(channel_config_t) * reply[1]); /* n x configs */
memcpy(effect_info->channel_configs, (reply + 2), sizeof(channel_config_t) * reply[1]);
}
#define NUM_IN_AUX_CNL_CONFIGS 2
static const channel_config_t in_aux_cnl_configs[NUM_IN_AUX_CNL_CONFIGS] = {
{ AUDIO_CHANNEL_IN_FRONT , AUDIO_CHANNEL_IN_BACK},
{ AUDIO_CHANNEL_IN_STEREO , AUDIO_CHANNEL_IN_RIGHT}
};
static uint32_t in_get_aux_channels(struct stream_in *in)
{
int i;
channel_config_t new_chcfg = {0, 0};
if (in->num_preprocessors == 0)
return 0;
/* do not enable dual mic configurations when capturing from other microphones than
* main or sub */
if (!(in->devices & (AUDIO_DEVICE_IN_BUILTIN_MIC | AUDIO_DEVICE_IN_BACK_MIC)))
return 0;
/* retain most complex aux channels configuration compatible with requested main channels and
* supported by audio driver and all pre processors */
for (i = 0; i < NUM_IN_AUX_CNL_CONFIGS; i++) {
const channel_config_t *cur_chcfg = &in_aux_cnl_configs[i];
if (cur_chcfg->main_channels == in->main_channels) {
size_t match_cnt;
size_t idx_preproc;
for (idx_preproc = 0, match_cnt = 0;
/* no need to continue if at least one preprocessor doesn't match */
idx_preproc < (size_t)in->num_preprocessors && match_cnt == idx_preproc;
idx_preproc++) {
struct effect_info_s *effect_info = &in->preprocessors[idx_preproc];
size_t idx_chcfg;
for (idx_chcfg = 0; idx_chcfg < effect_info->num_channel_configs; idx_chcfg++) {
if (memcmp(effect_info->channel_configs + idx_chcfg,
cur_chcfg,
sizeof(channel_config_t)) == 0) {
match_cnt++;
break;
}
}
}
/* if all preprocessors match, we have a candidate */
if (match_cnt == (size_t)in->num_preprocessors) {
/* retain most complex aux channels configuration */
if (audio_channel_count_from_in_mask(cur_chcfg->aux_channels) > audio_channel_count_from_in_mask(new_chcfg.aux_channels)) {
new_chcfg = *cur_chcfg;
}
}
}
}
ALOGV("in_get_aux_channels(): return %04x", new_chcfg.aux_channels);
return new_chcfg.aux_channels;
}
static int in_configure_effect_channels(effect_handle_t effect,
channel_config_t *channel_config)
{
int status = 0;
int fct_status;
int32_t cmd_status;
uint32_t reply_size;
effect_config_t config;
uint32_t cmd[(sizeof(uint32_t) + sizeof(channel_config_t) - 1) / sizeof(uint32_t) + 1];
ALOGV("in_configure_effect_channels(): configure effect with channels: [%04x][%04x]",
channel_config->main_channels,
channel_config->aux_channels);
config.inputCfg.mask = EFFECT_CONFIG_CHANNELS;
config.outputCfg.mask = EFFECT_CONFIG_CHANNELS;
reply_size = sizeof(effect_config_t);
fct_status = (*effect)->command(effect,
EFFECT_CMD_GET_CONFIG,
0,
NULL,
&reply_size,
&config);
if (fct_status != 0) {
ALOGE("in_configure_effect_channels(): EFFECT_CMD_GET_CONFIG failed");
return fct_status;
}
config.inputCfg.channels = channel_config->main_channels | channel_config->aux_channels;
config.outputCfg.channels = config.inputCfg.channels;
reply_size = sizeof(uint32_t);
fct_status = (*effect)->command(effect,
EFFECT_CMD_SET_CONFIG,
sizeof(effect_config_t),
&config,
&reply_size,
&cmd_status);
GET_COMMAND_STATUS(status, fct_status, cmd_status);
cmd[0] = EFFECT_FEATURE_AUX_CHANNELS;
memcpy(cmd + 1, channel_config, sizeof(channel_config_t));
reply_size = sizeof(uint32_t);
fct_status = (*effect)->command(effect,
EFFECT_CMD_SET_FEATURE_CONFIG,
sizeof(cmd), //sizeof(uint32_t) + sizeof(channel_config_t),
cmd,
&reply_size,
&cmd_status);
GET_COMMAND_STATUS(status, fct_status, cmd_status);
/* some implementations need to be re-enabled after a config change */
reply_size = sizeof(uint32_t);
fct_status = (*effect)->command(effect,
EFFECT_CMD_ENABLE,
0,
NULL,
&reply_size,
&cmd_status);
GET_COMMAND_STATUS(status, fct_status, cmd_status);
return status;
}
static int in_reconfigure_channels(struct stream_in *in,
effect_handle_t effect,
channel_config_t *channel_config,
bool config_changed) {
int status = 0;
ALOGV("in_reconfigure_channels(): config_changed %d effect %p",
config_changed, effect);
/* if config changed, reconfigure all previously added effects */
if (config_changed) {
int i;
ALOGV("%s: config_changed (%d)", __func__, config_changed);
for (i = 0; i < in->num_preprocessors; i++)
{
int cur_status = in_configure_effect_channels(in->preprocessors[i].effect_itfe,
channel_config);
ALOGV("%s: in_configure_effect_channels i=(%d), [main_channel,aux_channel]=[%d|%d], status=%d",
__func__, i, channel_config->main_channels, channel_config->aux_channels, cur_status);
if (cur_status != 0) {
ALOGV("in_reconfigure_channels(): error %d configuring effect "
"%d with channels: [%04x][%04x]",
cur_status,
i,
channel_config->main_channels,
channel_config->aux_channels);
status = cur_status;
}
}
} else if (effect != NULL && channel_config->aux_channels) {
/* if aux channels config did not change but aux channels are present,
* we still need to configure the effect being added */
status = in_configure_effect_channels(effect, channel_config);
}
return status;
}
static void in_update_aux_channels(struct stream_in *in,
effect_handle_t effect)
{
uint32_t aux_channels;
channel_config_t channel_config;
int status;
aux_channels = in_get_aux_channels(in);
channel_config.main_channels = in->main_channels;
channel_config.aux_channels = aux_channels;
status = in_reconfigure_channels(in,
effect,
&channel_config,
(aux_channels != in->aux_channels));
if (status != 0) {
ALOGV("in_update_aux_channels(): in_reconfigure_channels error %d", status);
/* resetting aux channels configuration */
aux_channels = 0;
channel_config.aux_channels = 0;
in_reconfigure_channels(in, effect, &channel_config, true);
}
ALOGV("%s: aux_channels=%d, in->aux_channels_changed=%d", __func__, aux_channels, in->aux_channels_changed);
if (in->aux_channels != aux_channels) {
in->aux_channels_changed = true;
in->aux_channels = aux_channels;
do_in_standby_l(in);
}
}
#endif
/* This function reads PCM data and:
* - resample if needed
* - process if pre-processors are attached
* - discard unwanted channels
*/
static ssize_t read_and_process_frames(struct stream_in *in, void* buffer, ssize_t frames)
{
ssize_t frames_wr = 0;
audio_buffer_t in_buf;
audio_buffer_t out_buf;
size_t src_channels = in->config.channels;
size_t dst_channels = audio_channel_count_from_in_mask(in->main_channels);
int i;
void *proc_buf_out;
struct pcm_device *pcm_device;
bool has_additional_channels = (dst_channels != src_channels) ? true : false;
#ifdef PREPROCESSING_ENABLED
bool has_processing = (in->num_preprocessors != 0) ? true : false;
#endif
/* Additional channels might be added on top of main_channels:
* - aux_channels (by processing effects)
* - extra channels due to HW limitations
* In case of additional channels, we cannot work inplace
*/
if (has_additional_channels)
proc_buf_out = in->proc_buf_out;
else
proc_buf_out = buffer;
if (list_empty(&in->pcm_dev_list)) {
ALOGE("%s: pcm device list empty", __func__);
return -EINVAL;
}
pcm_device = node_to_item(list_head(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
#ifdef PREPROCESSING_ENABLED
if (has_processing) {
/* since all the processing below is done in frames and using the config.channels
* as the number of channels, no changes is required in case aux_channels are present */
while (frames_wr < frames) {
/* first reload enough frames at the end of process input buffer */
if (in->proc_buf_frames < (size_t)frames) {
ssize_t frames_rd;
if (in->proc_buf_size < (size_t)frames) {
size_t size_in_bytes = pcm_frames_to_bytes(pcm_device->pcm, frames);
in->proc_buf_size = (size_t)frames;
in->proc_buf_in = (int16_t *)realloc(in->proc_buf_in, size_in_bytes);
ALOG_ASSERT((in->proc_buf_in != NULL),
"process_frames() failed to reallocate proc_buf_in");
if (has_additional_channels) {
in->proc_buf_out = (int16_t *)realloc(in->proc_buf_out, size_in_bytes);
ALOG_ASSERT((in->proc_buf_out != NULL),
"process_frames() failed to reallocate proc_buf_out");
proc_buf_out = in->proc_buf_out;
}
}
frames_rd = read_frames(in,
in->proc_buf_in +
in->proc_buf_frames * in->config.channels,
frames - in->proc_buf_frames);
if (frames_rd < 0) {
/* Return error code */
frames_wr = frames_rd;
break;
}
in->proc_buf_frames += frames_rd;
}
if (in->echo_reference != NULL) {
push_echo_reference(in, in->proc_buf_frames);
}
/* in_buf.frameCount and out_buf.frameCount indicate respectively
* the maximum number of frames to be consumed and produced by process() */
in_buf.frameCount = in->proc_buf_frames;
in_buf.s16 = in->proc_buf_in;
out_buf.frameCount = frames - frames_wr;
out_buf.s16 = (int16_t *)proc_buf_out + frames_wr * in->config.channels;
/* FIXME: this works because of current pre processing library implementation that
* does the actual process only when the last enabled effect process is called.
* The generic solution is to have an output buffer for each effect and pass it as
* input to the next.
*/
for (i = 0; i < in->num_preprocessors; i++) {
(*in->preprocessors[i].effect_itfe)->process(in->preprocessors[i].effect_itfe,
&in_buf,
&out_buf);
}
/* process() has updated the number of frames consumed and produced in
* in_buf.frameCount and out_buf.frameCount respectively
* move remaining frames to the beginning of in->proc_buf_in */
in->proc_buf_frames -= in_buf.frameCount;
if (in->proc_buf_frames) {
memcpy(in->proc_buf_in,
in->proc_buf_in + in_buf.frameCount * in->config.channels,
in->proc_buf_frames * in->config.channels * sizeof(int16_t));
}
/* if not enough frames were passed to process(), read more and retry. */
if (out_buf.frameCount == 0) {
ALOGW("No frames produced by preproc");
continue;
}
if ((frames_wr + (ssize_t)out_buf.frameCount) <= frames) {
frames_wr += out_buf.frameCount;
} else {
/* The effect does not comply to the API. In theory, we should never end up here! */
ALOGE("preprocessing produced too many frames: %d + %zd > %d !",
(unsigned int)frames_wr, out_buf.frameCount, (unsigned int)frames);
frames_wr = frames;
}
}
}
else
#endif //PREPROCESSING_ENABLED
{
/* No processing effects attached */
if (has_additional_channels) {
/* With additional channels, we cannot use original buffer */
if (in->proc_buf_size < (size_t)frames) {
size_t size_in_bytes = pcm_frames_to_bytes(pcm_device->pcm, frames);
in->proc_buf_size = (size_t)frames;
in->proc_buf_out = (int16_t *)realloc(in->proc_buf_out, size_in_bytes);
ALOG_ASSERT((in->proc_buf_out != NULL),
"process_frames() failed to reallocate proc_buf_out");
proc_buf_out = in->proc_buf_out;
}
}
frames_wr = read_frames(in, proc_buf_out, frames);
}
/* Remove all additional channels that have been added on top of main_channels:
* - aux_channels
* - extra channels from HW due to HW limitations
* Assumption is made that the channels are interleaved and that the main
* channels are first. */
if (has_additional_channels)
{
int16_t* src_buffer = (int16_t *)proc_buf_out;
int16_t* dst_buffer = (int16_t *)buffer;
if (dst_channels == 1) {
for (i = frames_wr; i > 0; i--)
{
*dst_buffer++ = *src_buffer;
src_buffer += src_channels;
}
} else {
for (i = frames_wr; i > 0; i--)
{
memcpy(dst_buffer, src_buffer, dst_channels*sizeof(int16_t));
dst_buffer += dst_channels;
src_buffer += src_channels;
}
}
}
return frames_wr;
}
static int get_next_buffer(struct resampler_buffer_provider *buffer_provider,
struct resampler_buffer* buffer)
{
struct stream_in *in;
struct pcm_device *pcm_device;
if (buffer_provider == NULL || buffer == NULL)
return -EINVAL;
in = (struct stream_in *)((char *)buffer_provider -
offsetof(struct stream_in, buf_provider));
if (list_empty(&in->pcm_dev_list)) {
buffer->raw = NULL;
buffer->frame_count = 0;
in->read_status = -ENODEV;
return -ENODEV;
}
pcm_device = node_to_item(list_head(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
if (in->read_buf_frames == 0) {
size_t size_in_bytes = pcm_frames_to_bytes(pcm_device->pcm, in->config.period_size);
if (in->read_buf_size < in->config.period_size) {
in->read_buf_size = in->config.period_size;
in->read_buf = (int16_t *) realloc(in->read_buf, size_in_bytes);
ALOG_ASSERT((in->read_buf != NULL),
"get_next_buffer() failed to reallocate read_buf");
}
in->read_status = pcm_read(pcm_device->pcm, (void*)in->read_buf, size_in_bytes);
if (in->read_status != 0) {
ALOGE("get_next_buffer() pcm_read error %d", in->read_status);
buffer->raw = NULL;
buffer->frame_count = 0;
return in->read_status;
}
in->read_buf_frames = in->config.period_size;
#ifdef PREPROCESSING_ENABLED
#ifdef HW_AEC_LOOPBACK
if (in->hw_echo_reference) {
struct pcm_device *temp_device = NULL;
struct pcm_device *ref_device = NULL;
struct listnode *node = NULL;
struct echo_reference_buffer b;
size_t size_hw_ref_bytes;
size_t size_hw_ref_frames;
int read_status = 0;
ref_device = node_to_item(list_tail(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
list_for_each(node, &in->pcm_dev_list) {
temp_device = node_to_item(node, struct pcm_device, stream_list_node);
if (temp_device->pcm_profile->id == 1) {
ref_device = temp_device;
break;
}
}
if (ref_device) {
size_hw_ref_bytes = pcm_frames_to_bytes(ref_device->pcm, ref_device->pcm_profile->config.period_size);
size_hw_ref_frames = ref_device->pcm_profile->config.period_size;
if (in->hw_ref_buf_size < size_hw_ref_frames) {
in->hw_ref_buf_size = size_hw_ref_frames;
in->hw_ref_buf = (int16_t *) realloc(in->hw_ref_buf, size_hw_ref_bytes);
ALOG_ASSERT((in->hw_ref_buf != NULL),
"get_next_buffer() failed to reallocate hw_ref_buf");
ALOGV("get_next_buffer(): hw_ref_buf %p extended to %zd bytes",
in->hw_ref_buf, size_hw_ref_bytes);
}
read_status = pcm_read(ref_device->pcm, (void*)in->hw_ref_buf, size_hw_ref_bytes);
if (read_status != 0) {
ALOGE("process_frames() pcm_read error for HW reference %d", read_status);
b.raw = NULL;
b.frame_count = 0;
}
else {
get_capture_reference_delay(in, size_hw_ref_frames, &b);
b.raw = (void *)in->hw_ref_buf;
b.frame_count = size_hw_ref_frames;
if (b.delay_ns != 0)
b.delay_ns = -b.delay_ns; // as this is capture delay, it needs to be subtracted from the microphone delay
in->echo_reference->write(in->echo_reference, &b);
}
}
}
#endif // HW_AEC_LOOPBACK
#endif // PREPROCESSING_ENABLED
}
buffer->frame_count = (buffer->frame_count > in->read_buf_frames) ?
in->read_buf_frames : buffer->frame_count;
buffer->i16 = in->read_buf + (in->config.period_size - in->read_buf_frames) *
in->config.channels;
return in->read_status;
}
static void release_buffer(struct resampler_buffer_provider *buffer_provider,
struct resampler_buffer* buffer)
{
struct stream_in *in;
if (buffer_provider == NULL || buffer == NULL)
return;
in = (struct stream_in *)((char *)buffer_provider -
offsetof(struct stream_in, buf_provider));
in->read_buf_frames -= buffer->frame_count;
}
/* read_frames() reads frames from kernel driver, down samples to capture rate
* if necessary and output the number of frames requested to the buffer specified */
static ssize_t read_frames(struct stream_in *in, void *buffer, ssize_t frames)
{
ssize_t frames_wr = 0;
struct pcm_device *pcm_device;
if (list_empty(&in->pcm_dev_list)) {
ALOGE("%s: pcm device list empty", __func__);
return -EINVAL;
}
pcm_device = node_to_item(list_head(&in->pcm_dev_list),
struct pcm_device, stream_list_node);
while (frames_wr < frames) {
size_t frames_rd = frames - frames_wr;
ALOGVV("%s: frames_rd: %zd, frames_wr: %zd, in->config.channels: %d",
__func__,frames_rd,frames_wr,in->config.channels);
if (in->resampler != NULL) {
in->resampler->resample_from_provider(in->resampler,
(int16_t *)((char *)buffer +
pcm_frames_to_bytes(pcm_device->pcm, frames_wr)),
&frames_rd);
} else {
struct resampler_buffer buf = {
{ raw : NULL, },
frame_count : frames_rd,
};
get_next_buffer(&in->buf_provider, &buf);
if (buf.raw != NULL) {
memcpy((char *)buffer +
pcm_frames_to_bytes(pcm_device->pcm, frames_wr),
buf.raw,
pcm_frames_to_bytes(pcm_device->pcm, buf.frame_count));
frames_rd = buf.frame_count;
}
release_buffer(&in->buf_provider, &buf);
}
/* in->read_status is updated by getNextBuffer() also called by
* in->resampler->resample_from_provider() */
if (in->read_status != 0)
return in->read_status;
frames_wr += frames_rd;
}
return frames_wr;
}
static int in_release_pcm_devices(struct stream_in *in)
{
struct pcm_device *pcm_device;
struct listnode *node;
struct listnode *next;
list_for_each_safe(node, next, &in->pcm_dev_list) {
pcm_device = node_to_item(node, struct pcm_device, stream_list_node);
list_remove(node);
free(pcm_device);
}
return 0;
}
static int stop_input_stream(struct stream_in *in)
{
struct audio_usecase *uc_info;
struct audio_device *adev = in->dev;
adev->active_input = NULL;
ALOGV("%s: enter: usecase(%d: %s)", __func__,
in->usecase, use_case_table[in->usecase]);
uc_info = get_usecase_from_id(adev, in->usecase);
if (uc_info == NULL) {
ALOGE("%s: Could not find the usecase (%d) in the list",
__func__, in->usecase);
return -EINVAL;
}
/* Disable the tx device */
disable_snd_device(adev, uc_info, uc_info->in_snd_device, true);
list_remove(&uc_info->adev_list_node);
free(uc_info);
if (list_empty(&in->pcm_dev_list)) {
ALOGE("%s: pcm device list empty", __func__);
return -EINVAL;
}
in_release_pcm_devices(in);
list_init(&in->pcm_dev_list);
#ifdef HW_AEC_LOOPBACK
if (in->hw_echo_reference)
{
in->hw_echo_reference = false;
}
#endif
ALOGV("%s: exit", __func__);
return 0;
}
static int start_input_stream(struct stream_in *in)
{
/* Enable output device and stream routing controls */
int ret = 0;
bool recreate_resampler = false;
struct audio_usecase *uc_info;
struct audio_device *adev = in->dev;
struct pcm_device_profile *pcm_profile;
struct pcm_device *pcm_device;
ALOGV("%s: enter: usecase(%d)", __func__, in->usecase);
adev->active_input = in;
pcm_profile = get_pcm_device(in->usecase_type, in->devices);
if (pcm_profile == NULL) {
ALOGE("%s: Could not find PCM device id for the usecase(%d)",
__func__, in->usecase);
ret = -EINVAL;
goto error_config;
}
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
uc_info->id = in->usecase;
uc_info->type = PCM_CAPTURE;
uc_info->stream = (struct audio_stream *)in;
uc_info->devices = in->devices;
uc_info->in_snd_device = SND_DEVICE_NONE;
uc_info->out_snd_device = SND_DEVICE_NONE;
pcm_device = (struct pcm_device *)calloc(1, sizeof(struct pcm_device));
pcm_device->pcm_profile = pcm_profile;
list_init(&in->pcm_dev_list);
list_add_tail(&in->pcm_dev_list, &pcm_device->stream_list_node);
list_init(&uc_info->mixer_list);
list_add_tail(&uc_info->mixer_list,
&adev_get_mixer_for_card(adev,
pcm_device->pcm_profile->card)->uc_list_node[uc_info->id]);
list_add_tail(&adev->usecase_list, &uc_info->adev_list_node);
select_devices(adev, in->usecase);
/* Config should be updated as profile can be changed between different calls
* to this function:
* - Trigger resampler creation
* - Config needs to be updated */
if (in->config.rate != pcm_profile->config.rate) {
recreate_resampler = true;
}
in->config = pcm_profile->config;
#ifdef PREPROCESSING_ENABLED
if (in->aux_channels_changed) {
in->config.channels = audio_channel_count_from_in_mask(in->main_channels | in->aux_channels);
recreate_resampler = true;
}
#endif
if (in->requested_rate != in->config.rate) {
recreate_resampler = true;
}
if (recreate_resampler) {
if (in->resampler) {
release_resampler(in->resampler);
in->resampler = NULL;
}
in->buf_provider.get_next_buffer = get_next_buffer;
in->buf_provider.release_buffer = release_buffer;
ret = create_resampler(in->config.rate,
in->requested_rate,
in->config.channels,
RESAMPLER_QUALITY_DEFAULT,
&in->buf_provider,
&in->resampler);
}
#ifdef PREPROCESSING_ENABLED
if (in->enable_aec && in->echo_reference == NULL) {
in->echo_reference = get_echo_reference(adev,
AUDIO_FORMAT_PCM_16_BIT,
audio_channel_count_from_in_mask(in->main_channels),
in->requested_rate
);
}
#ifdef HW_AEC_LOOPBACK
if (in->enable_aec) {
ret = get_hw_echo_reference(in);
if (ret!=0)
goto error_open;
/* force ref buffer reallocation */
in->hw_ref_buf_size = 0;
}
#endif
#endif
/* Open the PCM device.
* The HW is limited to support only the default pcm_profile settings.
* As such a change in aux_channels will not have an effect.
*/
ALOGV("%s: Opening PCM device card_id(%d) device_id(%d), channels %d, smp rate %d format %d, \
period_size %d", __func__, pcm_device->pcm_profile->card, pcm_device->pcm_profile->id,
pcm_device->pcm_profile->config.channels,pcm_device->pcm_profile->config.rate,
pcm_device->pcm_profile->config.format, pcm_device->pcm_profile->config.period_size);
if (pcm_profile->type == PCM_HOTWORD_STREAMING) {
if (!adev->sound_trigger_open_for_streaming) {
ALOGE("%s: No handle to sound trigger HAL", __func__);
ret = -EIO;
goto error_open;
}
pcm_device->pcm = NULL;
pcm_device->sound_trigger_handle = adev->sound_trigger_open_for_streaming();
if (pcm_device->sound_trigger_handle <= 0) {
ALOGE("%s: Failed to open DSP for streaming", __func__);
ret = -EIO;
goto error_open;
}
ALOGV("Opened DSP successfully");
} else {
pcm_device->sound_trigger_handle = 0;
pcm_device->pcm = pcm_open(pcm_device->pcm_profile->card, pcm_device->pcm_profile->id,
PCM_IN | PCM_MONOTONIC, &pcm_device->pcm_profile->config);
if (pcm_device->pcm && !pcm_is_ready(pcm_device->pcm)) {
ALOGE("%s: %s", __func__, pcm_get_error(pcm_device->pcm));
pcm_close(pcm_device->pcm);
pcm_device->pcm = NULL;
ret = -EIO;
goto error_open;
}
}
/* force read and proc buffer reallocation in case of frame size or
* channel count change */
in->proc_buf_frames = 0;
in->proc_buf_size = 0;
in->read_buf_size = 0;
in->read_buf_frames = 0;
/* if no supported sample rate is available, use the resampler */
if (in->resampler) {
in->resampler->reset(in->resampler);
}
ALOGV("%s: exit", __func__);
return ret;
error_open:
if (in->resampler) {
release_resampler(in->resampler);
in->resampler = NULL;
}
stop_input_stream(in);
error_config:
ALOGV("%s: exit: status(%d)", __func__, ret);
adev->active_input = NULL;
return ret;
}
void lock_input_stream(struct stream_in *in)
{
pthread_mutex_lock(&in->pre_lock);
pthread_mutex_lock(&in->lock);
pthread_mutex_unlock(&in->pre_lock);
}
void lock_output_stream(struct stream_out *out)
{
pthread_mutex_lock(&out->pre_lock);
pthread_mutex_lock(&out->lock);
pthread_mutex_unlock(&out->pre_lock);
}
/* must be called with out->lock locked */
static int send_offload_cmd_l(struct stream_out* out, int command)
{
struct offload_cmd *cmd = (struct offload_cmd *)calloc(1, sizeof(struct offload_cmd));
ALOGVV("%s %d", __func__, command);
cmd->cmd = command;
list_add_tail(&out->offload_cmd_list, &cmd->node);
pthread_cond_signal(&out->offload_cond);
return 0;
}
/* must be called iwth out->lock locked */
static void stop_compressed_output_l(struct stream_out *out)
{
out->send_new_metadata = 1;
if (out->compr != NULL) {
compress_stop(out->compr);
while (out->offload_thread_blocked) {
pthread_cond_wait(&out->cond, &out->lock);
}
}
}
static void *offload_thread_loop(void *context)
{
struct stream_out *out = (struct stream_out *) context;
struct listnode *item;
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_AUDIO);
set_sched_policy(0, SP_FOREGROUND);
prctl(PR_SET_NAME, (unsigned long)"Offload Callback", 0, 0, 0);
ALOGV("%s", __func__);
lock_output_stream(out);
for (;;) {
struct offload_cmd *cmd = NULL;
stream_callback_event_t event;
bool send_callback = false;
ALOGVV("%s offload_cmd_list %d out->offload_state %d",
__func__, list_empty(&out->offload_cmd_list),
out->offload_state);
if (list_empty(&out->offload_cmd_list)) {
ALOGV("%s SLEEPING", __func__);
pthread_cond_wait(&out->offload_cond, &out->lock);
ALOGV("%s RUNNING", __func__);
continue;
}
item = list_head(&out->offload_cmd_list);
cmd = node_to_item(item, struct offload_cmd, node);
list_remove(item);
ALOGVV("%s STATE %d CMD %d out->compr %p",
__func__, out->offload_state, cmd->cmd, out->compr);
if (cmd->cmd == OFFLOAD_CMD_EXIT) {
free(cmd);
break;
}
if (out->compr == NULL) {
ALOGE("%s: Compress handle is NULL", __func__);
pthread_cond_signal(&out->cond);
continue;
}
out->offload_thread_blocked = true;
pthread_mutex_unlock(&out->lock);
send_callback = false;
switch(cmd->cmd) {
case OFFLOAD_CMD_WAIT_FOR_BUFFER:
compress_wait(out->compr, -1);
send_callback = true;
event = STREAM_CBK_EVENT_WRITE_READY;
break;
case OFFLOAD_CMD_PARTIAL_DRAIN:
compress_next_track(out->compr);
compress_partial_drain(out->compr);
send_callback = true;
event = STREAM_CBK_EVENT_DRAIN_READY;
break;
case OFFLOAD_CMD_DRAIN:
compress_drain(out->compr);
send_callback = true;
event = STREAM_CBK_EVENT_DRAIN_READY;
break;
default:
ALOGE("%s unknown command received: %d", __func__, cmd->cmd);
break;
}
lock_output_stream(out);
out->offload_thread_blocked = false;
pthread_cond_signal(&out->cond);
if (send_callback) {
out->offload_callback(event, NULL, out->offload_cookie);
}
free(cmd);
}
pthread_cond_signal(&out->cond);
while (!list_empty(&out->offload_cmd_list)) {
item = list_head(&out->offload_cmd_list);
list_remove(item);
free(node_to_item(item, struct offload_cmd, node));
}
pthread_mutex_unlock(&out->lock);
return NULL;
}
static int create_offload_callback_thread(struct stream_out *out)
{
pthread_cond_init(&out->offload_cond, (const pthread_condattr_t *) NULL);
list_init(&out->offload_cmd_list);
pthread_create(&out->offload_thread, (const pthread_attr_t *) NULL,
offload_thread_loop, out);
return 0;
}
static int destroy_offload_callback_thread(struct stream_out *out)
{
lock_output_stream(out);
send_offload_cmd_l(out, OFFLOAD_CMD_EXIT);
pthread_mutex_unlock(&out->lock);
pthread_join(out->offload_thread, (void **) NULL);
pthread_cond_destroy(&out->offload_cond);
return 0;
}
static int uc_release_pcm_devices(struct audio_usecase *usecase)
{
struct stream_out *out = (struct stream_out *)usecase->stream;
struct pcm_device *pcm_device;
struct listnode *node;
struct listnode *next;
list_for_each_safe(node, next, &out->pcm_dev_list) {
pcm_device = node_to_item(node, struct pcm_device, stream_list_node);
list_remove(node);
free(pcm_device);
}
list_init(&usecase->mixer_list);
return 0;
}
static int uc_select_pcm_devices(struct audio_usecase *usecase)
{
struct stream_out *out = (struct stream_out *)usecase->stream;
struct pcm_device *pcm_device;
struct pcm_device_profile *pcm_profile;
struct mixer_card *mixer_card;
audio_devices_t devices = usecase->devices;
list_init(&usecase->mixer_list);
list_init(&out->pcm_dev_list);
while ((pcm_profile = get_pcm_device(usecase->type, devices)) != NULL) {
pcm_device = calloc(1, sizeof(struct pcm_device));
pcm_device->pcm_profile = pcm_profile;
list_add_tail(&out->pcm_dev_list, &pcm_device->stream_list_node);
mixer_card = uc_get_mixer_for_card(usecase, pcm_profile->card);
if (mixer_card == NULL) {
mixer_card = adev_get_mixer_for_card(out->dev, pcm_profile->card);
list_add_tail(&usecase->mixer_list, &mixer_card->uc_list_node[usecase->id]);
}
devices &= ~pcm_profile->devices;
}
return 0;
}
static int out_close_pcm_devices(struct stream_out *out)
{
struct pcm_device *pcm_device;
struct listnode *node;
struct audio_device *adev = out->dev;
list_for_each(node, &out->pcm_dev_list) {
pcm_device = node_to_item(node, struct pcm_device, stream_list_node);
if (pcm_device->sound_trigger_handle > 0) {
adev->sound_trigger_close_for_streaming(pcm_device->sound_trigger_handle);
pcm_device->sound_trigger_handle = 0;
}
if (pcm_device->pcm) {
pcm_close(pcm_device->pcm);
pcm_device->pcm = NULL;
}
if (pcm_device->resampler) {
release_resampler(pcm_device->resampler);
pcm_device->resampler = NULL;
}
if (pcm_device->res_buffer) {
free(pcm_device->res_buffer);
pcm_device->res_buffer = NULL;
}
}
return 0;
}
static int out_open_pcm_devices(struct stream_out *out)
{
struct pcm_device *pcm_device;
struct listnode *node;
int ret = 0;
list_for_each(node, &out->pcm_dev_list) {
pcm_device = node_to_item(node, struct pcm_device, stream_list_node);
ALOGV("%s: Opening PCM device card_id(%d) device_id(%d)",
__func__, pcm_device->pcm_profile->card, pcm_device->pcm_profile->id);
pcm_device->pcm = pcm_open(pcm_device->pcm_profile->card, pcm_device->pcm_profile->id,
PCM_OUT | PCM_MONOTONIC, &pcm_device->pcm_profile->config);
if (pcm_device->pcm && !pcm_is_ready(pcm_device->pcm)) {
ALOGE("%s: %s", __func__, pcm_get_error(pcm_device->pcm));
pcm_device->pcm = NULL;
ret = -EIO;
goto error_open;
}
/*
* If the stream rate differs from the PCM rate, we need to
* create a resampler.
*/
if (out->sample_rate != pcm_device->pcm_profile->config.rate) {
ALOGV("%s: create_resampler(), pcm_device_card(%d), pcm_device_id(%d), \
out_rate(%d), device_rate(%d)",__func__,
pcm_device->pcm_profile->card, pcm_device->pcm_profile->id,
out->sample_rate, pcm_device->pcm_profile->config.rate);
ret = create_resampler(out->sample_rate,
pcm_device->pcm_profile->config.rate,
audio_channel_count_from_out_mask(out->channel_mask),
RESAMPLER_QUALITY_DEFAULT,
NULL,
&pcm_device->resampler);
pcm_device->res_byte_count = 0;
pcm_device->res_buffer = NULL;
}
}
return ret;
error_open:
out_close_pcm_devices(out);
return ret;
}
static int disable_output_path_l(struct stream_out *out)
{
struct audio_device *adev = out->dev;
struct audio_usecase *uc_info;
uc_info = get_usecase_from_id(adev, out->usecase);
if (uc_info == NULL) {
ALOGE("%s: Could not find the usecase (%d) in the list",
__func__, out->usecase);
return -EINVAL;
}
disable_snd_device(adev, uc_info, uc_info->out_snd_device, true);
uc_release_pcm_devices(uc_info);
list_remove(&uc_info->adev_list_node);
free(uc_info);
return 0;
}
static void enable_output_path_l(struct stream_out *out)
{
struct audio_device *adev = out->dev;
struct audio_usecase *uc_info;
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
uc_info->id = out->usecase;
uc_info->type = PCM_PLAYBACK;
uc_info->stream = (struct audio_stream *)out;
uc_info->devices = out->devices;
uc_info->in_snd_device = SND_DEVICE_NONE;
uc_info->out_snd_device = SND_DEVICE_NONE;
uc_select_pcm_devices(uc_info);
list_add_tail(&adev->usecase_list, &uc_info->adev_list_node);
select_devices(adev, out->usecase);
}
static int stop_output_stream(struct stream_out *out)
{
int ret = 0;
struct audio_device *adev = out->dev;
bool do_disable = true;
ALOGV("%s: enter: usecase(%d: %s)", __func__,
out->usecase, use_case_table[out->usecase]);
if (out->usecase == USECASE_AUDIO_PLAYBACK_OFFLOAD &&
adev->offload_fx_stop_output != NULL) {
adev->offload_fx_stop_output(out->handle);
if (out->offload_state == OFFLOAD_STATE_PAUSED ||
out->offload_state == OFFLOAD_STATE_PAUSED_FLUSHED)
do_disable = false;
out->offload_state = OFFLOAD_STATE_IDLE;
}
if (do_disable)
ret = disable_output_path_l(out);
ALOGV("%s: exit: status(%d)", __func__, ret);
return ret;
}
static int start_output_stream(struct stream_out *out)
{
int ret = 0;
struct audio_device *adev = out->dev;
ALOGV("%s: enter: usecase(%d: %s) devices(%#x) channels(%d)",
__func__, out->usecase, use_case_table[out->usecase], out->devices, out->config.channels);
enable_output_path_l(out);
if (out->usecase != USECASE_AUDIO_PLAYBACK_OFFLOAD) {
out->compr = NULL;
ret = out_open_pcm_devices(out);
if (ret != 0)
goto error_open;
#ifdef PREPROCESSING_ENABLED
out->echo_reference = NULL;
out->echo_reference_generation = adev->echo_reference_generation;
if (adev->echo_reference != NULL)
out->echo_reference = adev->echo_reference;
#endif
} else {
out->compr = compress_open(COMPRESS_CARD, COMPRESS_DEVICE,
COMPRESS_IN, &out->compr_config);
if (out->compr && !is_compress_ready(out->compr)) {
ALOGE("%s: %s", __func__, compress_get_error(out->compr));
compress_close(out->compr);
out->compr = NULL;
ret = -EIO;
goto error_open;
}
if (out->offload_callback)
compress_nonblock(out->compr, out->non_blocking);
if (adev->offload_fx_start_output != NULL)
adev->offload_fx_start_output(out->handle);
}
ALOGV("%s: exit", __func__);
return 0;
error_open:
stop_output_stream(out);
error_config:
return ret;
}
static int stop_voice_call(struct audio_device *adev)
{
struct audio_usecase *uc_info;
ALOGV("%s: enter", __func__);
adev->in_call = false;
/* TODO: implement voice call stop */
uc_info = get_usecase_from_id(adev, USECASE_VOICE_CALL);
if (uc_info == NULL) {
ALOGE("%s: Could not find the usecase (%d) in the list",
__func__, USECASE_VOICE_CALL);
return -EINVAL;
}
disable_snd_device(adev, uc_info, uc_info->out_snd_device, false);
disable_snd_device(adev, uc_info, uc_info->in_snd_device, true);
uc_release_pcm_devices(uc_info);
list_remove(&uc_info->adev_list_node);
free(uc_info);
ALOGV("%s: exit", __func__);
return 0;
}
/* always called with adev lock held */
static int start_voice_call(struct audio_device *adev)
{
struct audio_usecase *uc_info;
ALOGV("%s: enter", __func__);
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
uc_info->id = USECASE_VOICE_CALL;
uc_info->type = VOICE_CALL;
uc_info->stream = (struct audio_stream *)adev->primary_output;
uc_info->devices = adev->primary_output->devices;
uc_info->in_snd_device = SND_DEVICE_NONE;
uc_info->out_snd_device = SND_DEVICE_NONE;
uc_select_pcm_devices(uc_info);
list_add_tail(&adev->usecase_list, &uc_info->adev_list_node);
select_devices(adev, USECASE_VOICE_CALL);
/* TODO: implement voice call start */
/* set cached volume */
set_voice_volume_l(adev, adev->voice_volume);
adev->in_call = true;
ALOGV("%s: exit", __func__);
return 0;
}
static int check_input_parameters(uint32_t sample_rate,
audio_format_t format,
int channel_count)
{
if (format != AUDIO_FORMAT_PCM_16_BIT) return -EINVAL;
if ((channel_count < 1) || (channel_count > 2)) return -EINVAL;
switch (sample_rate) {
case 8000:
case 11025:
case 12000:
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
break;
default:
return -EINVAL;
}
return 0;
}
static size_t get_input_buffer_size(uint32_t sample_rate,
audio_format_t format,
int channel_count,
usecase_type_t usecase_type,
audio_devices_t devices)
{
size_t size = 0;
struct pcm_device_profile *pcm_profile;
if (check_input_parameters(sample_rate, format, channel_count) != 0)
return 0;
pcm_profile = get_pcm_device(usecase_type, devices);
if (pcm_profile == NULL)
return 0;
/*
* take resampling into account and return the closest majoring
* multiple of 16 frames, as audioflinger expects audio buffers to
* be a multiple of 16 frames
*/
size = (pcm_profile->config.period_size * sample_rate) / pcm_profile->config.rate;
size = ((size + 15) / 16) * 16;
return (size * channel_count * audio_bytes_per_sample(format));
}
static