| /* |
| * 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. |
| */ |
| |
| #define LOG_TAG "audio_hw_primary" |
| /*#define LOG_NDEBUG 0*/ |
| |
| #include <errno.h> |
| #include <pthread.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/time.h> |
| |
| #include <cutils/log.h> |
| #include <cutils/properties.h> |
| #include <cutils/str_parms.h> |
| |
| #include <hardware/audio.h> |
| #include <hardware/hardware.h> |
| |
| #include <system/audio.h> |
| |
| #include <tinyalsa/asoundlib.h> |
| |
| #include <audio_utils/resampler.h> |
| |
| #include "audio_route.h" |
| |
| #define PCM_CARD 0 |
| #define PCM_CARD_SPDIF 1 |
| #define PCM_TOTAL 2 |
| |
| #define PCM_DEVICE 0 |
| |
| /* duration in ms of volume ramp applied when starting capture to remove plop */ |
| #define CAPTURE_START_RAMP_MS 100 |
| |
| struct pcm_config pcm_config = { |
| .channels = 2, |
| .rate = 44100, |
| .period_size = 512, |
| .period_count = 2, |
| .format = PCM_FORMAT_S16_LE, |
| }; |
| |
| struct audio_device { |
| struct audio_hw_device hw_device; |
| |
| pthread_mutex_t lock; /* see note below on mutex acquisition order */ |
| unsigned int out_device; |
| bool mic_mute; |
| struct audio_route *ar; |
| audio_source_t input_source; |
| int cur_route_id; /* current route ID: combination of input source |
| * and output device IDs */ |
| }; |
| |
| struct stream_out { |
| struct audio_stream_out stream; |
| |
| pthread_mutex_t lock; /* see note below on mutex acquisition order */ |
| struct pcm *pcm[PCM_TOTAL]; |
| bool standby; /* true if all PCMs are inactive */ |
| unsigned int device; |
| |
| struct audio_device *dev; |
| }; |
| |
| struct stream_in { |
| struct audio_stream_in stream; |
| |
| pthread_mutex_t lock; /* see note below on mutex acquisition order */ |
| struct pcm *pcm; |
| bool standby; |
| |
| unsigned int requested_rate; |
| struct resampler_itfe *resampler; |
| struct resampler_buffer_provider buf_provider; |
| int16_t *buffer; |
| size_t frames_in; |
| int read_status; |
| audio_source_t input_source; |
| uint16_t ramp_vol; |
| uint16_t ramp_step; |
| size_t ramp_frames; |
| |
| struct audio_device *dev; |
| }; |
| |
| enum { |
| OUT_DEVICE_SPEAKER, |
| OUT_DEVICE_HEADSET, |
| OUT_DEVICE_HEADPHONES, |
| OUT_DEVICE_BT_SCO, |
| OUT_DEVICE_SPEAKER_AND_HEADSET, |
| OUT_DEVICE_TAB_SIZE, /* number of rows in route_configs[][] */ |
| OUT_DEVICE_NONE, |
| OUT_DEVICE_CNT |
| }; |
| |
| enum { |
| IN_SOURCE_MIC, |
| IN_SOURCE_CAMCORDER, |
| IN_SOURCE_VOICE_RECOGNITION, |
| IN_SOURCE_VOICE_COMMUNICATION, |
| IN_SOURCE_TAB_SIZE, /* number of lines in route_configs[][] */ |
| IN_SOURCE_NONE, |
| IN_SOURCE_CNT |
| }; |
| |
| int get_output_device_id(unsigned int device) |
| { |
| if (device == AUDIO_DEVICE_NONE) |
| return OUT_DEVICE_NONE; |
| |
| if (popcount(device) == 2) { |
| if ((device == (AUDIO_DEVICE_OUT_SPEAKER | |
| AUDIO_DEVICE_OUT_WIRED_HEADSET)) || |
| (device == (AUDIO_DEVICE_OUT_SPEAKER | |
| AUDIO_DEVICE_OUT_WIRED_HEADPHONE))) |
| return OUT_DEVICE_SPEAKER_AND_HEADSET; |
| else |
| return OUT_DEVICE_NONE; |
| } |
| |
| if (popcount(device) != 1) |
| return OUT_DEVICE_NONE; |
| |
| switch (device) { |
| case AUDIO_DEVICE_OUT_SPEAKER: |
| return OUT_DEVICE_SPEAKER; |
| case AUDIO_DEVICE_OUT_WIRED_HEADSET: |
| return OUT_DEVICE_HEADSET; |
| case AUDIO_DEVICE_OUT_WIRED_HEADPHONE: |
| return OUT_DEVICE_HEADPHONES; |
| case AUDIO_DEVICE_OUT_BLUETOOTH_SCO: |
| case AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET: |
| case AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT: |
| return OUT_DEVICE_BT_SCO; |
| default: |
| return OUT_DEVICE_NONE; |
| } |
| } |
| |
| int get_input_source_id(audio_source_t source) |
| { |
| switch (source) { |
| case AUDIO_SOURCE_DEFAULT: |
| return IN_SOURCE_NONE; |
| case AUDIO_SOURCE_MIC: |
| return IN_SOURCE_MIC; |
| case AUDIO_SOURCE_CAMCORDER: |
| return IN_SOURCE_CAMCORDER; |
| case AUDIO_SOURCE_VOICE_RECOGNITION: |
| return IN_SOURCE_VOICE_RECOGNITION; |
| case AUDIO_SOURCE_VOICE_COMMUNICATION: |
| return IN_SOURCE_VOICE_COMMUNICATION; |
| default: |
| return IN_SOURCE_NONE; |
| } |
| } |
| |
| struct route_config { |
| const char * const output_route; |
| const char * const input_route; |
| /* TODO add other properties here: es305 presets... */ |
| }; |
| |
| const struct route_config media_speaker = { |
| "media-speaker", |
| "media-main-mic" |
| }; |
| |
| const struct route_config media_headphones = { |
| "media-headphones", |
| "media-main-mic" |
| }; |
| |
| const struct route_config media_headset = { |
| "media-headphones", |
| "media-headset-mic" |
| }; |
| |
| const struct route_config camcorder_speaker = { |
| "media-speaker", |
| "media-second-mic" |
| }; |
| |
| const struct route_config camcorder_headphones = { |
| "media-headphones", |
| "media-second-mic" |
| }; |
| |
| const struct route_config voice_rec_speaker = { |
| "voice-rec-speaker", |
| "voice-rec-main-mic" |
| }; |
| |
| const struct route_config voice_rec_headphones = { |
| "voice-rec-headphones", |
| "voice-rec-main-mic" |
| }; |
| |
| const struct route_config voice_rec_headset = { |
| "voice-rec-headphones", |
| "voice-rec-headset-mic" |
| }; |
| |
| const struct route_config communication_speaker = { |
| "communication-speaker", |
| "communication-main-mic" |
| }; |
| |
| const struct route_config communication_headphones = { |
| "communication-headphones", |
| "communication-main-mic" |
| }; |
| |
| const struct route_config communication_headset = { |
| "communication-headphones", |
| "communication-headset-mic" |
| }; |
| |
| const struct route_config speaker_and_headphones = { |
| "speaker-and-headphones", |
| "main-mic" |
| }; |
| |
| const struct route_config bluetooth_sco = { |
| "bt-sco-headset", |
| "bt-sco-mic" |
| }; |
| |
| const struct route_config * const route_configs[IN_SOURCE_TAB_SIZE] |
| [OUT_DEVICE_TAB_SIZE] = { |
| { /* IN_SOURCE_MIC */ |
| &media_speaker, /* OUT_DEVICE_SPEAKER */ |
| &media_headset, /* OUT_DEVICE_HEADSET */ |
| &media_headphones, /* OUT_DEVICE_HEADPHONES */ |
| &bluetooth_sco, /* OUT_DEVICE_BT_SCO */ |
| &speaker_and_headphones /* OUT_DEVICE_SPEAKER_AND_HEADSET */ |
| }, |
| { /* IN_SOURCE_CAMCORDER */ |
| &camcorder_speaker, /* OUT_DEVICE_SPEAKER */ |
| &camcorder_headphones, /* OUT_DEVICE_HEADSET */ |
| &camcorder_headphones, /* OUT_DEVICE_HEADPHONES */ |
| &bluetooth_sco, /* OUT_DEVICE_BT_SCO */ |
| &speaker_and_headphones /* OUT_DEVICE_SPEAKER_AND_HEADSET */ |
| }, |
| { /* IN_SOURCE_VOICE_RECOGNITION */ |
| &voice_rec_speaker, /* OUT_DEVICE_SPEAKER */ |
| &voice_rec_headset, /* OUT_DEVICE_HEADSET */ |
| &voice_rec_headphones, /* OUT_DEVICE_HEADPHONES */ |
| &bluetooth_sco, /* OUT_DEVICE_BT_SCO */ |
| &speaker_and_headphones /* OUT_DEVICE_SPEAKER_AND_HEADSET */ |
| }, |
| { /* IN_SOURCE_VOICE_COMMUNICATION */ |
| &communication_speaker, /* OUT_DEVICE_SPEAKER */ |
| &communication_headset, /* OUT_DEVICE_HEADSET */ |
| &communication_headphones, /* OUT_DEVICE_HEADPHONES */ |
| &bluetooth_sco, /* OUT_DEVICE_BT_SCO */ |
| &speaker_and_headphones /* OUT_DEVICE_SPEAKER_AND_HEADSET */ |
| } |
| }; |
| |
| /** |
| * NOTE: when multiple mutexes have to be acquired, always respect the |
| * following order: hw device > in stream > out stream |
| */ |
| |
| /* Helper functions */ |
| |
| static void select_devices(struct audio_device *adev) |
| { |
| int output_device_id = get_output_device_id(adev->out_device); |
| int input_source_id = get_input_source_id(adev->input_source); |
| const char *output_route = NULL; |
| const char *input_route = NULL; |
| int new_route_id; |
| |
| reset_mixer_state(adev->ar); |
| |
| new_route_id = (1 << (input_source_id + OUT_DEVICE_CNT)) + (1 << output_device_id); |
| if (new_route_id == adev->cur_route_id) |
| return; |
| adev->cur_route_id = new_route_id; |
| |
| if (input_source_id != IN_SOURCE_NONE) { |
| if (output_device_id != OUT_DEVICE_NONE) { |
| input_route = |
| route_configs[input_source_id][output_device_id]->input_route; |
| output_route = |
| route_configs[input_source_id][output_device_id]->output_route; |
| } else { |
| input_route = |
| route_configs[input_source_id][OUT_DEVICE_SPEAKER]->input_route; |
| } |
| } else { |
| if (output_device_id != OUT_DEVICE_NONE) { |
| output_route = |
| route_configs[IN_SOURCE_MIC][output_device_id]->output_route; |
| } |
| } |
| |
| ALOGV("select_devices() devices %#x input src %d output route %s input route %s", |
| adev->out_device, adev->input_source, |
| output_route ? output_route : "none", |
| input_route ? input_route : "none"); |
| |
| if (output_route) |
| audio_route_apply_path(adev->ar, output_route); |
| if (input_route) |
| audio_route_apply_path(adev->ar, input_route); |
| |
| update_mixer_state(adev->ar); |
| |
| } |
| |
| /* 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; |
| |
| if (out->device & (AUDIO_DEVICE_OUT_SPEAKER | |
| AUDIO_DEVICE_OUT_WIRED_HEADSET | |
| AUDIO_DEVICE_OUT_WIRED_HEADPHONE | |
| AUDIO_DEVICE_OUT_AUX_DIGITAL)) { |
| out->pcm[PCM_CARD] = pcm_open(PCM_CARD, PCM_DEVICE, |
| PCM_OUT, &pcm_config); |
| |
| if (out->pcm[PCM_CARD] && !pcm_is_ready(out->pcm[PCM_CARD])) { |
| ALOGE("pcm_open(PCM_CARD) failed: %s", |
| pcm_get_error(out->pcm[PCM_CARD])); |
| pcm_close(out->pcm[PCM_CARD]); |
| return -ENOMEM; |
| } |
| } |
| |
| if (out->device & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) { |
| out->pcm[PCM_CARD_SPDIF] = pcm_open(PCM_CARD_SPDIF, PCM_DEVICE, |
| PCM_OUT, &pcm_config); |
| |
| if (out->pcm[PCM_CARD_SPDIF] && |
| !pcm_is_ready(out->pcm[PCM_CARD_SPDIF])) { |
| ALOGE("pcm_open(PCM_CARD_SPDIF) failed: %s", |
| pcm_get_error(out->pcm[PCM_CARD_SPDIF])); |
| pcm_close(out->pcm[PCM_CARD_SPDIF]); |
| return -ENOMEM; |
| } |
| } |
| |
| adev->out_device = out->device; |
| select_devices(adev); |
| |
| return 0; |
| } |
| |
| /* must be called with hw device and input stream mutexes locked */ |
| static int start_input_stream(struct stream_in *in) |
| { |
| struct audio_device *adev = in->dev; |
| |
| in->pcm = pcm_open(PCM_CARD, PCM_DEVICE, PCM_IN, &pcm_config); |
| |
| if (in->pcm && !pcm_is_ready(in->pcm)) { |
| ALOGE("pcm_open() failed: %s", pcm_get_error(in->pcm)); |
| pcm_close(in->pcm); |
| return -ENOMEM; |
| } |
| |
| /* if no supported sample rate is available, use the resampler */ |
| if (in->resampler) |
| in->resampler->reset(in->resampler); |
| |
| in->frames_in = 0; |
| adev->input_source = in->input_source; |
| select_devices(adev); |
| |
| /* initialize volume ramp */ |
| in->ramp_frames = (CAPTURE_START_RAMP_MS * in->requested_rate) / 1000; |
| in->ramp_step = (uint16_t)(USHRT_MAX / in->ramp_frames); |
| in->ramp_vol = 0;; |
| |
| return 0; |
| } |
| |
| |
| static size_t get_input_buffer_size(unsigned int sample_rate, |
| audio_format_t format, |
| unsigned int channel_count) |
| { |
| size_t size; |
| |
| /* |
| * 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_config.period_size * sample_rate) / pcm_config.rate; |
| size = ((size + 15) / 16) * 16; |
| |
| return size * channel_count * audio_bytes_per_sample(format); |
| } |
| |
| static int get_next_buffer(struct resampler_buffer_provider *buffer_provider, |
| struct resampler_buffer* buffer) |
| { |
| struct stream_in *in; |
| size_t i; |
| |
| if (buffer_provider == NULL || buffer == NULL) |
| return -EINVAL; |
| |
| in = (struct stream_in *)((char *)buffer_provider - |
| offsetof(struct stream_in, buf_provider)); |
| |
| if (in->pcm == NULL) { |
| buffer->raw = NULL; |
| buffer->frame_count = 0; |
| in->read_status = -ENODEV; |
| return -ENODEV; |
| } |
| |
| if (in->frames_in == 0) { |
| in->read_status = pcm_read(in->pcm, |
| (void*)in->buffer, |
| pcm_frames_to_bytes(in->pcm, pcm_config.period_size)); |
| 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->frames_in = pcm_config.period_size; |
| |
| /* Do stereo to mono conversion in place by discarding right channel */ |
| for (i = 1; i < in->frames_in; i++) |
| in->buffer[i] = in->buffer[i * 2]; |
| } |
| |
| buffer->frame_count = (buffer->frame_count > in->frames_in) ? |
| in->frames_in : buffer->frame_count; |
| buffer->i16 = in->buffer + (pcm_config.period_size - in->frames_in); |
| |
| 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->frames_in -= 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; |
| size_t frame_size = audio_stream_frame_size(&in->stream.common); |
| |
| while (frames_wr < frames) { |
| size_t frames_rd = frames - frames_wr; |
| if (in->resampler != NULL) { |
| in->resampler->resample_from_provider(in->resampler, |
| (int16_t *)((char *)buffer + |
| frames_wr * frame_size), |
| &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 + |
| frames_wr * frame_size, |
| buf.raw, |
| buf.frame_count * frame_size); |
| 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; |
| } |
| |
| /* API functions */ |
| |
| static uint32_t out_get_sample_rate(const struct audio_stream *stream) |
| { |
| return pcm_config.rate; |
| } |
| |
| static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) |
| { |
| return -ENOSYS; |
| } |
| |
| static size_t out_get_buffer_size(const struct audio_stream *stream) |
| { |
| return pcm_config.period_size * |
| audio_stream_frame_size((struct audio_stream *)stream); |
| } |
| |
| static audio_channel_mask_t out_get_channels(const struct audio_stream *stream) |
| { |
| return AUDIO_CHANNEL_OUT_STEREO; |
| } |
| |
| static audio_format_t out_get_format(const struct audio_stream *stream) |
| { |
| return AUDIO_FORMAT_PCM_16_BIT; |
| } |
| |
| static int out_set_format(struct audio_stream *stream, audio_format_t format) |
| { |
| return -ENOSYS; |
| } |
| |
| static int do_out_standby(struct stream_out *out) |
| { |
| int i; |
| |
| if (!out->standby) { |
| for (i = 0; i < PCM_TOTAL; i++) { |
| if (out->pcm[i]) { |
| pcm_close(out->pcm[i]); |
| out->pcm[i] = NULL; |
| } |
| } |
| |
| out->dev->out_device = AUDIO_DEVICE_NONE; |
| select_devices(out->dev); |
| |
| out->standby = true; |
| } |
| |
| return 0; |
| } |
| |
| static int out_standby(struct audio_stream *stream) |
| { |
| struct stream_out *out = (struct stream_out *)stream; |
| int ret; |
| |
| pthread_mutex_lock(&out->dev->lock); |
| pthread_mutex_lock(&out->lock); |
| |
| ret = do_out_standby(out); |
| |
| pthread_mutex_unlock(&out->lock); |
| pthread_mutex_unlock(&out->dev->lock); |
| |
| return ret; |
| } |
| |
| static int out_dump(const struct audio_stream *stream, int 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; |
| unsigned int val; |
| |
| parms = str_parms_create_str(kvpairs); |
| |
| ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, |
| value, sizeof(value)); |
| pthread_mutex_lock(&adev->lock); |
| pthread_mutex_lock(&out->lock); |
| if (ret >= 0) { |
| val = atoi(value); |
| if ((out->device != val) && (val != 0)) { |
| /* Force standby if moving to/from SPDIF or if the output |
| * device changes when in SPDIF mode */ |
| if (((val & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) ^ |
| (adev->out_device & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET)) || |
| (adev->out_device & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET)) { |
| do_out_standby(out); |
| } |
| |
| out->device = val; |
| if (!out->standby) { |
| adev->out_device = out->device; |
| select_devices(adev); |
| } |
| } |
| } |
| pthread_mutex_unlock(&out->lock); |
| pthread_mutex_unlock(&adev->lock); |
| |
| str_parms_destroy(parms); |
| return ret; |
| } |
| |
| static char * out_get_parameters(const struct audio_stream *stream, const char *keys) |
| { |
| return strdup(""); |
| } |
| |
| static uint32_t out_get_latency(const struct audio_stream_out *stream) |
| { |
| return (pcm_config.period_size * pcm_config.period_count * 1000) / |
| pcm_config.rate; |
| } |
| |
| static int out_set_volume(struct audio_stream_out *stream, float left, |
| float right) |
| { |
| return -ENOSYS; |
| } |
| |
| static ssize_t out_write(struct audio_stream_out *stream, const void* buffer, |
| size_t bytes) |
| { |
| int ret; |
| struct stream_out *out = (struct stream_out *)stream; |
| struct audio_device *adev = out->dev; |
| int i; |
| |
| /* |
| * acquiring hw device mutex systematically is useful if a low |
| * priority thread is waiting on the output stream mutex - e.g. |
| * executing out_set_parameters() while holding the hw device |
| * mutex |
| */ |
| pthread_mutex_lock(&adev->lock); |
| pthread_mutex_lock(&out->lock); |
| if (out->standby) { |
| ret = start_output_stream(out); |
| if (ret != 0) { |
| pthread_mutex_unlock(&adev->lock); |
| goto exit; |
| } |
| out->standby = false; |
| } |
| pthread_mutex_unlock(&adev->lock); |
| |
| /* Write to all active PCMs */ |
| for (i = 0; i < PCM_TOTAL; i++) |
| if (out->pcm[i]) |
| pcm_write(out->pcm[i], (void *)buffer, bytes); |
| |
| exit: |
| pthread_mutex_unlock(&out->lock); |
| |
| if (ret != 0) { |
| usleep(bytes * 1000000 / audio_stream_frame_size(&stream->common) / |
| out_get_sample_rate(&stream->common)); |
| } |
| |
| return bytes; |
| } |
| |
| static int out_get_render_position(const struct audio_stream_out *stream, |
| uint32_t *dsp_frames) |
| { |
| return -EINVAL; |
| } |
| |
| static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) |
| { |
| return 0; |
| } |
| |
| static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) |
| { |
| return 0; |
| } |
| |
| static int out_get_next_write_timestamp(const struct audio_stream_out *stream, |
| int64_t *timestamp) |
| { |
| return -EINVAL; |
| } |
| |
| /** audio_stream_in implementation **/ |
| static uint32_t in_get_sample_rate(const struct audio_stream *stream) |
| { |
| struct stream_in *in = (struct stream_in *)stream; |
| |
| return in->requested_rate; |
| } |
| |
| static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) |
| { |
| return 0; |
| } |
| |
| static audio_channel_mask_t in_get_channels(const struct audio_stream *stream) |
| { |
| return AUDIO_CHANNEL_IN_MONO; |
| } |
| |
| |
| static size_t in_get_buffer_size(const struct audio_stream *stream) |
| { |
| struct stream_in *in = (struct stream_in *)stream; |
| |
| return get_input_buffer_size(in->requested_rate, |
| AUDIO_FORMAT_PCM_16_BIT, |
| popcount(in_get_channels(stream))); |
| } |
| |
| static audio_format_t in_get_format(const struct audio_stream *stream) |
| { |
| return AUDIO_FORMAT_PCM_16_BIT; |
| } |
| |
| static int in_set_format(struct audio_stream *stream, audio_format_t format) |
| { |
| return -ENOSYS; |
| } |
| |
| static int in_standby(struct audio_stream *stream) |
| { |
| struct stream_in *in = (struct stream_in *)stream; |
| |
| pthread_mutex_lock(&in->dev->lock); |
| pthread_mutex_lock(&in->lock); |
| |
| if (!in->standby) { |
| pcm_close(in->pcm); |
| in->pcm = NULL; |
| in->dev->input_source = AUDIO_SOURCE_DEFAULT; |
| select_devices(in->dev); |
| in->standby = true; |
| } |
| |
| pthread_mutex_unlock(&in->lock); |
| pthread_mutex_unlock(&in->dev->lock); |
| |
| return 0; |
| } |
| |
| static int in_dump(const struct audio_stream *stream, int fd) |
| { |
| return 0; |
| } |
| |
| static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) |
| { |
| struct stream_in *in = (struct stream_in *)stream; |
| struct audio_device *adev = in->dev; |
| struct str_parms *parms; |
| char value[32]; |
| int ret; |
| unsigned int val; |
| bool apply_now = false; |
| |
| parms = str_parms_create_str(kvpairs); |
| |
| ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_INPUT_SOURCE, |
| value, sizeof(value)); |
| |
| pthread_mutex_lock(&adev->lock); |
| pthread_mutex_lock(&in->lock); |
| if (ret >= 0) { |
| val = atoi(value); |
| /* no audio source uses val == 0 */ |
| if ((in->input_source != val) && (val != 0)) { |
| in->input_source = val; |
| apply_now = !in->standby; |
| } |
| } |
| |
| if (apply_now) { |
| adev->input_source = in->input_source; |
| select_devices(adev); |
| } |
| |
| pthread_mutex_unlock(&in->lock); |
| pthread_mutex_unlock(&adev->lock); |
| |
| str_parms_destroy(parms); |
| return ret; |
| } |
| |
| static char * in_get_parameters(const struct audio_stream *stream, |
| const char *keys) |
| { |
| return strdup(""); |
| } |
| |
| static int in_set_gain(struct audio_stream_in *stream, float gain) |
| { |
| return 0; |
| } |
| |
| static void in_apply_ramp(struct stream_in *in, int16_t *buffer, size_t frames) |
| { |
| size_t i; |
| uint16_t vol = in->ramp_vol; |
| uint16_t step = in->ramp_step; |
| |
| frames = (frames < in->ramp_frames) ? frames : in->ramp_frames; |
| |
| for (i = 0; i < frames; i++) |
| { |
| buffer[i] = (int16_t)((buffer[i] * vol) >> 16); |
| vol += step; |
| } |
| |
| in->ramp_vol = vol; |
| in->ramp_frames -= frames; |
| } |
| |
| static ssize_t in_read(struct audio_stream_in *stream, void* buffer, |
| size_t bytes) |
| { |
| int ret = 0; |
| struct stream_in *in = (struct stream_in *)stream; |
| struct audio_device *adev = in->dev; |
| size_t frames_rq = bytes / audio_stream_frame_size(&stream->common); |
| |
| /* |
| * acquiring hw device mutex systematically is useful if a low |
| * priority thread is waiting on the input stream mutex - e.g. |
| * executing in_set_parameters() while holding the hw device |
| * mutex |
| */ |
| pthread_mutex_lock(&adev->lock); |
| pthread_mutex_lock(&in->lock); |
| if (in->standby) { |
| ret = start_input_stream(in); |
| if (ret == 0) |
| in->standby = 0; |
| } |
| pthread_mutex_unlock(&adev->lock); |
| |
| if (ret < 0) |
| goto exit; |
| |
| /*if (in->num_preprocessors != 0) |
| ret = process_frames(in, buffer, frames_rq); |
| else */ |
| ret = read_frames(in, buffer, frames_rq); |
| |
| if (ret > 0) |
| ret = 0; |
| |
| if (in->ramp_frames > 0) |
| in_apply_ramp(in, buffer, frames_rq); |
| |
| /* |
| * Instead of writing zeroes here, we could trust the hardware |
| * to always provide zeroes when muted. |
| */ |
| if (ret == 0 && adev->mic_mute) |
| memset(buffer, 0, bytes); |
| |
| exit: |
| if (ret < 0) |
| usleep(bytes * 1000000 / audio_stream_frame_size(&stream->common) / |
| in_get_sample_rate(&stream->common)); |
| |
| pthread_mutex_unlock(&in->lock); |
| return bytes; |
| } |
| |
| static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) |
| { |
| return 0; |
| } |
| |
| static int in_add_audio_effect(const struct audio_stream *stream, |
| effect_handle_t effect) |
| { |
| return 0; |
| } |
| |
| static int in_remove_audio_effect(const struct audio_stream *stream, |
| effect_handle_t effect) |
| { |
| return 0; |
| } |
| |
| |
| 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) |
| { |
| struct audio_device *adev = (struct audio_device *)dev; |
| struct stream_out *out; |
| int ret; |
| |
| out = (struct stream_out *)calloc(1, sizeof(struct stream_out)); |
| if (!out) |
| return -ENOMEM; |
| |
| 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; |
| |
| out->dev = adev; |
| |
| 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; |
| |
| *stream_out = &out->stream; |
| return 0; |
| |
| err_open: |
| free(out); |
| *stream_out = NULL; |
| return ret; |
| } |
| |
| static void adev_close_output_stream(struct audio_hw_device *dev, |
| struct audio_stream_out *stream) |
| { |
| out_standby(&stream->common); |
| free(stream); |
| } |
| |
| static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) |
| { |
| return 0; |
| } |
| |
| static char * adev_get_parameters(const struct audio_hw_device *dev, |
| const char *keys) |
| { |
| return strdup(""); |
| } |
| |
| static int adev_init_check(const struct audio_hw_device *dev) |
| { |
| return 0; |
| } |
| |
| static int adev_set_voice_volume(struct audio_hw_device *dev, float volume) |
| { |
| return -ENOSYS; |
| } |
| |
| static int adev_set_master_volume(struct audio_hw_device *dev, float volume) |
| { |
| return -ENOSYS; |
| } |
| |
| static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode) |
| { |
| return 0; |
| } |
| |
| static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) |
| { |
| struct audio_device *adev = (struct audio_device *)dev; |
| |
| adev->mic_mute = state; |
| |
| return 0; |
| } |
| |
| static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) |
| { |
| struct audio_device *adev = (struct audio_device *)dev; |
| |
| *state = adev->mic_mute; |
| |
| return 0; |
| } |
| |
| static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, |
| const struct audio_config *config) |
| { |
| |
| return get_input_buffer_size(config->sample_rate, config->format, |
| popcount(config->channel_mask)); |
| } |
| |
| 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) |
| { |
| struct audio_device *adev = (struct audio_device *)dev; |
| struct stream_in *in; |
| int ret; |
| |
| *stream_in = NULL; |
| |
| /* Respond with a request for mono if a different format is given. */ |
| if (config->channel_mask != AUDIO_CHANNEL_IN_MONO) { |
| config->channel_mask = AUDIO_CHANNEL_IN_MONO; |
| return -EINVAL; |
| } |
| |
| in = (struct stream_in *)calloc(1, sizeof(struct stream_in)); |
| if (!in) |
| return -ENOMEM; |
| |
| in->stream.common.get_sample_rate = in_get_sample_rate; |
| in->stream.common.set_sample_rate = in_set_sample_rate; |
| in->stream.common.get_buffer_size = in_get_buffer_size; |
| in->stream.common.get_channels = in_get_channels; |
| in->stream.common.get_format = in_get_format; |
| in->stream.common.set_format = in_set_format; |
| in->stream.common.standby = in_standby; |
| in->stream.common.dump = in_dump; |
| in->stream.common.set_parameters = in_set_parameters; |
| in->stream.common.get_parameters = in_get_parameters; |
| in->stream.common.add_audio_effect = in_add_audio_effect; |
| in->stream.common.remove_audio_effect = in_remove_audio_effect; |
| in->stream.set_gain = in_set_gain; |
| in->stream.read = in_read; |
| in->stream.get_input_frames_lost = in_get_input_frames_lost; |
| |
| in->dev = adev; |
| in->standby = true; |
| in->requested_rate = config->sample_rate; |
| in->input_source = AUDIO_SOURCE_DEFAULT; |
| |
| in->buffer = malloc(pcm_config.period_size * pcm_config.channels |
| * audio_stream_frame_size(&in->stream.common)); |
| |
| if (!in->buffer) { |
| ret = -ENOMEM; |
| goto err_malloc; |
| } |
| |
| if (in->requested_rate != pcm_config.rate) { |
| in->buf_provider.get_next_buffer = get_next_buffer; |
| in->buf_provider.release_buffer = release_buffer; |
| |
| ret = create_resampler(pcm_config.rate, |
| in->requested_rate, |
| 1, |
| RESAMPLER_QUALITY_DEFAULT, |
| &in->buf_provider, |
| &in->resampler); |
| if (ret != 0) { |
| ret = -EINVAL; |
| goto err_resampler; |
| } |
| } |
| |
| *stream_in = &in->stream; |
| return 0; |
| |
| err_resampler: |
| free(in->buffer); |
| err_malloc: |
| free(in); |
| return ret; |
| } |
| |
| static void adev_close_input_stream(struct audio_hw_device *dev, |
| struct audio_stream_in *stream) |
| { |
| struct stream_in *in = (struct stream_in *)stream; |
| |
| in_standby(&stream->common); |
| if (in->resampler) { |
| release_resampler(in->resampler); |
| in->resampler = NULL; |
| } |
| free(in->buffer); |
| free(stream); |
| } |
| |
| static int adev_dump(const audio_hw_device_t *device, int fd) |
| { |
| return 0; |
| } |
| |
| static int adev_close(hw_device_t *device) |
| { |
| struct audio_device *adev = (struct audio_device *)device; |
| |
| audio_route_free(adev->ar); |
| |
| free(device); |
| return 0; |
| } |
| |
| static int adev_open(const hw_module_t* module, const char* name, |
| hw_device_t** device) |
| { |
| struct audio_device *adev; |
| int ret; |
| |
| 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; |
| |
| adev->ar = audio_route_init(); |
| adev->input_source = AUDIO_SOURCE_DEFAULT; |
| /* adev->cur_route_id initial value is 0 and such that first device |
| * selection is always applied by select_devices() */ |
| |
| *device = &adev->hw_device.common; |
| |
| 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 = "Manta audio HW HAL", |
| .author = "The Android Open Source Project", |
| .methods = &hal_module_methods, |
| }, |
| }; |