audio: sndmonitor
Monitor sound card status and report online/offline
events back to the streams registered with it and
to audio_dev.
Bug: 30075678
Change-Id: Iea74d5c83f7fe92ba4a24f4f4650ce255bdb48e8
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 527e9c5..1e185e5 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -348,6 +348,27 @@
adev->adm_abandon_focus(adev->adm_data, in->capture_handle);
}
+static int parse_snd_card_status(struct str_parms * parms, int * card,
+ card_status_t * status)
+{
+ char value[32]={0};
+ char state[32]={0};
+
+ int ret = str_parms_get_str(parms, "SND_CARD_STATUS", value, sizeof(value));
+
+ if (ret < 0)
+ return -1;
+
+ // sscanf should be okay as value is of max length 32.
+ // same as sizeof state.
+ if (sscanf(value, "%d,%s", card, state) < 2)
+ return -1;
+
+ *status = !strcmp(state, "ONLINE") ? CARD_STATUS_ONLINE :
+ CARD_STATUS_OFFLINE;
+ return 0;
+}
+
__attribute__ ((visibility ("default")))
bool audio_hw_send_gain_dep_calibration(int level) {
bool ret_val = false;
@@ -1006,6 +1027,14 @@
struct audio_device *adev = in->dev;
ALOGV("%s: enter: usecase(%d)", __func__, in->usecase);
+
+ if (in->card_status == CARD_STATUS_OFFLINE ||
+ adev->card_status == CARD_STATUS_OFFLINE) {
+ ALOGW("in->card_status or adev->card_status offline, try again");
+ ret = -EAGAIN;
+ goto error_config;
+ }
+
in->pcm_device_id = platform_get_pcm_device_id(in->usecase, PCM_CAPTURE);
if (in->pcm_device_id < 0) {
ALOGE("%s: Could not find PCM device id for the usecase(%d)",
@@ -1074,7 +1103,6 @@
ret = pcm_start(in->pcm);
}
audio_extn_perf_lock_release();
-
ALOGV("%s: exit", __func__);
return ret;
@@ -1373,6 +1401,14 @@
ALOGV("%s: enter: usecase(%d: %s) devices(%#x)",
__func__, out->usecase, use_case_table[out->usecase], out->devices);
+
+ if (out->card_status == CARD_STATUS_OFFLINE ||
+ adev->card_status == CARD_STATUS_OFFLINE) {
+ ALOGW("out->card_status or adev->card_status offline, try again");
+ ret = -EAGAIN;
+ goto error_config;
+ }
+
out->pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
if (out->pcm_device_id < 0) {
ALOGE("%s: Invalid PCM device id(%d) for the usecase(%d)",
@@ -1848,6 +1884,46 @@
return -ENOSYS;
}
+// note: this call is safe only if the stream_cb is
+// removed first in close_output_stream (as is done now).
+static void out_snd_mon_cb(void * stream, struct str_parms * parms)
+{
+ if (!stream || !parms)
+ return;
+
+ struct stream_out *out = (struct stream_out *)stream;
+ struct audio_device *adev = out->dev;
+
+ card_status_t status;
+ int card;
+ if (parse_snd_card_status(parms, &card, &status) < 0)
+ return;
+
+ pthread_mutex_lock(&adev->lock);
+ bool valid_cb = (card == adev->snd_card);
+ pthread_mutex_unlock(&adev->lock);
+
+ if (!valid_cb)
+ return;
+
+ ALOGV("out_snd_mon_cb for card %d usecase %s", card,
+ use_case_table[out->usecase]);
+
+ lock_output_stream(out);
+ if (out->card_status != status)
+ out->card_status = status;
+ pthread_mutex_unlock(&out->lock);
+
+ // a better solution would be to report error back to AF and let
+ // it put the stream to standby
+ if (status == CARD_STATUS_OFFLINE) {
+ ALOGW("new state == offline, move stream to standby");
+ out_standby(&out->stream.common);
+ }
+
+ return;
+}
+
#ifdef NO_AUDIO_OUT
static ssize_t out_write_for_no_output(struct audio_stream_out *stream,
const void *buffer, size_t bytes)
@@ -2256,6 +2332,43 @@
return 0;
}
+static void in_snd_mon_cb(void * stream, struct str_parms * parms)
+{
+ if (!stream || !parms)
+ return;
+
+ struct stream_in *in = (struct stream_in *)stream;
+ struct audio_device *adev = in->dev;
+
+ card_status_t status;
+ int card;
+ if (parse_snd_card_status(parms, &card, &status) < 0)
+ return;
+
+ pthread_mutex_lock(&adev->lock);
+ bool valid_cb = (card == adev->snd_card);
+ pthread_mutex_unlock(&adev->lock);
+
+ if (!valid_cb)
+ return;
+
+ lock_input_stream(in);
+ if (in->card_status != status)
+ in->card_status = status;
+ pthread_mutex_unlock(&in->lock);
+
+ ALOGW("in_snd_mon_cb for card %d usecase %s, status %s", card,
+ use_case_table[in->usecase],
+ status == CARD_STATUS_OFFLINE ? "offline" : "online");
+
+ // a better solution would be to report error back to AF and let
+ // it put the stream to standby
+ if (status == CARD_STATUS_OFFLINE)
+ in_standby(&in->stream.common);
+
+ return;
+}
+
static ssize_t in_read(struct audio_stream_in *stream, void *buffer,
size_t bytes)
{
@@ -2651,6 +2764,19 @@
config->channel_mask = out->stream.common.get_channels(&out->stream.common);
config->sample_rate = out->stream.common.get_sample_rate(&out->stream.common);
+
+ /*
+ By locking output stream before registering, we allow the callback
+ to update stream's state only after stream's initial state is set to
+ adev state.
+ */
+ lock_output_stream(out);
+ audio_extn_snd_mon_register_listener(out, out_snd_mon_cb);
+ pthread_mutex_lock(&adev->lock);
+ out->card_status = adev->card_status;
+ pthread_mutex_unlock(&adev->lock);
+ pthread_mutex_unlock(&out->lock);
+
*stream_out = &out->stream;
ALOGV("%s: exit", __func__);
return 0;
@@ -2669,6 +2795,10 @@
struct audio_device *adev = out->dev;
ALOGV("%s: enter", __func__);
+
+ // must deregister from sndmonitor first to prevent races
+ // between the callback and close_stream
+ audio_extn_snd_mon_unregister_listener(out);
out_standby(&stream->common);
if (out->usecase == USECASE_AUDIO_PLAYBACK_OFFLOAD) {
destroy_offload_callback_thread(out);
@@ -3010,6 +3140,13 @@
get sound trigger pcm if present */
audio_extn_sound_trigger_check_and_get_session(in);
+ lock_input_stream(in);
+ audio_extn_snd_mon_register_listener(in, in_snd_mon_cb);
+ pthread_mutex_lock(&adev->lock);
+ in->card_status = adev->card_status;
+ pthread_mutex_unlock(&adev->lock);
+ pthread_mutex_unlock(&in->lock);
+
*stream_in = &in->stream;
ALOGV("%s: exit", __func__);
return 0;
@@ -3025,6 +3162,9 @@
{
ALOGV("%s", __func__);
+ // must deregister from sndmonitor first to prevent races
+ // between the callback and close_stream
+ audio_extn_snd_mon_unregister_listener(stream);
in_standby(&stream->common);
free(stream);
@@ -3177,6 +3317,7 @@
if (!adev)
return 0;
+ audio_extn_snd_mon_unregister_listener(adev);
pthread_mutex_lock(&adev_init_lock);
if ((--audio_device_ref_count) == 0) {
@@ -3219,6 +3360,29 @@
}
}
+static void adev_snd_mon_cb(void * stream __unused, struct str_parms * parms)
+{
+ int card;
+ card_status_t status;
+
+ if (!parms)
+ return;
+
+ if (parse_snd_card_status(parms, &card, &status) < 0)
+ return;
+
+ pthread_mutex_lock(&adev->lock);
+ bool valid_cb = (card == adev->snd_card);
+ if (valid_cb) {
+ if (adev->card_status != status) {
+ adev->card_status = status;
+ platform_snd_card_update(adev->platform, status);
+ }
+ }
+ pthread_mutex_unlock(&adev->lock);
+ return;
+}
+
static int adev_open(const hw_module_t *module, const char *name,
hw_device_t **device)
{
@@ -3285,7 +3449,6 @@
pthread_mutex_unlock(&adev_init_lock);
return -EINVAL;
}
-
adev->extspk = audio_extn_extspk_init(adev);
audio_extn_sound_trigger_init(adev);
@@ -3390,6 +3553,11 @@
adev->adm_data = adev->adm_init();
audio_extn_perf_lock_init();
+ audio_extn_snd_mon_init();
+ pthread_mutex_lock(&adev->lock);
+ audio_extn_snd_mon_register_listener(NULL, adev_snd_mon_cb);
+ adev->card_status = CARD_STATUS_ONLINE;
+ pthread_mutex_unlock(&adev->lock);
ALOGD("%s: exit", __func__);
return 0;