/* * Copyright (C) 2012 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/ * Copyright (C) 2012 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. */ /* * This code has modified by Intel Corporation */ /* * Copyright (c) 2014, Intel Corporation * * 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 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "tiny_hdmi_audio_hw" //#define LOG_NDEBUG 0 #include <errno.h> #include <pthread.h> #include <stdint.h> #include <sys/time.h> #include <stdlib.h> #include <cutils/log.h> #include <cutils/str_parms.h> #include <cutils/properties.h> #include <hardware/hardware.h> #include <system/audio.h> #include <hardware/audio.h> #include <sound/asound.h> #include <tinyalsa/asoundlib.h> #define UNUSED_PARAMETER(x) (void)(x) #define DEFAULT_CARD 0 #define DEFAULT_DEVICE 0 /*this is used to avoid starvation*/ #define LATENCY_TO_BUFFER_SIZE_RATIO 2 /*Playback Channel Map*/ #define CHANNEL_MAP_REQUEST 2 /*global - keep track of the active device. This is needed since we are supporting more than one profile for HDMI. The Flinger assumes we can suport multiple streams at the same time. This makes sure only one stream is active at a time.*/ struct pcm * activePcm = NULL; /*TODO - move active channel inside activepcm*/ static unsigned int activeChannel; #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define STRING_TO_ENUM(string) { #string, string } struct channel_list { const char *name; uint32_t value; }; const struct channel_list channel_list_table[] = { STRING_TO_ENUM(AUDIO_CHANNEL_OUT_STEREO), STRING_TO_ENUM(AUDIO_CHANNEL_OUT_5POINT1), STRING_TO_ENUM(AUDIO_CHANNEL_OUT_7POINT1), }; struct pcm_config pcm_config_default = { .channels = 2, .rate = 44100, .period_size = 1024, .period_count = 4, .format = PCM_FORMAT_S24_LE, }; #define CHANNEL_MASK_MAX 3 struct audio_device { struct audio_hw_device hw_device; pthread_mutex_t lock; int card; int device; bool standby; int sink_sup_channels; audio_channel_mask_t sup_channel_masks[CHANNEL_MASK_MAX]; }; struct stream_out { struct audio_stream_out stream; pthread_mutex_t lock; struct pcm *pcm; bool standby; /* PCM Stream Configurations */ struct pcm_config pcm_config; uint32_t channel_mask; /* ALSA PCM Configurations */ uint32_t sample_rate; uint32_t buffer_size; uint32_t channels; uint32_t latency; struct audio_device *dev; }; /** * NOTE: when multiple mutexes have to be acquired, always respect the * following order: hw device > out stream */ /* Helper functions */ // This function return the card number associated with the card ID (name) // passed as argument static int get_card_number_by_name(const char* name) { char id_filepath[PATH_MAX] = {0}; char number_filepath[PATH_MAX] = {0}; ssize_t written; snprintf(id_filepath, sizeof(id_filepath), "/proc/asound/%s", name); written = readlink(id_filepath, number_filepath, sizeof(number_filepath)); if (written < 0) { ALOGE("Sound card %s does not exist - setting default", name); return DEFAULT_CARD; } else if (written >= (ssize_t)sizeof(id_filepath)) { ALOGE("Sound card %s name is too long - setting default", name); return DEFAULT_CARD; } // We are assured, because of the check in the previous elseif, that this // buffer is null-terminated. So this call is safe. // 4 == strlen("card") return atoi(number_filepath + 4); } static enum pcm_format Get_SinkSupported_format() { /*TODO : query sink supported formats*/ return PCM_FORMAT_S24_LE; } static int make_sinkcompliant_buffers(void* input, void *output, int ipbytes) { int i = 0,outbytes = 0; enum pcm_format out_pcmformat; int *src = (int*)input; int *dst = (int*)output; /*by default android currently support only 16 bit signed PCM*/ out_pcmformat = Get_SinkSupported_format(); switch (out_pcmformat) { default: case PCM_FORMAT_S24_LE: { ALOGV("convert 16 to 24 bits for %d",ipbytes); /*convert 16 bit input to 24 bit output in a 32 bit sample*/ if(0 == ipbytes) break; for(i = 0; i < (ipbytes/4); i++){ int x = (int)((int*)src)[i]; dst[i*2] = ((int)( x & 0x0000FFFF)) << 8; // trying to sign exdent dst[i*2] = dst[i*2] << 8; dst[i*2] = dst[i*2] >> 8; //shift to middle dst[i*2 + 1] = (int)(( x & 0xFFFF0000) >> 8); dst[i*2 + 1] = dst[i*2 + 1] << 8; dst[i*2 + 1] = dst[i*2 + 1] >> 8; } outbytes=ipbytes * 2; }//case };//switch return outbytes; } /* must be called with hw device and output stream mutexes locked */ static int start_output_stream(struct stream_out *out) { struct audio_device *adev = out->dev; ALOGV("%s enter",__func__); if ((adev->card < 0) || (adev->device < 0)){ /*this will be updated once the hot plug intent sends these information.*/ adev->card = DEFAULT_CARD; adev->device = DEFAULT_DEVICE; ALOGV("%s : Setting default card/ device %d,%d",__func__,adev->card,adev->device); } ALOGV("%s enter %d,%d,%d,%d,%d",__func__, out->pcm_config.channels, out->pcm_config.rate, out->pcm_config.period_size, out->pcm_config.period_count, out->pcm_config.format); out->pcm_config.start_threshold = 0; out->pcm_config.stop_threshold = 0; out->pcm_config.silence_threshold = 0; if(activePcm){ ALOGV("Closing already open tiny alsa stream running state %d",(int)(activePcm)); pcm_close(activePcm); activePcm = NULL; } /*TODO - this needs to be updated once the device connect intent sends card, device id*/ adev->card = get_card_number_by_name("IntelHDMI"); ALOGD("%s: HDMI card number = %d, device = %d",__func__,adev->card,adev->device); out->pcm = pcm_open(adev->card, adev->device, PCM_OUT, &out->pcm_config); if (out->pcm && !pcm_is_ready(out->pcm)) { ALOGE("pcm_open() failed: %s", pcm_get_error(out->pcm)); pcm_close(out->pcm); activePcm = NULL; return -ENOMEM; } activePcm = out->pcm; activeChannel = out->pcm_config.channels; ALOGV("Initialized PCM device for channels %d handle = %d",out->pcm_config.channels, (int)activePcm); ALOGV("%s exit",__func__); return 0; } /* API functions */ static uint32_t out_get_sample_rate(const struct audio_stream *stream) { struct stream_out *out = (struct stream_out *)stream; return out->pcm_config.rate; } static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(rate); return 0; } static size_t out_get_buffer_size(const struct audio_stream *stream) { struct stream_out *out = (struct stream_out *)stream; size_t buf_size; if(out->channel_mask > 2){ buf_size = out->pcm_config.period_size * audio_stream_out_frame_size((struct audio_stream_out *)stream); } else{ buf_size = out->pcm_config.period_size * out->pcm_config.period_count * audio_stream_out_frame_size((struct audio_stream_out *)stream); /*latency of audio flinger is based on this buffer size. modifying the buffer size to avoid starvation*/ buf_size/=LATENCY_TO_BUFFER_SIZE_RATIO; } ALOGV("%s : %d, period_size : %d, frame_size : %d", __func__, buf_size, out->pcm_config.period_size, audio_stream_out_frame_size((struct audio_stream_out *)stream)); return buf_size; } static uint32_t out_get_channels(const struct audio_stream *stream) { struct stream_out *out = (struct stream_out *)stream; ALOGV("%s channel mask : %x",__func__,out->channel_mask); return out->channel_mask; } static audio_format_t out_get_format(const struct audio_stream *stream) { UNUSED_PARAMETER(stream); return AUDIO_FORMAT_PCM_16_BIT; } static int out_set_format(struct audio_stream *stream, audio_format_t format) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(format); return 0; } static int out_standby(struct audio_stream *stream) { struct stream_out *out = (struct stream_out *)stream; ALOGV("%s enter standby = %d",__func__,out->standby); pthread_mutex_lock(&out->dev->lock); pthread_mutex_lock(&out->lock); if (!out->standby && activePcm) { pcm_close(activePcm); out->pcm = NULL; out->standby = true; activePcm = NULL; ALOGV("%s PCM device closed",__func__); } pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&out->dev->lock); ALOGV("%s exit",__func__); return 0; } static int out_dump(const struct audio_stream *stream, int fd) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(fd); return 0; } static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) { struct stream_out *out = (struct stream_out *)stream; struct audio_device *adev = out->dev; struct str_parms *parms; char value[32]; int ret; ALOGV("%s enter",__func__); parms = str_parms_create_str(kvpairs); pthread_mutex_lock(&adev->lock); if (parms == NULL) { ALOGE("couldn't extract string params from key value pairs"); pthread_mutex_unlock(&adev->lock); return 0; } ret = str_parms_get_str(parms, "card", value, sizeof(value)); if (ret >= 0) adev->card = atoi(value); ret = str_parms_get_str(parms, "device", value, sizeof(value)); if (ret >= 0) adev->device = atoi(value); pthread_mutex_unlock(&adev->lock); str_parms_destroy(parms); ALOGV("%s exit",__func__); return 0; } static int parse_channel_map() { struct mixer *mixer; int card = 0; struct mixer_ctl *ctl; enum mixer_ctl_type type; unsigned int num_values; unsigned int i,id; int chcount=0, chmap=0; card = get_card_number_by_name("IntelHDMI"); mixer = mixer_open(card); if (!mixer) { ALOGE("[EDID] Failed to open mixer\n"); goto chmap_error; } id = CHANNEL_MAP_REQUEST; if (id >= mixer_get_num_ctls(mixer)) { ALOGE("[EDID] Invalid request for channel map %d",id); goto chmap_error; } ctl = mixer_get_ctl_by_name(mixer, "Playback Channel Map"); //ctl = mixer_get_ctl(mixer, id); type = mixer_ctl_get_type(ctl); num_values = mixer_ctl_get_num_values(ctl); ALOGV("[EDID]id = %d",id); ALOGV("[EDID]type = %d",type); ALOGV("[EDID]count = %d",num_values); for (i = 0; i < num_values; i++) { switch (type) { case MIXER_CTL_TYPE_INT: chmap = mixer_ctl_get_value(ctl, i); ALOGD("[EDID]chmap = %d", chmap); if(chmap > 0) ++chcount; break; default: printf(" unknown"); break; }; }//for ALOGD("[EDID]valid number of channels supported by sink = %d",chcount); mixer_close(mixer); return chcount; chmap_error: mixer_close(mixer); return 2;//stereo by default } static int out_read_edid(const struct stream_out *stream) { struct stream_out *out = (struct stream_out *)stream; struct audio_device *adev = out->dev; /**read the channel max param from the sink*/ adev->sink_sup_channels = parse_channel_map(); if(adev->sink_sup_channels == 8) { adev->sup_channel_masks[0] = AUDIO_CHANNEL_OUT_5POINT1; adev->sup_channel_masks[1] = AUDIO_CHANNEL_OUT_7POINT1; } else if((adev->sink_sup_channels == 6) || (adev->sink_sup_channels > 2)) { adev->sup_channel_masks[0] = AUDIO_CHANNEL_OUT_5POINT1; } else { adev->sup_channel_masks[0] = AUDIO_CHANNEL_OUT_STEREO; } ALOGV("%s sink supports 0x%x max channels", __func__,adev->sink_sup_channels); return 0; } static char * out_get_parameters(const struct audio_stream *stream, const char *keys) { struct stream_out *out = (struct stream_out *)stream; struct audio_device *adev = out->dev; struct str_parms *params_in = str_parms_create_str(keys); char *str = NULL; char value[256] = {0}; int ret; size_t i, j; bool append = false; struct str_parms *params_out = str_parms_create(); ALOGV("%s Entered %s", __func__,keys); if (params_in) { ret = str_parms_get_str(params_in, AUDIO_PARAMETER_STREAM_SUP_CHANNELS, value, sizeof(value)); if (ret >= 0) { /*read the channel support from sink*/ out_read_edid(out); value[0] = '\0'; for (i = 0; i < CHANNEL_MASK_MAX; i++) { for (j = 0; j < ARRAY_SIZE(channel_list_table); j++) { if (channel_list_table[j].value == adev->sup_channel_masks[i]) { if (append) { strcat(value, "|"); } strcat(value, channel_list_table[j].name); append = true; break; } } } } } if (params_out) { str_parms_add_str(params_out, AUDIO_PARAMETER_STREAM_SUP_CHANNELS, value); str = str_parms_to_str(params_out); } else { str = strdup(keys); } ALOGV("%s AUDIO_PARAMETER_STREAM_SUP_CHANNELS %s", __func__,str); if (params_in) { str_parms_destroy(params_in); } if (params_out) { str_parms_destroy(params_out); } return str; } static uint32_t out_get_latency(const struct audio_stream_out *stream) { struct stream_out *out = (struct stream_out *)stream; return (out->pcm_config.period_size * out->pcm_config.period_count * 1000) / out_get_sample_rate(&stream->common); } static int out_set_volume(struct audio_stream_out *stream, float left, float right) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(left); UNUSED_PARAMETER(right); return -ENOSYS; } static ssize_t out_write(struct audio_stream_out *stream, const void* buffer, size_t bytes) { int ret = 0; struct stream_out *out = (struct stream_out *)stream; int32_t* dstbuff = NULL; int outbytes = 0; ALOGV("%s enter for bytes = %d channels = %d",__func__,bytes, out->pcm_config.channels); pthread_mutex_lock(&out->dev->lock); pthread_mutex_lock(&out->lock); if(activePcm == NULL) { ALOGV("%s: previous stream closed- open again",__func__); out->standby = true; } if (out->standby) { ret = start_output_stream(out); if (ret != 0) { goto err; } out->standby = false; } if((!out->pcm) || (activeChannel != out->pcm_config.channels)){ ALOGD("%s: null handle to write - device already closed",__func__); goto err; } if(Get_SinkSupported_format() == out->pcm_config.format){ /*16 bit data will be converted to 24 bit over 32 bit data type hence the multiplier 2*/ dstbuff = (int32_t*)malloc(bytes* 2); if (!dstbuff) { pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&out->dev->lock); ALOGE("%s : memory allocation failed",__func__); return -ENOMEM; } memset(dstbuff,0,bytes * 2); outbytes = make_sinkcompliant_buffers((void*)buffer, (void*)dstbuff,bytes); } //if()for conversion if(dstbuff){ ret = pcm_write(out->pcm, (void *)dstbuff, outbytes); } else ret = pcm_write(out->pcm, (void *)buffer, bytes); ALOGV("pcm_write: %s done for %d input bytes, output bytes = %d ", pcm_get_error(out->pcm),bytes,outbytes); free(dstbuff); err: pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&out->dev->lock); if(ret !=0){ uint64_t duration_ms = ((bytes * 1000)/ (audio_stream_out_frame_size(stream)) / (out_get_sample_rate(&stream->common))); ALOGV("%s : silence written", __func__); usleep(duration_ms * 1000); } ALOGV("%s exit",__func__); return bytes; } static int out_get_render_position(const struct audio_stream_out *stream, uint32_t *dsp_frames) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(dsp_frames); return -EINVAL; } static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(effect); return 0; } static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(effect); return 0; } static int out_get_next_write_timestamp(const struct audio_stream_out *stream, int64_t *timestamp) { UNUSED_PARAMETER(stream); UNUSED_PARAMETER(timestamp); return -EINVAL; } static int adev_open_output_stream(struct audio_hw_device *dev, audio_io_handle_t handle, audio_devices_t devices, audio_output_flags_t flags, struct audio_config *config, struct audio_stream_out **stream_out, const char *address) { UNUSED_PARAMETER(devices); UNUSED_PARAMETER(handle); UNUSED_PARAMETER(address); struct audio_device *adev = (struct audio_device *)dev; struct stream_out *out; int ret; ALOGV("%s enter",__func__); out = (struct stream_out *)calloc(1, sizeof(struct stream_out)); if (!out) return -ENOMEM; out->dev = adev; out->channel_mask = AUDIO_CHANNEL_OUT_STEREO; adev->sup_channel_masks[0] = AUDIO_CHANNEL_OUT_STEREO; if (flags & AUDIO_OUTPUT_FLAG_DIRECT) { ALOGV("%s: HDMI Multichannel",__func__); if (config->sample_rate == 0) config->sample_rate = pcm_config_default.rate; if (config->channel_mask == 0){ /*read the channel support from sink*/ out_read_edid(out); if(config->channel_mask == 0) config->channel_mask = AUDIO_CHANNEL_OUT_5POINT1; } } else { ALOGV("%s: HDMI Stereo",__func__); if (config->sample_rate == 0) config->sample_rate = pcm_config_default.rate; if (config->channel_mask == 0) config->channel_mask = AUDIO_CHANNEL_OUT_STEREO; } out->channel_mask = config->channel_mask; out->pcm_config.channels = popcount(config->channel_mask); out->pcm_config.rate = config->sample_rate; out->pcm_config.period_size = pcm_config_default.period_size; out->pcm_config.period_count = pcm_config_default.period_count; out->pcm_config.format = pcm_config_default.format; out->stream.common.get_sample_rate = out_get_sample_rate; out->stream.common.set_sample_rate = out_set_sample_rate; out->stream.common.get_buffer_size = out_get_buffer_size; out->stream.common.get_channels = out_get_channels; out->stream.common.get_format = out_get_format; out->stream.common.set_format = out_set_format; out->stream.common.standby = out_standby; out->stream.common.dump = out_dump; out->stream.common.set_parameters = out_set_parameters; out->stream.common.get_parameters = out_get_parameters; out->stream.common.add_audio_effect = out_add_audio_effect; out->stream.common.remove_audio_effect = out_remove_audio_effect; out->stream.get_latency = out_get_latency; out->stream.set_volume = out_set_volume; out->stream.write = out_write; out->stream.get_render_position = out_get_render_position; out->stream.get_next_write_timestamp = out_get_next_write_timestamp; config->format = out_get_format(&out->stream.common); config->channel_mask = out_get_channels(&out->stream.common); config->sample_rate = out_get_sample_rate(&out->stream.common); out->standby = true; adev->card = -1; adev->device = -1; pthread_mutex_lock(&out->dev->lock); pthread_mutex_lock(&out->lock); if(activePcm){ ALOGV("Closing already open tiny alsa stream %d",(int)out->pcm); pcm_close(activePcm); activePcm = NULL; } ret = start_output_stream(out); if (ret != 0) { ALOGV("%s: stream start failed", __func__); goto err_open; } out->standby = false; *stream_out = &out->stream; pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&out->dev->lock); ALOGV("%s exit",__func__); return 0; err_open: ALOGE("%s exit with error",__func__); pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&out->dev->lock); free(out); *stream_out = NULL; return ret; } static void adev_close_output_stream(struct audio_hw_device *dev, struct audio_stream_out *stream) { UNUSED_PARAMETER(dev); struct stream_out *out = (struct stream_out *)stream; ALOGV("%s enter",__func__); out->standby = false; out_standby(&stream->common); free(stream); ALOGV("%s exit",__func__); } static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(kvpairs); return 0; } static char * adev_get_parameters(const struct audio_hw_device *dev, const char *keys) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(keys); return strdup(""); } static int adev_init_check(const struct audio_hw_device *dev) { UNUSED_PARAMETER(dev); return 0; } static int adev_set_voice_volume(struct audio_hw_device *dev, float volume) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(volume); return -ENOSYS; } static int adev_set_master_volume(struct audio_hw_device *dev, float volume) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(volume); return -ENOSYS; } static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(mode); return 0; } static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(state); return -ENOSYS; } static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(state); return -ENOSYS; } static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, const struct audio_config *config) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(config); return 0; } static int adev_open_input_stream(struct audio_hw_device *dev, audio_io_handle_t handle, audio_devices_t devices, struct audio_config *config, struct audio_stream_in **stream_in, audio_input_flags_t flags, const char *address, audio_source_t source) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(handle); UNUSED_PARAMETER(devices); UNUSED_PARAMETER(config); UNUSED_PARAMETER(stream_in); UNUSED_PARAMETER(flags); UNUSED_PARAMETER(address); UNUSED_PARAMETER(source); return -ENOSYS; } static void adev_close_input_stream(struct audio_hw_device *dev, struct audio_stream_in *stream) { UNUSED_PARAMETER(dev); UNUSED_PARAMETER(stream); } static int adev_dump(const audio_hw_device_t *device, int fd) { UNUSED_PARAMETER(device); UNUSED_PARAMETER(fd); return 0; } static int adev_close(hw_device_t *device) { free(device); return 0; } static int adev_open(const hw_module_t* module, const char* name, hw_device_t** device) { struct audio_device *adev; ALOGV("%s enter",__func__); if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) return -EINVAL; adev = calloc(1, sizeof(struct audio_device)); if (!adev) return -ENOMEM; adev->hw_device.common.tag = HARDWARE_DEVICE_TAG; adev->hw_device.common.version = AUDIO_DEVICE_API_VERSION_2_0; adev->hw_device.common.module = (struct hw_module_t *) module; adev->hw_device.common.close = adev_close; adev->hw_device.init_check = adev_init_check; adev->hw_device.set_voice_volume = adev_set_voice_volume; adev->hw_device.set_master_volume = adev_set_master_volume; adev->hw_device.set_mode = adev_set_mode; adev->hw_device.set_mic_mute = adev_set_mic_mute; adev->hw_device.get_mic_mute = adev_get_mic_mute; adev->hw_device.set_parameters = adev_set_parameters; adev->hw_device.get_parameters = adev_get_parameters; adev->hw_device.get_input_buffer_size = adev_get_input_buffer_size; adev->hw_device.open_output_stream = adev_open_output_stream; adev->hw_device.close_output_stream = adev_close_output_stream; adev->hw_device.open_input_stream = adev_open_input_stream; adev->hw_device.close_input_stream = adev_close_input_stream; adev->hw_device.dump = adev_dump; *device = &adev->hw_device.common; ALOGV("%s exit",__func__); return 0; } static struct hw_module_methods_t hal_module_methods = { .open = adev_open, }; struct audio_module HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .module_api_version = AUDIO_MODULE_API_VERSION_0_1, .hal_api_version = HARDWARE_HAL_API_VERSION, .id = AUDIO_HARDWARE_MODULE_ID, .name = "tiny_hdmi audio HW HAL", .author = "The Android Open Source Project", .methods = &hal_module_methods, }, };