/* * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * Not a contribution. * * 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 "compress_voip" /*#define LOG_NDEBUG 0*/ #define LOG_NDDEBUG 0 #include <errno.h> #include <pthread.h> #include <stdint.h> #include <sys/time.h> #include <stdlib.h> #include <math.h> #include <cutils/log.h> #include <cutils/str_parms.h> #include <cutils/properties.h> #include "audio_hw.h" #include "platform_api.h" #include "platform.h" #include "voice_extn.h" #define COMPRESS_VOIP_IO_BUF_SIZE_NB 320 #define COMPRESS_VOIP_IO_BUF_SIZE_WB 640 struct pcm_config pcm_config_voip_nb = { .channels = 1, .rate = 8000, /* changed when the stream is opened */ .period_size = COMPRESS_VOIP_IO_BUF_SIZE_NB/2, .period_count = 10, .format = PCM_FORMAT_S16_LE, }; struct pcm_config pcm_config_voip_wb = { .channels = 1, .rate = 16000, /* changed when the stream is opened */ .period_size = COMPRESS_VOIP_IO_BUF_SIZE_WB/2, .period_count = 10, .format = PCM_FORMAT_S16_LE, }; struct voip_data { struct pcm *pcm_rx; struct pcm *pcm_tx; struct stream_out *out_stream; uint32_t out_stream_count; uint32_t in_stream_count; uint32_t sample_rate; }; #define MODE_IS127 0x2 #define MODE_4GV_NB 0x3 #define MODE_4GV_WB 0x4 #define MODE_AMR 0x5 #define MODE_AMR_WB 0xD #define MODE_PCM 0xC #define MODE_4GV_NW 0xE #define AUDIO_PARAMETER_KEY_VOIP_RATE "voip_rate" #define AUDIO_PARAMETER_KEY_VOIP_EVRC_RATE_MIN "evrc_rate_min" #define AUDIO_PARAMETER_KEY_VOIP_EVRC_RATE_MAX "evrc_rate_max" #define AUDIO_PARAMETER_KEY_VOIP_DTX_MODE "dtx_on" #define AUDIO_PARAMETER_VALUE_VOIP_TRUE "true" #define AUDIO_PARAMETER_KEY_VOIP_CHECK "voip_flag" #define AUDIO_PARAMETER_KEY_VOIP_OUT_STREAM_COUNT "voip_out_stream_count" #define AUDIO_PARAMETER_KEY_VOIP_SAMPLE_RATE "voip_sample_rate" static struct voip_data voip_data = { .pcm_rx = NULL, .pcm_tx = NULL, .out_stream = NULL, .out_stream_count = 0, .in_stream_count = 0, .sample_rate = 0 }; static int voip_set_volume(struct audio_device *adev, int volume); static int voip_set_mic_mute(struct audio_device *adev, bool state); static int voip_set_mode(struct audio_device *adev, int format); static int voip_set_rate(struct audio_device *adev, int rate); static int voip_set_evrc_min_max_rate(struct audio_device *adev, int min_rate, int max_rate); static int voip_set_dtx(struct audio_device *adev, bool enable); static int voip_stop_call(struct audio_device *adev); static int voip_start_call(struct audio_device *adev, struct pcm_config *voip_config); static int audio_format_to_voip_mode(int format) { int mode; switch(format) { case AUDIO_FORMAT_PCM_16_BIT: mode = MODE_PCM; break; case AUDIO_FORMAT_AMR_NB: mode = MODE_AMR; break; case AUDIO_FORMAT_AMR_WB: mode = MODE_AMR_WB; break; case AUDIO_FORMAT_EVRC: mode = MODE_IS127; break; case AUDIO_FORMAT_EVRCB: mode = MODE_4GV_NB; break; case AUDIO_FORMAT_EVRCWB: mode = MODE_4GV_WB; break; case AUDIO_FORMAT_EVRCNW: mode = MODE_4GV_NW; break; default: mode = MODE_PCM; } return mode; } static int voip_set_volume(struct audio_device *adev, int volume) { struct mixer_ctl *ctl; const char *mixer_ctl_name = "Voip Rx Gain"; int vol_index = 0; uint32_t set_values[ ] = {0, DEFAULT_VOLUME_RAMP_DURATION_MS}; ALOGV("%s: enter", __func__); /* Voice volume levels are mapped to adsp volume levels as follows. * 100 -> 5, 80 -> 4, 60 -> 3, 40 -> 2, 20 -> 1 0 -> 0 * But this values don't changed in kernel. So, below change is need. */ vol_index = (int)percent_to_index(volume, MIN_VOL_INDEX, MAX_VOL_INDEX); set_values[0] = vol_index; ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } ALOGV("%s: Setting voip volume index: %d", __func__, set_values[0]); mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values)); ALOGV("%s: exit", __func__); return 0; } static int voip_set_mic_mute(struct audio_device *adev, bool state) { struct mixer_ctl *ctl; const char *mixer_ctl_name = "Voip Tx Mute"; uint32_t set_values[ ] = {0, DEFAULT_VOLUME_RAMP_DURATION_MS}; ALOGV("%s: enter, state=%d", __func__, state); if (adev->mode == AUDIO_MODE_IN_COMMUNICATION) { set_values[0] = state; ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values)); } ALOGV("%s: exit", __func__); return 0; } static int voip_set_mode(struct audio_device *adev, int format) { struct mixer_ctl *ctl; const char *mixer_ctl_name = "Voip Mode Config"; uint32_t set_values[ ] = {0}; int mode; ALOGD("%s: enter, format=%d", __func__, format); mode = audio_format_to_voip_mode(format); ALOGD("%s: Derived mode = %d", __func__, mode); set_values[0] = mode; ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values)); ALOGV("%s: exit", __func__); return 0; } static int voip_set_rate(struct audio_device *adev, int rate) { struct mixer_ctl *ctl; const char *mixer_ctl_name = "Voip Rate Config"; uint32_t set_values[ ] = {0}; ALOGD("%s: enter, rate=%d", __func__, rate); set_values[0] = rate; ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values)); ALOGV("%s: exit", __func__); return 0; } static int voip_set_evrc_min_max_rate(struct audio_device *adev, int min_rate, int max_rate) { struct mixer_ctl *ctl; const char *mixer_ctl_name = "Voip Evrc Min Max Rate Config"; uint32_t set_values[ ] = {0, 0}; ALOGD("%s: enter, min_rate=%d, max_rate=%d", __func__, min_rate, max_rate); set_values[0] = min_rate; set_values[1] = max_rate; ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values)); ALOGV("%s: exit", __func__); return 0; } static int voip_set_dtx(struct audio_device *adev, bool enable) { struct mixer_ctl *ctl; const char *mixer_ctl_name = "Voip Dtx Mode"; uint32_t set_values[ ] = {0}; ALOGD("%s: enter, enable=%d", __func__, enable); set_values[0] = enable; ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } mixer_ctl_set_array(ctl, set_values, ARRAY_SIZE(set_values)); ALOGV("%s: exit", __func__); return 0; } static int voip_stop_call(struct audio_device *adev) { int i, ret = 0; struct audio_usecase *uc_info; ALOGD("%s: enter, out_stream_count=%d, in_stream_count=%d", __func__, voip_data.out_stream_count, voip_data.in_stream_count); if (!voip_data.out_stream_count && !voip_data.in_stream_count) { voip_data.sample_rate = 0; uc_info = get_usecase_from_list(adev, USECASE_COMPRESS_VOIP_CALL); if (uc_info == NULL) { ALOGE("%s: Could not find the usecase (%d) in the list", __func__, USECASE_COMPRESS_VOIP_CALL); return -EINVAL; } /* 1. Close the PCM devices */ if (voip_data.pcm_rx) { pcm_close(voip_data.pcm_rx); voip_data.pcm_rx = NULL; } if (voip_data.pcm_tx) { pcm_close(voip_data.pcm_tx); voip_data.pcm_tx = NULL; } /* 2. Get and set stream specific mixer controls */ disable_audio_route(adev, uc_info); /* 3. Disable the rx and tx devices */ disable_snd_device(adev, uc_info->out_snd_device); disable_snd_device(adev, uc_info->in_snd_device); list_remove(&uc_info->list); free(uc_info); } else ALOGV("%s: NO-OP because out_stream_count=%d, in_stream_count=%d", __func__, voip_data.out_stream_count, voip_data.in_stream_count); ALOGV("%s: exit: status(%d)", __func__, ret); return ret; } static int voip_start_call(struct audio_device *adev, struct pcm_config *voip_config) { int i, ret = 0; struct audio_usecase *uc_info; int pcm_dev_rx_id, pcm_dev_tx_id; ALOGD("%s: enter", __func__); uc_info = get_usecase_from_list(adev, USECASE_COMPRESS_VOIP_CALL); if (uc_info == NULL) { ALOGV("%s: voip usecase is added to the list", __func__); uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase)); if (!uc_info) { ALOGE("failed to allocate voip usecase mem"); return -ENOMEM; } uc_info->id = USECASE_COMPRESS_VOIP_CALL; uc_info->type = VOIP_CALL; if (voip_data.out_stream) uc_info->stream.out = voip_data.out_stream; else uc_info->stream.out = adev->primary_output; uc_info->in_snd_device = SND_DEVICE_NONE; uc_info->out_snd_device = SND_DEVICE_NONE; list_add_tail(&adev->usecase_list, &uc_info->list); select_devices(adev, USECASE_COMPRESS_VOIP_CALL); pcm_dev_rx_id = platform_get_pcm_device_id(uc_info->id, PCM_PLAYBACK); pcm_dev_tx_id = platform_get_pcm_device_id(uc_info->id, PCM_CAPTURE); if (pcm_dev_rx_id < 0 || pcm_dev_tx_id < 0) { ALOGE("%s: Invalid PCM devices (rx: %d tx: %d) for the usecase(%d)", __func__, pcm_dev_rx_id, pcm_dev_tx_id, uc_info->id); ret = -EIO; goto error_start_voip; } ALOGD("%s: Opening PCM playback device card_id(%d) device_id(%d)", __func__, adev->snd_card, pcm_dev_rx_id); voip_data.pcm_rx = pcm_open(adev->snd_card, pcm_dev_rx_id, PCM_OUT, voip_config); if (voip_data.pcm_rx && !pcm_is_ready(voip_data.pcm_rx)) { ALOGE("%s: %s", __func__, pcm_get_error(voip_data.pcm_rx)); pcm_close(voip_data.pcm_rx); voip_data.pcm_rx = NULL; ret = -EIO; goto error_start_voip; } ALOGD("%s: Opening PCM capture device card_id(%d) device_id(%d)", __func__, adev->snd_card, pcm_dev_tx_id); voip_data.pcm_tx = pcm_open(adev->snd_card, pcm_dev_tx_id, PCM_IN, voip_config); if (voip_data.pcm_tx && !pcm_is_ready(voip_data.pcm_tx)) { ALOGE("%s: %s", __func__, pcm_get_error(voip_data.pcm_tx)); pcm_close(voip_data.pcm_rx); voip_data.pcm_tx = NULL; if (voip_data.pcm_rx) { pcm_close(voip_data.pcm_rx); voip_data.pcm_rx = NULL; } ret = -EIO; goto error_start_voip; } pcm_start(voip_data.pcm_rx); pcm_start(voip_data.pcm_tx); voice_extn_compress_voip_set_volume(adev, adev->voice.volume); if (ret < 0) { ALOGE("%s: error %d\n", __func__, ret); goto error_start_voip; } } else { ALOGV("%s: voip usecase is already enabled", __func__); if (voip_data.out_stream) uc_info->stream.out = voip_data.out_stream; else uc_info->stream.out = adev->primary_output; select_devices(adev, USECASE_COMPRESS_VOIP_CALL); } return 0; error_start_voip: voip_stop_call(adev); ALOGV("%s: exit: status(%d)", __func__, ret); return ret; } int voice_extn_compress_voip_set_parameters(struct audio_device *adev, struct str_parms *parms) { char *str; char value[32]={0}; int ret = 0, err, rate; int min_rate, max_rate; bool flag; char *kv_pairs = str_parms_to_str(parms); ALOGV_IF(kv_pairs != NULL, "%s: enter: %s", __func__, kv_pairs); err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_VOIP_RATE, value, sizeof(value)); if (err >= 0) { rate = atoi(value); voip_set_rate(adev, rate); voip_set_evrc_min_max_rate(adev, rate, rate); } memset(value, 0, sizeof(value)); err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_VOIP_EVRC_RATE_MIN, value, sizeof(value)); if (err >= 0) { min_rate = atoi(value); str_parms_del(parms, AUDIO_PARAMETER_KEY_VOIP_EVRC_RATE_MIN); memset(value, 0, sizeof(value)); err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_VOIP_EVRC_RATE_MAX, value, sizeof(value)); if (err >= 0) { max_rate = atoi(value); voip_set_evrc_min_max_rate(adev, min_rate, max_rate); } else { ALOGE("%s: AUDIO_PARAMETER_KEY_VOIP_EVRC_RATE_MAX not found", __func__); ret = -EINVAL; goto done; } } memset(value, 0, sizeof(value)); err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_VOIP_DTX_MODE, value, sizeof(value)); if (err >= 0) { flag = false; if (strcmp(value, AUDIO_PARAMETER_VALUE_VOIP_TRUE) == 0) flag = true; voip_set_dtx(adev, flag); } done: ALOGV("%s: exit", __func__); free(kv_pairs); return ret; } void voice_extn_compress_voip_get_parameters(struct str_parms *query, struct str_parms *reply) { int ret; char value[32]={0}; char *str = NULL; ret = str_parms_get_str(query, AUDIO_PARAMETER_KEY_VOIP_OUT_STREAM_COUNT, value, sizeof(value)); if (ret >= 0) { str_parms_add_int(reply, AUDIO_PARAMETER_KEY_VOIP_OUT_STREAM_COUNT, voip_data.out_stream_count); } ret = str_parms_get_str(query, AUDIO_PARAMETER_KEY_VOIP_SAMPLE_RATE, value, sizeof(value)); if (ret >= 0) { str_parms_add_int(reply, AUDIO_PARAMETER_KEY_VOIP_SAMPLE_RATE, voip_data.sample_rate); } } void voice_extn_compress_voip_out_get_parameters(struct stream_out *out, struct str_parms *query, struct str_parms *reply) { int ret, val; char value[32]={0}; ALOGD("%s: enter", __func__); ret = str_parms_get_str(query, AUDIO_PARAMETER_KEY_VOIP_CHECK, value, sizeof(value)); if (ret >= 0) { if (out->usecase == USECASE_COMPRESS_VOIP_CALL) str_parms_add_int(reply, AUDIO_PARAMETER_KEY_VOIP_CHECK, true); else str_parms_add_int(reply, AUDIO_PARAMETER_KEY_VOIP_CHECK, false); } ALOGV("%s: exit", __func__); } void voice_extn_compress_voip_in_get_parameters(struct stream_in *in, struct str_parms *query, struct str_parms *reply) { int ret, val; char value[32]={0}; char *kv_pairs = NULL; ALOGV("%s: enter", __func__); ret = str_parms_get_str(query, AUDIO_PARAMETER_KEY_VOIP_CHECK, value, sizeof(value)); if (ret >= 0) { if (in->usecase == USECASE_COMPRESS_VOIP_CALL) str_parms_add_int(reply, AUDIO_PARAMETER_KEY_VOIP_CHECK, true); else str_parms_add_int(reply, AUDIO_PARAMETER_KEY_VOIP_CHECK, false); } kv_pairs = str_parms_to_str(reply); ALOGD_IF(kv_pairs != NULL, "%s: exit: return - %s", __func__, kv_pairs); free(kv_pairs); } int voice_extn_compress_voip_out_get_buffer_size(struct stream_out *out) { if (out->config.rate == 16000) return COMPRESS_VOIP_IO_BUF_SIZE_WB; else return COMPRESS_VOIP_IO_BUF_SIZE_NB; } int voice_extn_compress_voip_in_get_buffer_size(struct stream_in *in) { if (in->config.rate == 16000) return COMPRESS_VOIP_IO_BUF_SIZE_WB; else return COMPRESS_VOIP_IO_BUF_SIZE_NB; } int voice_extn_compress_voip_start_output_stream(struct stream_out *out) { int ret = 0; struct audio_device *adev = out->dev; struct audio_usecase *uc_info; int snd_card_status = get_snd_card_state(adev); ALOGD("%s: enter", __func__); if (SND_CARD_STATE_OFFLINE == snd_card_status) { ret = -ENETRESET; ALOGE("%s: sound card is not active/SSR returning error %d ", __func__, ret); goto error; } if (!voip_data.out_stream_count) ret = voice_extn_compress_voip_open_output_stream(out); ret = voip_start_call(adev, &out->config); out->pcm = voip_data.pcm_rx; uc_info = get_usecase_from_list(adev, USECASE_COMPRESS_VOIP_CALL); if (uc_info) { uc_info->stream.out = out; uc_info->devices = out->devices; } else { ret = -EINVAL; ALOGE("%s: exit(%d): failed to get use case info", __func__, ret); goto error; } error: ALOGV("%s: exit: status(%d)", __func__, ret); return ret; } int voice_extn_compress_voip_start_input_stream(struct stream_in *in) { int ret = 0; struct audio_usecase *uc_info; struct audio_device *adev = in->dev; int snd_card_status = get_snd_card_state(adev); ALOGD("%s: enter", __func__); if (SND_CARD_STATE_OFFLINE == snd_card_status) { ret = -ENETRESET; ALOGE("%s: sound card is not active/SSR returning error %d ", __func__, ret); goto error; } if (!voip_data.in_stream_count) ret = voice_extn_compress_voip_open_input_stream(in); adev->active_input = in; ret = voip_start_call(adev, &in->config); in->pcm = voip_data.pcm_tx; error: ALOGV("%s: exit: status(%d)", __func__, ret); return ret; } int voice_extn_compress_voip_close_output_stream(struct audio_stream *stream) { struct stream_out *out = (struct stream_out *)stream; struct audio_device *adev = out->dev; int ret = 0; ALOGD("%s: enter", __func__); if (voip_data.out_stream_count > 0) { voip_data.out_stream_count--; ret = voip_stop_call(adev); voip_data.out_stream = NULL; out->pcm = NULL; } ALOGV("%s: exit: status(%d)", __func__, ret); return ret; } int voice_extn_compress_voip_open_output_stream(struct stream_out *out) { int mode, ret; ALOGD("%s: enter", __func__); out->supported_channel_masks[0] = AUDIO_CHANNEL_OUT_MONO; out->channel_mask = AUDIO_CHANNEL_OUT_MONO; out->usecase = USECASE_COMPRESS_VOIP_CALL; if (out->sample_rate == 16000) out->config = pcm_config_voip_wb; else out->config = pcm_config_voip_nb; voip_data.out_stream = out; voip_data.out_stream_count++; voip_data.sample_rate = out->sample_rate; ret = voip_set_mode(out->dev, out->format); ALOGV("%s: exit", __func__); return ret; } int voice_extn_compress_voip_close_input_stream(struct audio_stream *stream) { struct stream_in *in = (struct stream_in *)stream; struct audio_device *adev = in->dev; int status = 0; ALOGD("%s: enter", __func__); if(voip_data.in_stream_count > 0) { adev->active_input = NULL; voip_data.in_stream_count--; status = voip_stop_call(adev); in->pcm = NULL; } ALOGV("%s: exit: status(%d)", __func__, status); return status; } int voice_extn_compress_voip_open_input_stream(struct stream_in *in) { int sample_rate; int buffer_size,frame_size; int mode, ret; ALOGD("%s: enter", __func__); if ((voip_data.sample_rate != 0) && (voip_data.sample_rate != in->config.rate)) { ret = -ENOTSUP; goto done; } else { voip_data.sample_rate = in->config.rate; } in->usecase = USECASE_COMPRESS_VOIP_CALL; if (in->config.rate == 16000) in->config = pcm_config_voip_wb; else in->config = pcm_config_voip_nb; voip_data.in_stream_count++; ret = voip_set_mode(in->dev, in->format); done: ALOGV("%s: exit, ret=%d", __func__, ret); return ret; } int voice_extn_compress_voip_set_volume(struct audio_device *adev, float volume) { int vol, err = 0; ALOGV("%s: enter", __func__); if (volume < 0.0) { volume = 0.0; } else if (volume > 1.0) { volume = 1.0; } vol = lrint(volume * 100.0); /* Voice volume levels from android are mapped to driver volume levels as follows. * 0 -> 5, 20 -> 4, 40 ->3, 60 -> 2, 80 -> 1, 100 -> 0 * So adjust the volume to get the correct volume index in driver */ vol = 100 - vol; err = voip_set_volume(adev, vol); ALOGV("%s: exit: status(%d)", __func__, err); return err; } int voice_extn_compress_voip_set_mic_mute(struct audio_device *adev, bool state) { int err = 0; ALOGV("%s: enter", __func__); err = voip_set_mic_mute(adev, state); ALOGV("%s: exit: status(%d)", __func__, err); return err; } bool voice_extn_compress_voip_pcm_prop_check() { char prop_value[PROPERTY_VALUE_MAX] = {0}; property_get("use.voice.path.for.pcm.voip", prop_value, "0"); if (!strncmp("true", prop_value, sizeof("true"))) { ALOGD("%s: VoIP PCM property is enabled", __func__); return true; } else return false; } bool voice_extn_compress_voip_is_active(struct audio_device *adev) { struct audio_usecase *voip_usecase = NULL; voip_usecase = get_usecase_from_list(adev, USECASE_COMPRESS_VOIP_CALL); if (voip_usecase != NULL) return true; else return false; } bool voice_extn_compress_voip_is_format_supported(audio_format_t format) { switch (format) { case AUDIO_FORMAT_PCM_16_BIT: if (voice_extn_compress_voip_pcm_prop_check()) return true; else return false; case AUDIO_FORMAT_AMR_NB: case AUDIO_FORMAT_AMR_WB: case AUDIO_FORMAT_EVRC: case AUDIO_FORMAT_EVRCB: case AUDIO_FORMAT_EVRCWB: case AUDIO_FORMAT_EVRCNW: return true; default: return false; } } bool voice_extn_compress_voip_is_config_supported(struct audio_config *config) { bool ret = false; ret = voice_extn_compress_voip_is_format_supported(config->format); if (ret) { if ((popcount(config->channel_mask) == 1) && (config->sample_rate == 8000 || config->sample_rate == 16000)) ret = ((voip_data.sample_rate == 0) ? true: (voip_data.sample_rate == config->sample_rate)); else ret = false; } return ret; }