| /* |
| * Copyright (C) 2016 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_utils" |
| //#define LOG_NDEBUG 0 |
| |
| #include <errno.h> |
| #include <cutils/properties.h> |
| #include <cutils/config_utils.h> |
| #include <stdlib.h> |
| #include <dlfcn.h> |
| #include <unistd.h> |
| #include <cutils/str_parms.h> |
| #include <log/log.h> |
| #include <cutils/misc.h> |
| |
| #include "acdb.h" |
| #include "audio_hw.h" |
| #include "platform.h" |
| #include "platform_api.h" |
| #include "audio_extn.h" |
| |
| #define MAX_LENGTH_MIXER_CONTROL_IN_INT 128 |
| |
| static int set_stream_app_type_mixer_ctrl(struct audio_device *adev, |
| int pcm_device_id, int app_type, |
| int acdb_dev_id, int sample_rate, |
| int stream_type, |
| snd_device_t snd_device) |
| { |
| |
| char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT]; |
| struct mixer_ctl *ctl; |
| int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT], len = 0, rc = 0; |
| int snd_device_be_idx = -1; |
| |
| if (stream_type == PCM_PLAYBACK) { |
| snprintf(mixer_ctl_name, sizeof(mixer_ctl_name), |
| "Audio Stream %d App Type Cfg", pcm_device_id); |
| } else if (stream_type == PCM_CAPTURE) { |
| snprintf(mixer_ctl_name, sizeof(mixer_ctl_name), |
| "Audio Stream Capture %d App Type Cfg", pcm_device_id); |
| } |
| |
| 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); |
| rc = -EINVAL; |
| goto exit; |
| } |
| app_type_cfg[len++] = app_type; |
| app_type_cfg[len++] = acdb_dev_id; |
| app_type_cfg[len++] = sample_rate; |
| |
| snd_device_be_idx = platform_get_snd_device_backend_index(snd_device); |
| if (snd_device_be_idx > 0) |
| app_type_cfg[len++] = snd_device_be_idx; |
| ALOGV("%s: stream type %d app_type %d, acdb_dev_id %d " |
| "sample rate %d, snd_device_be_idx %d", |
| __func__, stream_type, app_type, acdb_dev_id, sample_rate, |
| snd_device_be_idx); |
| mixer_ctl_set_array(ctl, app_type_cfg, len); |
| |
| exit: |
| return rc; |
| } |
| |
| void audio_extn_utils_send_default_app_type_cfg(void *platform, struct mixer *mixer) |
| { |
| int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {-1}; |
| int length = 0, app_type = 0,rc = 0; |
| struct mixer_ctl *ctl = NULL; |
| const char *mixer_ctl_name = "App Type Config"; |
| |
| ctl = mixer_get_ctl_by_name(mixer, mixer_ctl_name); |
| if (!ctl) { |
| ALOGE("%s: Could not get ctl for mixer cmd - %s",__func__, mixer_ctl_name); |
| return; |
| } |
| rc = platform_get_default_app_type_v2(platform, PCM_PLAYBACK, &app_type); |
| if (rc == 0) { |
| app_type_cfg[length++] = 1; |
| app_type_cfg[length++] = app_type; |
| app_type_cfg[length++] = 48000; |
| app_type_cfg[length++] = 16; |
| mixer_ctl_set_array(ctl, app_type_cfg, length); |
| } |
| return; |
| } |
| |
| static const char *flags_to_mode(int dir, uint32_t flags) |
| { |
| if (dir == 0) { |
| if (flags & AUDIO_OUTPUT_FLAG_VOIP_RX) { |
| return "voip"; |
| } |
| } else if (dir == 1) { |
| if (flags & AUDIO_INPUT_FLAG_VOIP_TX) { |
| return "voip"; |
| } |
| } |
| return "default"; |
| } |
| |
| static int audio_extn_utils_send_app_type_cfg_hfp(struct audio_device *adev, |
| struct audio_usecase *usecase) |
| { |
| struct mixer_ctl *ctl; |
| int pcm_device_id, acdb_dev_id = 0, snd_device = usecase->out_snd_device; |
| int32_t sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE; |
| int app_type = 0, rc = 0; |
| |
| ALOGV("%s", __func__); |
| |
| if (usecase->type != PCM_HFP_CALL) { |
| ALOGV("%s: not a playback or HFP path, no need to cfg app type", __func__); |
| rc = 0; |
| goto exit_send_app_type_cfg; |
| } |
| if ((usecase->id != USECASE_AUDIO_HFP_SCO) && |
| (usecase->id != USECASE_AUDIO_HFP_SCO_WB)) { |
| ALOGV("%s: a playback path where app type cfg is not required", __func__); |
| rc = 0; |
| goto exit_send_app_type_cfg; |
| } |
| |
| snd_device = usecase->out_snd_device; |
| pcm_device_id = platform_get_pcm_device_id(usecase->id, PCM_PLAYBACK); |
| |
| acdb_dev_id = platform_get_snd_device_acdb_id(snd_device); |
| if (acdb_dev_id < 0) { |
| ALOGE("%s: Couldn't get the acdb dev id", __func__); |
| rc = -EINVAL; |
| goto exit_send_app_type_cfg; |
| } |
| |
| if (usecase->type == PCM_HFP_CALL) { |
| |
| /* config HFP session:1 playback path */ |
| rc = platform_get_default_app_type_v2(adev->platform, PCM_PLAYBACK, &app_type); |
| if (rc < 0) |
| goto exit_send_app_type_cfg; |
| |
| sample_rate= CODEC_BACKEND_DEFAULT_SAMPLE_RATE; |
| rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type, |
| acdb_dev_id, sample_rate, |
| PCM_PLAYBACK, |
| SND_DEVICE_NONE); // use legacy behavior |
| if (rc < 0) |
| goto exit_send_app_type_cfg; |
| /* config HFP session:1 capture path */ |
| rc = platform_get_default_app_type_v2(adev->platform, PCM_CAPTURE, &app_type); |
| |
| if (rc == 0) { |
| rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type, |
| acdb_dev_id, sample_rate, |
| PCM_CAPTURE, |
| SND_DEVICE_NONE); |
| if (rc < 0) |
| goto exit_send_app_type_cfg; |
| } |
| /* config HFP session:2 capture path */ |
| pcm_device_id = HFP_ASM_RX_TX; |
| snd_device = usecase->in_snd_device; |
| acdb_dev_id = platform_get_snd_device_acdb_id(snd_device); |
| if (acdb_dev_id <= 0) { |
| ALOGE("%s: Couldn't get the acdb dev id", __func__); |
| rc = -EINVAL; |
| goto exit_send_app_type_cfg; |
| } |
| rc = platform_get_default_app_type_v2(adev->platform, PCM_CAPTURE, &app_type); |
| if (rc == 0) { |
| rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type, |
| acdb_dev_id, sample_rate, PCM_CAPTURE, |
| SND_DEVICE_NONE); |
| if (rc < 0) |
| goto exit_send_app_type_cfg; |
| } |
| |
| /* config HFP session:2 playback path */ |
| rc = platform_get_default_app_type_v2(adev->platform, PCM_PLAYBACK, &app_type); |
| if (rc == 0) { |
| rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type, |
| acdb_dev_id, sample_rate, |
| PCM_PLAYBACK, SND_DEVICE_NONE); |
| if (rc < 0) |
| goto exit_send_app_type_cfg; |
| } |
| } |
| |
| rc = 0; |
| exit_send_app_type_cfg: |
| return rc; |
| } |
| |
| |
| static int derive_capture_app_type_cfg(struct audio_device *adev, |
| struct audio_usecase *usecase, |
| int *app_type, |
| int *sample_rate) |
| { |
| if (usecase->stream.in == NULL) { |
| return -1; |
| } |
| struct stream_in *in = usecase->stream.in; |
| struct stream_app_type_cfg *app_type_cfg = &in->app_type_cfg; |
| |
| *sample_rate = DEFAULT_INPUT_SAMPLING_RATE; |
| if (audio_is_usb_in_device(in->device)) { |
| platform_check_and_update_copp_sample_rate(adev->platform, |
| usecase->in_snd_device, |
| in->sample_rate, |
| sample_rate); |
| } |
| |
| app_type_cfg->mode = flags_to_mode(1 /*capture*/, in->flags); |
| ALOGV("%s mode %s", __func__, app_type_cfg->mode); |
| if (in->format == AUDIO_FORMAT_PCM_16_BIT) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_CAPTURE, |
| app_type_cfg->mode, |
| 16, |
| app_type_cfg->sample_rate, |
| app_type); |
| } else if (in->format == AUDIO_FORMAT_PCM_24_BIT_PACKED || |
| in->format == AUDIO_FORMAT_PCM_8_24_BIT) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_CAPTURE, |
| app_type_cfg->mode, |
| 24, |
| app_type_cfg->sample_rate, |
| app_type); |
| } else if (in->format == AUDIO_FORMAT_PCM_32_BIT) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_CAPTURE, |
| app_type_cfg->mode, |
| 32, |
| app_type_cfg->sample_rate, |
| app_type); |
| } else { |
| ALOGE("%s bad format\n", __func__); |
| return -1; |
| } |
| |
| app_type_cfg->app_type = *app_type; |
| app_type_cfg->sample_rate = *sample_rate; |
| return 0; |
| } |
| |
| static int derive_playback_app_type_cfg(struct audio_device *adev, |
| struct audio_usecase *usecase, |
| int *app_type, |
| int *sample_rate) |
| { |
| if (usecase->stream.out == NULL) { |
| return -1; |
| } |
| struct stream_out *out = usecase->stream.out; |
| struct stream_app_type_cfg *app_type_cfg = &out->app_type_cfg; |
| |
| *sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE; |
| |
| // add speaker prot changes if needed |
| // and use that to check for device |
| if (audio_is_usb_out_device(out->devices)) { |
| platform_check_and_update_copp_sample_rate(adev->platform, |
| usecase->out_snd_device, |
| out->sample_rate, |
| sample_rate); |
| } else if (out->devices & AUDIO_DEVICE_OUT_ALL_A2DP) { |
| audio_extn_a2dp_get_sample_rate(sample_rate); |
| ALOGI("%s: Using sample rate %d for A2DP CoPP", __func__, |
| *sample_rate); |
| } |
| |
| app_type_cfg->mode = flags_to_mode(0 /*playback*/, out->flags); |
| if (!audio_is_linear_pcm(out->format)) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_PLAYBACK, |
| app_type_cfg->mode, |
| 24, |
| *sample_rate, |
| app_type); |
| ALOGV("Non pcm got app type %d", *app_type); |
| } else if (out->format == AUDIO_FORMAT_PCM_16_BIT) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_PLAYBACK, |
| app_type_cfg->mode, |
| 16, |
| *sample_rate, |
| app_type); |
| } else if (out->format == AUDIO_FORMAT_PCM_24_BIT_PACKED || |
| out->format == AUDIO_FORMAT_PCM_8_24_BIT) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_PLAYBACK, |
| app_type_cfg->mode, |
| 24, |
| *sample_rate, |
| app_type); |
| } else if (out->format == AUDIO_FORMAT_PCM_32_BIT) { |
| platform_get_app_type_v2(adev->platform, |
| PCM_PLAYBACK, |
| app_type_cfg->mode, |
| 32, |
| *sample_rate, |
| app_type); |
| } else { |
| ALOGE("%s bad format\n", __func__); |
| return -1; |
| } |
| |
| app_type_cfg->app_type = *app_type; |
| app_type_cfg->sample_rate = *sample_rate; |
| return 0; |
| } |
| |
| static int derive_acdb_dev_id(struct audio_device *adev __unused, |
| struct audio_usecase *usecase) |
| { |
| struct stream_out *out; |
| struct stream_in *in; |
| |
| if (usecase->type == PCM_PLAYBACK) { |
| return platform_get_snd_device_acdb_id(usecase->out_snd_device); |
| } else if(usecase->type == PCM_CAPTURE) { |
| return platform_get_snd_device_acdb_id(usecase->in_snd_device); |
| } |
| return -1; |
| } |
| |
| int audio_extn_utils_send_app_type_cfg(struct audio_device *adev, |
| struct audio_usecase *usecase) |
| { |
| int len = 0; |
| int sample_rate; |
| int app_type; |
| int acdb_dev_id; |
| int new_snd_device[2] = {0}; |
| int i = 0, num_devices = 1; |
| size_t app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {0}; |
| char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {0}; |
| int pcm_device_id; |
| struct mixer_ctl *ctl; |
| int ret; |
| |
| if (usecase->type == PCM_HFP_CALL) { |
| return audio_extn_utils_send_app_type_cfg_hfp(adev, usecase); |
| } |
| |
| if (!platform_supports_app_type_cfg()) |
| return -1; |
| |
| if (usecase->type == PCM_PLAYBACK) { |
| ret = derive_playback_app_type_cfg(adev, |
| usecase, |
| &app_type, |
| &sample_rate); |
| } else if (usecase->type == PCM_CAPTURE) { |
| ret = derive_capture_app_type_cfg(adev, |
| usecase, |
| &app_type, |
| &sample_rate); |
| } else { |
| ALOGE("%s: Invalid uc type : 0x%x", __func__, usecase->type); |
| return -1; |
| } |
| |
| if (ret < 0) { |
| ALOGE("%s: Failed to derive app_type for uc type : 0x%x", __func__, |
| usecase->type); |
| return -1; |
| } |
| |
| if (usecase->type == PCM_PLAYBACK) { |
| if (platform_can_split_snd_device(usecase->out_snd_device, |
| &num_devices, new_snd_device) < 0) |
| new_snd_device[0] = usecase->out_snd_device; |
| |
| } else if (usecase->type == PCM_CAPTURE) |
| new_snd_device[0] = usecase->in_snd_device; |
| |
| pcm_device_id = platform_get_pcm_device_id(usecase->id, usecase->type); |
| |
| for (i = 0; i < num_devices; i++) { |
| acdb_dev_id = platform_get_snd_device_acdb_id(new_snd_device[i]); |
| |
| if (acdb_dev_id < 0) { |
| ALOGE("%s: Could not find acdb id for device(%d)", |
| __func__, new_snd_device[i]); |
| return -EINVAL; |
| } |
| ALOGV("%s: sending app type for snd_device(%d) acdb_id(%d) i %d", |
| __func__, new_snd_device[i], acdb_dev_id, i); |
| |
| set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type, acdb_dev_id, |
| sample_rate, |
| usecase->type, |
| new_snd_device[i]); |
| } |
| |
| return 0; |
| } |
| |
| int audio_extn_utils_send_app_type_gain(struct audio_device *adev, |
| int app_type, |
| int *gain) |
| { |
| int gain_cfg[4]; |
| const char *mixer_ctl_name = "App Type Gain"; |
| struct mixer_ctl *ctl; |
| ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); |
| if (!ctl) { |
| ALOGE("%s: Could not get volume ctl mixer %s", __func__, |
| mixer_ctl_name); |
| return -EINVAL; |
| } |
| gain_cfg[0] = 0; |
| gain_cfg[1] = app_type; |
| gain_cfg[2] = gain[0]; |
| gain_cfg[3] = gain[1]; |
| ALOGV("%s app_type %d l(%d) r(%d)", __func__, app_type, gain[0], gain[1]); |
| return mixer_ctl_set_array(ctl, gain_cfg, |
| sizeof(gain_cfg)/sizeof(gain_cfg[0])); |
| } |
| |
| // this assumes correct app_type and sample_rate fields |
| // have been set for the stream using audio_extn_utils_send_app_type_cfg |
| void audio_extn_utils_send_audio_calibration(struct audio_device *adev, |
| struct audio_usecase *usecase) |
| { |
| int type = usecase->type; |
| int app_type = 0; |
| |
| if (type == PCM_PLAYBACK && usecase->stream.out != NULL) { |
| struct stream_out *out = usecase->stream.out; |
| ALOGV("%s send cal for app_type %d, rate %d", __func__, |
| out->app_type_cfg.app_type, |
| out->app_type_cfg.sample_rate); |
| platform_send_audio_calibration_v2(adev->platform, usecase, |
| out->app_type_cfg.app_type, |
| out->app_type_cfg.sample_rate); |
| } else if (type == PCM_CAPTURE && usecase->stream.in != NULL) { |
| struct stream_in *in = usecase->stream.in; |
| ALOGV("%s send cal for capture app_type %d, rate %d", __func__, |
| in->app_type_cfg.app_type, |
| in->app_type_cfg.sample_rate); |
| platform_send_audio_calibration_v2(adev->platform, usecase, |
| in->app_type_cfg.app_type, |
| in->app_type_cfg.sample_rate); |
| } else { |
| /* when app type is default. the sample rate is not used to send cal */ |
| platform_get_default_app_type_v2(adev->platform, type, &app_type); |
| platform_send_audio_calibration_v2(adev->platform, usecase, app_type, |
| 48000); |
| } |
| } |
| |
| #define MAX_SND_CARD 8 |
| #define RETRY_US 500000 |
| #define RETRY_NUMBER 10 |
| |
| #define min(a, b) ((a) < (b) ? (a) : (b)) |
| |
| static const char *kConfigLocationList[] = |
| {"/odm/etc", "/vendor/etc", "/system/etc"}; |
| static const int kConfigLocationListSize = |
| (sizeof(kConfigLocationList) / sizeof(kConfigLocationList[0])); |
| |
| bool audio_extn_utils_resolve_config_file(char file_name[MIXER_PATH_MAX_LENGTH]) |
| { |
| char full_config_path[MIXER_PATH_MAX_LENGTH]; |
| for (int i = 0; i < kConfigLocationListSize; i++) { |
| snprintf(full_config_path, |
| MIXER_PATH_MAX_LENGTH, |
| "%s/%s", |
| kConfigLocationList[i], |
| file_name); |
| if (F_OK == access(full_config_path, 0)) { |
| strcpy(file_name, full_config_path); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* platform_info_file should be size 'MIXER_PATH_MAX_LENGTH' */ |
| int audio_extn_utils_get_platform_info(const char* snd_card_name, char* platform_info_file) |
| { |
| if (NULL == snd_card_name) { |
| return -1; |
| } |
| |
| struct snd_card_split *snd_split_handle = NULL; |
| int ret = 0; |
| audio_extn_set_snd_card_split(snd_card_name); |
| snd_split_handle = audio_extn_get_snd_card_split(); |
| |
| snprintf(platform_info_file, MIXER_PATH_MAX_LENGTH, "%s_%s_%s.xml", |
| PLATFORM_INFO_XML_BASE_STRING, snd_split_handle->snd_card, |
| snd_split_handle->form_factor); |
| |
| if (!audio_extn_utils_resolve_config_file(platform_info_file)) { |
| memset(platform_info_file, 0, MIXER_PATH_MAX_LENGTH); |
| snprintf(platform_info_file, MIXER_PATH_MAX_LENGTH, "%s_%s.xml", |
| PLATFORM_INFO_XML_BASE_STRING, snd_split_handle->snd_card); |
| |
| if (!audio_extn_utils_resolve_config_file(platform_info_file)) { |
| memset(platform_info_file, 0, MIXER_PATH_MAX_LENGTH); |
| strlcpy(platform_info_file, PLATFORM_INFO_XML_PATH, MIXER_PATH_MAX_LENGTH); |
| ret = audio_extn_utils_resolve_config_file(platform_info_file) ? 0 : -1; |
| } |
| } |
| |
| return ret; |
| } |
| |
| int audio_extn_utils_get_snd_card_num() |
| { |
| |
| void *hw_info = NULL; |
| struct mixer *mixer = NULL; |
| int retry_num = 0; |
| int snd_card_num = 0; |
| const char* snd_card_name = NULL; |
| char platform_info_file[MIXER_PATH_MAX_LENGTH]= {0}; |
| |
| struct acdb_platform_data *my_data = calloc(1, sizeof(struct acdb_platform_data)); |
| |
| bool card_verifed[MAX_SND_CARD] = {0}; |
| const int retry_limit = property_get_int32( |
| "vendor.audio.snd_card.open.retries", |
| property_get_int32("audio.snd_card.open.retries", RETRY_NUMBER)); |
| |
| for (;;) { |
| if (snd_card_num >= MAX_SND_CARD) { |
| if (retry_num++ >= retry_limit) { |
| ALOGE("%s: Unable to find correct sound card, aborting.", __func__); |
| snd_card_num = -1; |
| goto done; |
| } |
| |
| snd_card_num = 0; |
| usleep(RETRY_US); |
| continue; |
| } |
| |
| if (card_verifed[snd_card_num]) { |
| ++snd_card_num; |
| continue; |
| } |
| |
| mixer = mixer_open(snd_card_num); |
| |
| if (!mixer) { |
| ALOGE("%s: Unable to open the mixer card: %d", __func__, |
| snd_card_num); |
| ++snd_card_num; |
| continue; |
| } |
| |
| card_verifed[snd_card_num] = true; |
| |
| snd_card_name = mixer_get_name(mixer); |
| hw_info = hw_info_init(snd_card_name); |
| |
| if (audio_extn_utils_get_platform_info(snd_card_name, platform_info_file) < 0) { |
| ALOGE("Failed to find platform_info_file"); |
| goto cleanup; |
| } |
| |
| /* Initialize snd card name specific ids and/or backends*/ |
| if (platform_info_init(platform_info_file, my_data, false, |
| &acdb_set_parameters) < 0) { |
| ALOGE("Failed to find platform_info_file"); |
| goto cleanup; |
| } |
| |
| /* validate the sound card name |
| * my_data->snd_card_name can contain |
| * <a> complete sound card name, i.e. <device>-<codec>-<form_factor>-snd-card |
| * example: msm8994-tomtom-mtp-snd-card |
| * <b> or sub string of the card name, i.e. <device>-<codec> |
| * example: msm8994-tomtom |
| * snd_card_name is truncated to 32 charaters as per mixer_get_name() implementation |
| * so use min of my_data->snd_card_name and snd_card_name length for comparison |
| */ |
| |
| if (my_data->snd_card_name != NULL && |
| strncmp(snd_card_name, my_data->snd_card_name, |
| min(strlen(snd_card_name), strlen(my_data->snd_card_name))) != 0) { |
| ALOGI("%s: found valid sound card %s, but not primary sound card %s", |
| __func__, snd_card_name, my_data->snd_card_name); |
| goto cleanup; |
| } |
| |
| ALOGI("%s: found sound card %s, primary sound card expected is %s", |
| __func__, snd_card_name, my_data->snd_card_name); |
| break; |
| cleanup: |
| ++snd_card_num; |
| mixer_close(mixer); |
| mixer = NULL; |
| hw_info_deinit(hw_info); |
| hw_info = NULL; |
| } |
| |
| done: |
| mixer_close(mixer); |
| hw_info_deinit(hw_info); |
| |
| if (my_data) |
| free(my_data); |
| |
| return snd_card_num; |
| } |