diff options
author | Aaron Echols <atechols@gmail.com> | 2012-10-10 16:08:18 -0700 |
---|---|---|
committer | Aaron Echols <atechols@gmail.com> | 2012-10-10 16:08:18 -0700 |
commit | 901a04c71f7c9cf2572e7da73ec859c0e8e78cb4 (patch) | |
tree | d63cffeaebdcaa0a5d27f069f4961c8382fc2888 | |
parent | e86decfec11ab03e16fc65e67fce1c042cddd2fe (diff) | |
download | tf700t-901a04c71f7c9cf2572e7da73ec859c0e8e78cb4.tar.gz |
tf700t: switch to opensource audio hal
Change-Id: I7dd8c75ed5df7f32487646d6ba3d825977ed4f90
-rw-r--r-- | audio/Android.mk | 32 | ||||
-rw-r--r-- | audio/audio_hw.c | 1340 | ||||
-rw-r--r-- | audio/audio_route.c | 494 | ||||
-rw-r--r-- | audio/audio_route.h | 33 | ||||
-rw-r--r-- | device_tf700t.mk | 5 | ||||
-rw-r--r-- | prebuilt/mixer_paths.xml | 38 | ||||
-rw-r--r-- | proprietary-files.txt | 2 |
7 files changed, 1942 insertions, 2 deletions
diff --git a/audio/Android.mk b/audio/Android.mk new file mode 100644 index 0000000..5003282 --- /dev/null +++ b/audio/Android.mk @@ -0,0 +1,32 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := audio.primary.cardhu +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw +LOCAL_SRC_FILES := \ + audio_hw.c \ + audio_route.c +LOCAL_C_INCLUDES += \ + external/tinyalsa/include \ + external/expat/lib \ + $(call include-path-for, audio-utils) +LOCAL_SHARED_LIBRARIES := liblog libcutils libtinyalsa libaudioutils libexpat +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) + diff --git a/audio/audio_hw.c b/audio/audio_hw.c new file mode 100644 index 0000000..02ead96 --- /dev/null +++ b/audio/audio_hw.c @@ -0,0 +1,1340 @@ +/* + * 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 <sys/ioctl.h> +#include <fcntl.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 1 +#define PCM_DEVICE 0 +#define PCM_DEVICE_SCO 2 + +#define OUT_PERIOD_SIZE 1024 +#define OUT_SHORT_PERIOD_COUNT 2 +#define OUT_LONG_PERIOD_COUNT 8 +#define OUT_SAMPLING_RATE 44100 + +#define IN_PERIOD_SIZE 1024 +#define IN_PERIOD_COUNT 4 +#define IN_SAMPLING_RATE 44100 + +#define SCO_PERIOD_SIZE 1024 +#define SCO_PERIOD_COUNT 4 +#define SCO_SAMPLING_RATE 44100 + +/* minimum sleep time in out_write() when write threshold is not reached */ +#define MIN_WRITE_SLEEP_US 2000 +#define MAX_WRITE_SLEEP_US ((OUT_PERIOD_SIZE * OUT_SHORT_PERIOD_COUNT * 1000000) \ + / OUT_SAMPLING_RATE) + +#define DSP_DEV_PATH "/dev/dsp_fm34" +#define DSP_IOC_MAGIC 0xf3 +#define DSP_CONTROL _IOW(DSP_IOC_MAGIC, 1,int) +#define START_RECORDING 1 +#define END_RECORDING 0 +#define PLAYBACK 2 + +#define AUDIO_DEV_PATH "/dev/rt5631" +#define AUDIO_IOC_MAGIC 0xf7 +#define AUDIO_CAPTURE_MODE _IOW(AUDIO_IOC_MAGIC, 6,int) +#define INPUT_SOURCE_NORMAL 100 +#define INPUT_SOURCE_AGC 301 +#define OUTPUT_SOURCE_NORMAL 200 + +bool isRecording = false; + +enum { + OUT_BUFFER_TYPE_UNKNOWN, + OUT_BUFFER_TYPE_SHORT, + OUT_BUFFER_TYPE_LONG, +}; + +struct pcm_config pcm_config_out = { + .channels = 2, + .rate = OUT_SAMPLING_RATE, + .period_size = OUT_PERIOD_SIZE, + .period_count = OUT_LONG_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = OUT_PERIOD_SIZE * OUT_SHORT_PERIOD_COUNT, +}; + +struct pcm_config pcm_config_in = { + .channels = 2, + .rate = IN_SAMPLING_RATE, + .period_size = IN_PERIOD_SIZE, + .period_count = IN_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 1, + .stop_threshold = (IN_PERIOD_SIZE * IN_PERIOD_COUNT), +}; + +struct pcm_config pcm_config_sco = { + .channels = 1, + .rate = SCO_SAMPLING_RATE, + .period_size = SCO_PERIOD_SIZE, + .period_count = SCO_PERIOD_COUNT, + .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 devices; + bool standby; + bool mic_mute; + struct audio_route *ar; + int orientation; + bool screen_off; + + struct stream_out *active_out; + struct stream_in *active_in; +}; + +struct stream_out { + struct audio_stream_out stream; + + pthread_mutex_t lock; /* see note below on mutex acquisition order */ + struct pcm *pcm; + struct pcm_config *pcm_config; + bool standby; + + struct resampler_itfe *resampler; + int16_t *buffer; + size_t buffer_frames; + + int write_threshold; + int cur_write_threshold; + int buffer_type; + + 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; + struct pcm_config *pcm_config; + bool standby; + + unsigned int requested_rate; + struct resampler_itfe *resampler; + struct resampler_buffer_provider buf_provider; + int16_t *buffer; + size_t buffer_size; + size_t frames_in; + int read_status; + + struct audio_device *dev; +}; + +enum { + ORIENTATION_LANDSCAPE, + ORIENTATION_PORTRAIT, + ORIENTATION_SQUARE, + ORIENTATION_UNDEFINED, +}; + +static uint32_t out_get_sample_rate(const struct audio_stream *stream); +static size_t out_get_buffer_size(const struct audio_stream *stream); +static audio_format_t out_get_format(const struct audio_stream *stream); +static uint32_t in_get_sample_rate(const struct audio_stream *stream); +static size_t in_get_buffer_size(const struct audio_stream *stream); +static audio_format_t in_get_format(const struct audio_stream *stream); +static int get_next_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer); +static void release_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer); + +/* + * NOTE: when multiple mutexes have to be acquired, always take the + * audio_device mutex first, followed by the stream_in and/or + * stream_out mutexes. + */ + +/* Helper functions */ + +static void select_devices(struct audio_device *adev) +{ + int headphone_on; + int speaker_on; + int main_mic_on; + int fm34_dev, rt5631_dev; + int ret; + + fm34_dev = open(DSP_DEV_PATH,0); + + rt5631_dev = open(AUDIO_DEV_PATH,0); + + if (fm34_dev < 0) + ALOGE("open %s failed", DSP_DEV_PATH); + + if (rt5631_dev < 0) + ALOGE("open %s failed", AUDIO_DEV_PATH); + + headphone_on = adev->devices & (AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE); + speaker_on = adev->devices & AUDIO_DEVICE_OUT_SPEAKER; + main_mic_on = adev->devices & AUDIO_DEVICE_IN_BUILTIN_MIC; + + reset_mixer_state(adev->ar); + + if (speaker_on) { + audio_route_apply_path(adev->ar, "speaker"); + ALOGE("Speaker Is Online"); + + ioctl(fm34_dev, DSP_CONTROL, END_RECORDING); + ioctl(fm34_dev, DSP_CONTROL, PLAYBACK); + ioctl(rt5631_dev, AUDIO_CAPTURE_MODE, OUTPUT_SOURCE_NORMAL); + + } + if (headphone_on){ + audio_route_apply_path(adev->ar, "headphone"); + ALOGE("Headset Is Online"); + + ioctl(fm34_dev, DSP_CONTROL, END_RECORDING); + ioctl(fm34_dev, DSP_CONTROL, PLAYBACK); + ioctl(rt5631_dev, AUDIO_CAPTURE_MODE, OUTPUT_SOURCE_NORMAL); + + } + if (main_mic_on) { + + ALOGE("MIC Is Online"); + + ioctl(fm34_dev, DSP_CONTROL, INPUT_SOURCE_AGC); + ioctl(fm34_dev, DSP_CONTROL, START_RECORDING); + ioctl(rt5631_dev, AUDIO_CAPTURE_MODE, INPUT_SOURCE_AGC); + + if (adev->orientation == ORIENTATION_LANDSCAPE) + audio_route_apply_path(adev->ar, "main-mic-left"); + else + audio_route_apply_path(adev->ar, "main-mic-top"); + } + + update_mixer_state(adev->ar); + + ALOGV("hp=%c speaker=%c main-mic=%c", headphone_on ? 'y' : 'n', + speaker_on ? 'y' : 'n', main_mic_on ? 'y' : 'n'); + + if(fm34_dev > 0) + close(fm34_dev); + + if(rt5631_dev > 0) + close(rt5631_dev); +} + +/* must be called with hw device and output stream mutexes locked */ +static void do_out_standby(struct stream_out *out) +{ + struct audio_device *adev = out->dev; + + if (!out->standby) { + pcm_close(out->pcm); + out->pcm = NULL; + adev->active_out = NULL; + if (out->resampler) { + release_resampler(out->resampler); + out->resampler = NULL; + } + if (out->buffer) { + free(out->buffer); + out->buffer = NULL; + } + out->standby = true; + } +} + +/* must be called with hw device and input stream mutexes locked */ +static void do_in_standby(struct stream_in *in) +{ + struct audio_device *adev = in->dev; + + if (!in->standby) { + pcm_close(in->pcm); + in->pcm = NULL; + adev->active_in = NULL; + if (in->resampler) { + release_resampler(in->resampler); + in->resampler = NULL; + } + if (in->buffer) { + free(in->buffer); + in->buffer = NULL; + } + in->standby = true; + } +} + +/* 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; + unsigned int device; + int ret; + + /* + * Due to the lack of sample rate converters in the SoC, + * it greatly simplifies things to have only the main + * (speaker/headphone) PCM or the BC SCO PCM open at + * the same time. + */ + if (adev->devices & AUDIO_DEVICE_OUT_ALL_SCO) { + device = PCM_DEVICE_SCO; + out->pcm_config = &pcm_config_sco; + } else { + device = PCM_DEVICE; + out->pcm_config = &pcm_config_out; + out->buffer_type = OUT_BUFFER_TYPE_UNKNOWN; + } + + /* + * All open PCMs can only use a single group of rates at once: + * Group 1: 11.025, 22.05, 44.1 + * Group 2: 8, 16, 32, 48 + * Group 1 is used for digital audio playback since 44.1 is + * the most common rate, but group 2 is required for SCO. + */ + if (adev->active_in) { + pthread_mutex_lock(&adev->active_in->lock); + if (((out->pcm_config->rate % 8000 == 0) && + (adev->active_in->pcm_config->rate % 8000) != 0) || + ((out->pcm_config->rate % 11025 == 0) && + (adev->active_in->pcm_config->rate % 11025) != 0)) + do_in_standby(adev->active_in); + pthread_mutex_unlock(&adev->active_in->lock); + } + + out->pcm = pcm_open(PCM_CARD, device, PCM_OUT | PCM_NORESTART, out->pcm_config); + + if (out->pcm && !pcm_is_ready(out->pcm)) { + ALOGE("pcm_open(out) failed: %s", pcm_get_error(out->pcm)); + pcm_close(out->pcm); + return -ENOMEM; + } + + /* + * If the stream rate differs from the PCM rate, we need to + * create a resampler. + */ + if (out_get_sample_rate(&out->stream.common) != out->pcm_config->rate) { + ret = create_resampler(out_get_sample_rate(&out->stream.common), + out->pcm_config->rate, + out->pcm_config->channels, + RESAMPLER_QUALITY_DEFAULT, + NULL, + &out->resampler); + out->buffer_frames = (pcm_config_out.period_size * out->pcm_config->rate) / + out_get_sample_rate(&out->stream.common) + 1; + + out->buffer = malloc(pcm_frames_to_bytes(out->pcm, out->buffer_frames)); + } + + adev->active_out = out; + + 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; + unsigned int device; + int ret; + + /* + * Due to the lack of sample rate converters in the SoC, + * it greatly simplifies things to have only the main + * mic PCM or the BC SCO PCM open at the same time. + */ + if (adev->devices & AUDIO_DEVICE_IN_ALL_SCO) { + device = PCM_DEVICE_SCO; + in->pcm_config = &pcm_config_sco; + } else { + device = PCM_DEVICE; + in->pcm_config = &pcm_config_in; + } + + /* + * All open PCMs can only use a single group of rates at once: + * Group 1: 11.025, 22.05, 44.1 + * Group 2: 8, 16, 32, 48 + * Group 1 is used for digital audio playback since 44.1 is + * the most common rate, but group 2 is required for SCO. + */ + if (adev->active_out) { + pthread_mutex_lock(&adev->active_out->lock); + if (((in->pcm_config->rate % 8000 == 0) && + (adev->active_out->pcm_config->rate % 8000) != 0) || + ((in->pcm_config->rate % 11025 == 0) && + (adev->active_out->pcm_config->rate % 11025) != 0)) + do_out_standby(adev->active_out); + pthread_mutex_unlock(&adev->active_out->lock); + } + + in->pcm = pcm_open(PCM_CARD, device, PCM_IN, in->pcm_config); + + if (in->pcm && !pcm_is_ready(in->pcm)) { + ALOGE("pcm_open(in) failed: %s", pcm_get_error(in->pcm)); + pcm_close(in->pcm); + return -ENOMEM; + } + + /* + * If the stream rate differs from the PCM rate, we need to + * create a resampler. + */ + if (in_get_sample_rate(&in->stream.common) != in->pcm_config->rate) { + in->buf_provider.get_next_buffer = get_next_buffer; + in->buf_provider.release_buffer = release_buffer; + + ret = create_resampler(in->pcm_config->rate, + in_get_sample_rate(&in->stream.common), + 1, + RESAMPLER_QUALITY_DEFAULT, + &in->buf_provider, + &in->resampler); + } + in->buffer_size = pcm_frames_to_bytes(in->pcm, + in->pcm_config->period_size); + in->buffer = malloc(in->buffer_size); + + adev->active_in = in; + + return 0; +} + +static int get_next_buffer(struct resampler_buffer_provider *buffer_provider, + struct resampler_buffer* buffer) +{ + struct stream_in *in; + + 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, + in->buffer_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 = in->pcm_config->period_size; + if (in->pcm_config->channels == 2) { + unsigned int i; + + /* Discard 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 + (in->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; + + 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 * audio_stream_frame_size(&in->stream.common)), + &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 * audio_stream_frame_size(&in->stream.common), + buf.raw, + buf.frame_count * audio_stream_frame_size(&in->stream.common)); + 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_out.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_out.period_size * + audio_stream_frame_size((struct audio_stream *)stream); +} + +static uint32_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 out_standby(struct audio_stream *stream) +{ + struct stream_out *out = (struct stream_out *)stream; + + pthread_mutex_lock(&out->dev->lock); + pthread_mutex_lock(&out->lock); + do_out_standby(out); + pthread_mutex_unlock(&out->lock); + pthread_mutex_unlock(&out->dev->lock); + + return 0; +} + +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); + if (ret >= 0) { + val = atoi(value); + if (((adev->devices & AUDIO_DEVICE_OUT_ALL) != val) && (val != 0)) { + /* + * If SCO is turned on/off, we need to put audio into standby + * because SCO uses a different PCM. + */ + if ((val & AUDIO_DEVICE_OUT_ALL_SCO) ^ + (adev->devices & AUDIO_DEVICE_OUT_ALL_SCO)) { + pthread_mutex_lock(&out->lock); + do_out_standby(out); + pthread_mutex_unlock(&out->lock); + } + + adev->devices &= ~AUDIO_DEVICE_OUT_ALL; + adev->devices |= val; + select_devices(adev); + } + } + 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) +{ + struct stream_out *out = (struct stream_out *)stream; + struct audio_device *adev = out->dev; + size_t period_count; + + pthread_mutex_lock(&adev->lock); + + if (adev->screen_off && !adev->active_in && !(adev->devices & AUDIO_DEVICE_OUT_ALL_SCO)) + period_count = OUT_LONG_PERIOD_COUNT; + else + period_count = OUT_SHORT_PERIOD_COUNT; + + pthread_mutex_unlock(&adev->lock); + + return (pcm_config_out.period_size * period_count * 1000) / pcm_config_out.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 = 0; + struct stream_out *out = (struct stream_out *)stream; + struct audio_device *adev = out->dev; + size_t frame_size = audio_stream_frame_size(&out->stream.common); + int16_t *in_buffer = (int16_t *)buffer; + size_t in_frames = bytes / frame_size; + size_t out_frames; + int buffer_type; + int kernel_frames; + bool sco_on; + + /* + * 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; + } + buffer_type = (adev->screen_off && !adev->active_in) ? + OUT_BUFFER_TYPE_LONG : OUT_BUFFER_TYPE_SHORT; + sco_on = (adev->devices & AUDIO_DEVICE_OUT_ALL_SCO); + pthread_mutex_unlock(&adev->lock); + + /* detect changes in screen ON/OFF state and adapt buffer size + * if needed. Do not change buffer size when routed to SCO device. */ + if (!sco_on && (buffer_type != out->buffer_type)) { + size_t period_count; + + if (buffer_type == OUT_BUFFER_TYPE_LONG) + period_count = OUT_LONG_PERIOD_COUNT; + else + period_count = OUT_SHORT_PERIOD_COUNT; + + out->write_threshold = out->pcm_config->period_size * period_count; + /* reset current threshold if exiting standby */ + if (out->buffer_type == OUT_BUFFER_TYPE_UNKNOWN) + out->cur_write_threshold = out->write_threshold; + out->buffer_type = buffer_type; + } + + /* Reduce number of channels, if necessary */ + if (popcount(out_get_channels(&stream->common)) > + (int)out->pcm_config->channels) { + unsigned int i; + + /* Discard right channel */ + for (i = 1; i < in_frames; i++) + in_buffer[i] = in_buffer[i * 2]; + + /* The frame size is now half */ + frame_size /= 2; + } + + /* Change sample rate, if necessary */ + if (out_get_sample_rate(&stream->common) != out->pcm_config->rate) { + out_frames = out->buffer_frames; + out->resampler->resample_from_input(out->resampler, + in_buffer, &in_frames, + out->buffer, &out_frames); + in_buffer = out->buffer; + } else { + out_frames = in_frames; + } + + if (!sco_on) { + int total_sleep_time_us = 0; + size_t period_size = out->pcm_config->period_size; + + /* do not allow more than out->cur_write_threshold frames in kernel + * pcm driver buffer */ + do { + struct timespec time_stamp; + if (pcm_get_htimestamp(out->pcm, + (unsigned int *)&kernel_frames, + &time_stamp) < 0) + break; + kernel_frames = pcm_get_buffer_size(out->pcm) - kernel_frames; + + if (kernel_frames > out->cur_write_threshold) { + int sleep_time_us = + (int)(((int64_t)(kernel_frames - out->cur_write_threshold) + * 1000000) / out->pcm_config->rate); + if (sleep_time_us < MIN_WRITE_SLEEP_US) + break; + total_sleep_time_us += sleep_time_us; + if (total_sleep_time_us > MAX_WRITE_SLEEP_US) { + ALOGW("out_write() limiting sleep time %d to %d", + total_sleep_time_us, MAX_WRITE_SLEEP_US); + sleep_time_us = MAX_WRITE_SLEEP_US - + (total_sleep_time_us - sleep_time_us); + } + usleep(sleep_time_us); + } + + } while ((kernel_frames > out->cur_write_threshold) && + (total_sleep_time_us <= MAX_WRITE_SLEEP_US)); + + /* do not allow abrupt changes on buffer size. Increasing/decreasing + * the threshold by steps of 1/4th of the buffer size keeps the write + * time within a reasonable range during transitions. + * Also reset current threshold just above current filling status when + * kernel buffer is really depleted to allow for smooth catching up with + * target threshold. + */ + if (out->cur_write_threshold > out->write_threshold) { + out->cur_write_threshold -= period_size / 4; + if (out->cur_write_threshold < out->write_threshold) { + out->cur_write_threshold = out->write_threshold; + } + } else if (out->cur_write_threshold < out->write_threshold) { + out->cur_write_threshold += period_size / 4; + if (out->cur_write_threshold > out->write_threshold) { + out->cur_write_threshold = out->write_threshold; + } + } else if ((kernel_frames < out->write_threshold) && + ((out->write_threshold - kernel_frames) > + (int)(period_size * OUT_SHORT_PERIOD_COUNT))) { + out->cur_write_threshold = (kernel_frames / period_size + 1) * period_size; + out->cur_write_threshold += period_size / 4; + } + } + + ret = pcm_write(out->pcm, in_buffer, out_frames * frame_size); + if (ret == -EPIPE) { + /* In case of underrun, don't sleep since we want to catch up asap */ + pthread_mutex_unlock(&out->lock); + return ret; + } + +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 size_t in_get_buffer_size(const struct audio_stream *stream) +{ + struct stream_in *in = (struct stream_in *)stream; + 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 = (in->pcm_config->period_size * in_get_sample_rate(stream)) / + in->pcm_config->rate; + size = ((size + 15) / 16) * 16; + + return size * audio_stream_frame_size((struct audio_stream *)stream); +} + +static uint32_t in_get_channels(const struct audio_stream *stream) +{ + return AUDIO_CHANNEL_IN_MONO; +} + +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); + do_in_standby(in); + 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; + + parms = str_parms_create_str(kvpairs); + + ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, + value, sizeof(value)); + pthread_mutex_lock(&adev->lock); + if (ret >= 0) { + val = atoi(value); + if (((adev->devices & AUDIO_DEVICE_IN_ALL) != val) && (val != 0)) { + /* + * If SCO is turned on/off, we need to put audio into standby + * because SCO uses a different PCM. + */ + if ((val & AUDIO_DEVICE_IN_ALL_SCO) ^ + (adev->devices & AUDIO_DEVICE_IN_ALL_SCO)) { + pthread_mutex_lock(&in->lock); + do_in_standby(in); + pthread_mutex_unlock(&in->lock); + } + + adev->devices &= ~AUDIO_DEVICE_IN_ALL; + adev->devices |= val; + select_devices(adev); + } + } + 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 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 */if (in->resampler != NULL) { + ret = read_frames(in, buffer, frames_rq); + } else if (in->pcm_config->channels == 2) { + /* + * If the PCM is stereo, capture twice as many frames and + * discard the right channel. + */ + unsigned int i; + int16_t *in_buffer = (int16_t *)buffer; + + ret = pcm_read(in->pcm, in->buffer, bytes * 2); + + /* Discard right channel */ + for (i = 0; i < frames_rq; i++) + in_buffer[i] = in->buffer[i * 2]; + } else { + ret = pcm_read(in->pcm, buffer, bytes); + } + + if (ret > 0) + ret = 0; + + /* + * 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) +{ + struct audio_device *adev = (struct audio_device *)dev; + struct str_parms *parms; + char *str; + char value[32]; + int ret; + + parms = str_parms_create_str(kvpairs); + ret = str_parms_get_str(parms, "orientation", value, sizeof(value)); + if (ret >= 0) { + int orientation; + + if (strcmp(value, "landscape") == 0) + orientation = ORIENTATION_LANDSCAPE; + else if (strcmp(value, "portrait") == 0) + orientation = ORIENTATION_PORTRAIT; + else if (strcmp(value, "square") == 0) + orientation = ORIENTATION_SQUARE; + else + orientation = ORIENTATION_UNDEFINED; + + pthread_mutex_lock(&adev->lock); + if (orientation != adev->orientation) { + adev->orientation = orientation; + /* + * Orientation changes can occur with the input device + * closed so we must call select_devices() here to set + * up the mixer. This is because select_devices() will + * not be called when the input device is opened if no + * other input parameter is changed. + */ + select_devices(adev); + } + pthread_mutex_unlock(&adev->lock); + } + + ret = str_parms_get_str(parms, "screen_state", value, sizeof(value)); + if (ret >= 0) { + if (strcmp(value, AUDIO_PARAMETER_VALUE_ON) == 0) + adev->screen_off = false; + else + adev->screen_off = true; + } + + str_parms_destroy(parms); + return ret; +} + +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) +{ + 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_in.period_size * config->sample_rate) / pcm_config_in.rate; + size = ((size + 15) / 16) * 16; + + return (size * popcount(config->channel_mask) * + audio_bytes_per_sample(config->format)); +} + +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->pcm_config = &pcm_config_in; /* default PCM config */ + + *stream_in = &in->stream; + return 0; +} + +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); + 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 uint32_t adev_get_supported_devices(const struct audio_hw_device *dev) +{ + return (/* OUT */ + AUDIO_DEVICE_OUT_SPEAKER | + AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | + AUDIO_DEVICE_OUT_ALL_SCO | + AUDIO_DEVICE_OUT_DEFAULT | + /* IN */ + AUDIO_DEVICE_IN_BUILTIN_MIC | + AUDIO_DEVICE_IN_WIRED_HEADSET | + AUDIO_DEVICE_IN_BACK_MIC | + AUDIO_DEVICE_IN_ALL_SCO | + AUDIO_DEVICE_IN_DEFAULT); +} + +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_1_0; + adev->hw_device.common.module = (struct hw_module_t *) module; + adev->hw_device.common.close = adev_close; + + adev->hw_device.get_supported_devices = adev_get_supported_devices; + 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->orientation = ORIENTATION_UNDEFINED; + + *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 = "TF201 audio HW HAL", + .author = "The Android Open Source Project", + .methods = &hal_module_methods, + }, +}; diff --git a/audio/audio_route.c b/audio/audio_route.c new file mode 100644 index 0000000..7a13de4 --- /dev/null +++ b/audio/audio_route.c @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * Inspired by TinyHW, written by Mark Brown at Wolfson Micro + * + * 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 <expat.h> +#include <stdbool.h> +#include <stdio.h> + +#include <cutils/log.h> + +#include <tinyalsa/asoundlib.h> + +#define BUF_SIZE 1024 +#define MIXER_XML_PATH "/system/etc/mixer_paths.xml" +#define INITIAL_MIXER_PATH_SIZE 8 + +#define MIXER_CARD 1 + +struct mixer_state { + struct mixer_ctl *ctl; + int old_value; + int new_value; + int reset_value; +}; + +struct mixer_setting { + struct mixer_ctl *ctl; + int value; +}; + +struct mixer_path { + char *name; + unsigned int size; + unsigned int length; + struct mixer_setting *setting; +}; + +struct audio_route { + struct mixer *mixer; + unsigned int num_mixer_ctls; + struct mixer_state *mixer_state; + + unsigned int mixer_path_size; + unsigned int num_mixer_paths; + struct mixer_path *mixer_path; +}; + +struct config_parse_state { + struct audio_route *ar; + struct mixer_path *path; + int level; +}; + +/* path functions */ + +static void path_free(struct audio_route *ar) +{ + unsigned int i; + + for (i = 0; i < ar->num_mixer_paths; i++) { + if (ar->mixer_path[i].name) + free(ar->mixer_path[i].name); + if (ar->mixer_path[i].setting) + free(ar->mixer_path[i].setting); + } + free(ar->mixer_path); +} + +static struct mixer_path *path_get_by_name(struct audio_route *ar, + const char *name) +{ + unsigned int i; + + for (i = 0; i < ar->num_mixer_paths; i++) + if (strcmp(ar->mixer_path[i].name, name) == 0) + return &ar->mixer_path[i]; + + return NULL; +} + +static struct mixer_path *path_create(struct audio_route *ar, const char *name) +{ + struct mixer_path *new_mixer_path = NULL; + + if (path_get_by_name(ar, name)) { + ALOGE("Path name '%s' already exists", name); + return NULL; + } + + /* check if we need to allocate more space for mixer paths */ + if (ar->mixer_path_size <= ar->num_mixer_paths) { + if (ar->mixer_path_size == 0) + ar->mixer_path_size = INITIAL_MIXER_PATH_SIZE; + else + ar->mixer_path_size *= 2; + + new_mixer_path = realloc(ar->mixer_path, ar->mixer_path_size * + sizeof(struct mixer_path)); + if (new_mixer_path == NULL) { + ALOGE("Unable to allocate more paths"); + return NULL; + } else { + ar->mixer_path = new_mixer_path; + } + } + + /* initialise the new mixer path */ + ar->mixer_path[ar->num_mixer_paths].name = strdup(name); + ar->mixer_path[ar->num_mixer_paths].size = 0; + ar->mixer_path[ar->num_mixer_paths].length = 0; + ar->mixer_path[ar->num_mixer_paths].setting = NULL; + + /* return the mixer path just added, then increment number of them */ + return &ar->mixer_path[ar->num_mixer_paths++]; +} + +static bool path_setting_exists(struct mixer_path *path, + struct mixer_setting *setting) +{ + unsigned int i; + + for (i = 0; i < path->length; i++) + if (path->setting[i].ctl == setting->ctl) + return true; + + return false; +} + +static int path_add_setting(struct mixer_path *path, + struct mixer_setting *setting) +{ + struct mixer_setting *new_path_setting; + + if (path_setting_exists(path, setting)) { + ALOGE("Duplicate path setting '%s'", + mixer_ctl_get_name(setting->ctl)); + return -1; + } + + /* check if we need to allocate more space for path settings */ + if (path->size <= path->length) { + if (path->size == 0) + path->size = INITIAL_MIXER_PATH_SIZE; + else + path->size *= 2; + + new_path_setting = realloc(path->setting, + path->size * sizeof(struct mixer_setting)); + if (new_path_setting == NULL) { + ALOGE("Unable to allocate more path settings"); + return -1; + } else { + path->setting = new_path_setting; + } + } + + /* initialise the new path setting */ + path->setting[path->length].ctl = setting->ctl; + path->setting[path->length].value = setting->value; + path->length++; + + return 0; +} + +static int path_add_path(struct mixer_path *path, struct mixer_path *sub_path) +{ + unsigned int i; + + for (i = 0; i < sub_path->length; i++) + if (path_add_setting(path, &sub_path->setting[i]) < 0) + return -1; + + return 0; +} + +static void path_print(struct mixer_path *path) +{ + unsigned int i; + + ALOGV("Path: %s, length: %d", path->name, path->length); + for (i = 0; i < path->length; i++) + ALOGV(" %d: %s -> %d", i, mixer_ctl_get_name(path->setting[i].ctl), + path->setting[i].value); +} + +static int path_apply(struct audio_route *ar, struct mixer_path *path) +{ + unsigned int i; + unsigned int j; + + for (i = 0; i < path->length; i++) { + struct mixer_ctl *ctl = path->setting[i].ctl; + + /* locate the mixer ctl in the list */ + for (j = 0; j < ar->num_mixer_ctls; j++) { + if (ar->mixer_state[j].ctl == ctl) + break; + } + + /* apply the new value */ + ar->mixer_state[j].new_value = path->setting[i].value; + } + + return 0; +} + +/* mixer helper function */ +static int mixer_enum_string_to_value(struct mixer_ctl *ctl, const char *string) +{ + unsigned int i; + + /* Search the enum strings for a particular one */ + for (i = 0; i < mixer_ctl_get_num_enums(ctl); i++) { + if (strcmp(mixer_ctl_get_enum_string(ctl, i), string) == 0) + break; + } + + return i; +} + +static void start_tag(void *data, const XML_Char *tag_name, + const XML_Char **attr) +{ + const XML_Char *attr_name = NULL; + const XML_Char *attr_value = NULL; + struct config_parse_state *state = data; + struct audio_route *ar = state->ar; + unsigned int i; + struct mixer_ctl *ctl; + int value; + struct mixer_setting mixer_setting; + + /* Get name, type and value attributes (these may be empty) */ + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "name") == 0) + attr_name = attr[i + 1]; + else if (strcmp(attr[i], "value") == 0) + attr_value = attr[i + 1]; + } + + /* Look at tags */ + if (strcmp(tag_name, "path") == 0) { + if (attr_name == NULL) { + ALOGE("Unnamed path!"); + } else { + if (state->level == 1) { + /* top level path: create and stash the path */ + state->path = path_create(ar, (char *)attr_name); + } else { + /* nested path */ + struct mixer_path *sub_path = path_get_by_name(ar, attr_name); + path_add_path(state->path, sub_path); + } + } + } + + else if (strcmp(tag_name, "ctl") == 0) { + /* Obtain the mixer ctl and value */ + ctl = mixer_get_ctl_by_name(ar->mixer, attr_name); + switch (mixer_ctl_get_type(ctl)) { + case MIXER_CTL_TYPE_BOOL: + case MIXER_CTL_TYPE_INT: + value = atoi((char *)attr_value); + break; + case MIXER_CTL_TYPE_ENUM: + value = mixer_enum_string_to_value(ctl, (char *)attr_value); + break; + default: + value = 0; + break; + } + + if (state->level == 1) { + /* top level ctl (initial setting) */ + + /* locate the mixer ctl in the list */ + for (i = 0; i < ar->num_mixer_ctls; i++) { + if (ar->mixer_state[i].ctl == ctl) + break; + } + + /* apply the new value */ + ar->mixer_state[i].new_value = value; + } else { + /* nested ctl (within a path) */ + mixer_setting.ctl = ctl; + mixer_setting.value = value; + path_add_setting(state->path, &mixer_setting); + } + } + + state->level++; +} + +static void end_tag(void *data, const XML_Char *tag_name) +{ + struct config_parse_state *state = data; + + state->level--; +} + +static int alloc_mixer_state(struct audio_route *ar) +{ + unsigned int i; + + ar->num_mixer_ctls = mixer_get_num_ctls(ar->mixer); + ar->mixer_state = malloc(ar->num_mixer_ctls * sizeof(struct mixer_state)); + if (!ar->mixer_state) + return -1; + + for (i = 0; i < ar->num_mixer_ctls; i++) { + ar->mixer_state[i].ctl = mixer_get_ctl(ar->mixer, i); + /* only get value 0, assume multiple ctl values are the same */ + ar->mixer_state[i].old_value = mixer_ctl_get_value(ar->mixer_state[i].ctl, 0); + ar->mixer_state[i].new_value = ar->mixer_state[i].old_value; + } + + return 0; +} + +static void free_mixer_state(struct audio_route *ar) +{ + free(ar->mixer_state); + ar->mixer_state = NULL; +} + +void update_mixer_state(struct audio_route *ar) +{ + unsigned int i; + unsigned int j; + + for (i = 0; i < ar->num_mixer_ctls; i++) { + /* if the value has changed, update the mixer */ + if (ar->mixer_state[i].old_value != ar->mixer_state[i].new_value) { + /* set all ctl values the same */ + for (j = 0; j < mixer_ctl_get_num_values(ar->mixer_state[i].ctl); j++) + mixer_ctl_set_value(ar->mixer_state[i].ctl, j, + ar->mixer_state[i].new_value); + ar->mixer_state[i].old_value = ar->mixer_state[i].new_value; + } + } +} + +/* saves the current state of the mixer, for resetting all controls */ +static void save_mixer_state(struct audio_route *ar) +{ + unsigned int i; + + for (i = 0; i < ar->num_mixer_ctls; i++) { + /* only get value 0, assume multiple ctl values are the same */ + ar->mixer_state[i].reset_value = mixer_ctl_get_value(ar->mixer_state[i].ctl, 0); + } +} + +/* this resets all mixer settings to the saved values */ +void reset_mixer_state(struct audio_route *ar) +{ + unsigned int i; + + /* load all of the saved values */ + for (i = 0; i < ar->num_mixer_ctls; i++) + ar->mixer_state[i].new_value = ar->mixer_state[i].reset_value; +} + +void audio_route_apply_path(struct audio_route *ar, const char *name) +{ + struct mixer_path *path; + + if (!ar) { + ALOGE("invalid audio_route"); + return; + } + + path = path_get_by_name(ar, name); + if (!path) { + ALOGE("unable to find path '%s'", name); + return; + } + + path_apply(ar, path); +} + +struct audio_route *audio_route_init(void) +{ + struct config_parse_state state; + XML_Parser parser; + FILE *file; + int bytes_read; + void *buf; + int i; + struct mixer_path *path; + struct audio_route *ar; + + ar = calloc(1, sizeof(struct audio_route)); + if (!ar) + goto err_calloc; + + ar->mixer = mixer_open(MIXER_CARD); + if (!ar->mixer) { + ALOGE("Unable to open the mixer, aborting."); + goto err_mixer_open; + } + + ar->mixer_path = NULL; + ar->mixer_path_size = 0; + ar->num_mixer_paths = 0; + + /* allocate space for and read current mixer settings */ + if (alloc_mixer_state(ar) < 0) + goto err_mixer_state; + + file = fopen(MIXER_XML_PATH, "r"); + if (!file) { + ALOGE("Failed to open %s", MIXER_XML_PATH); + goto err_fopen; + } + + parser = XML_ParserCreate(NULL); + if (!parser) { + ALOGE("Failed to create XML parser"); + goto err_parser_create; + } + + memset(&state, 0, sizeof(state)); + state.ar = ar; + XML_SetUserData(parser, &state); + XML_SetElementHandler(parser, start_tag, end_tag); + + for (;;) { + buf = XML_GetBuffer(parser, BUF_SIZE); + if (buf == NULL) + goto err_parse; + + bytes_read = fread(buf, 1, BUF_SIZE, file); + if (bytes_read < 0) + goto err_parse; + + if (XML_ParseBuffer(parser, bytes_read, + bytes_read == 0) == XML_STATUS_ERROR) { + ALOGE("Error in mixer xml (%s)", MIXER_XML_PATH); + goto err_parse; + } + + if (bytes_read == 0) + break; + } + + /* apply the initial mixer values, and save them so we can reset the + mixer to the original values */ + update_mixer_state(ar); + save_mixer_state(ar); + + XML_ParserFree(parser); + fclose(file); + return ar; + +err_parse: + XML_ParserFree(parser); +err_parser_create: + fclose(file); +err_fopen: + free_mixer_state(ar); +err_mixer_state: + mixer_close(ar->mixer); +err_mixer_open: + free(ar); + ar = NULL; +err_calloc: + return NULL; +} + +void audio_route_free(struct audio_route *ar) +{ + free_mixer_state(ar); + mixer_close(ar->mixer); + free(ar); +} diff --git a/audio/audio_route.h b/audio/audio_route.h new file mode 100644 index 0000000..31355dd --- /dev/null +++ b/audio/audio_route.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef AUDIO_ROUTE_H +#define AUDIO_ROUTE_H + +/* Initialises and frees the audio routes */ +struct audio_route *audio_route_init(void); +void audio_route_free(struct audio_route *ar); + +/* Applies an audio route path by name */ +void audio_route_apply_path(struct audio_route *ar, const char *name); + +/* Resets the mixer back to its initial state */ +void reset_mixer_state(struct audio_route *ar); + +/* Updates the mixer with any changed values */ +void update_mixer_state(struct audio_route *ar); + +#endif diff --git a/device_tf700t.mk b/device_tf700t.mk index 5780a5b..19399a4 100644 --- a/device_tf700t.mk +++ b/device_tf700t.mk @@ -35,6 +35,7 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/prebuilt/cpu.sh:system/bin/cpu.sh \ $(LOCAL_PATH)/prebuilt/vold.fstab:system/etc/vold.fstab \ $(LOCAL_PATH)/prebuilt/gpsconfig.xml:system/etc/gps/gpsconfig.xml + $(LOCAL_PATH)/prebuilt/mixer_paths.xml:system/etc/mixer_paths.xml # Input device configuration files PRODUCT_COPY_FILES += \ @@ -85,10 +86,14 @@ PRODUCT_PACKAGES += \ com.android.future.usb.accessory \ make_ext4fs \ setup_fs \ + audio.primary.cardhu \ audio.a2dp.default \ audio.usb.default \ libtinyalsa \ libaudioutils \ + tinymix \ + tinyplay \ + tinyrec \ libinvensense_mpl \ AutoParts_tfp \ blobpack_tfp \ diff --git a/prebuilt/mixer_paths.xml b/prebuilt/mixer_paths.xml new file mode 100644 index 0000000..548db7f --- /dev/null +++ b/prebuilt/mixer_paths.xml @@ -0,0 +1,38 @@ +<mixer> + <!-- These are the initial mixer settings --> + <ctl name="DMIC Mute Left" value="1" /> + <ctl name="DMIC Mute Right " value="1" /> + <ctl name="Int Spk Switch" value="0" /> + <ctl name="DMIC Capture Switch" value="0" /> + <ctl name="Int Mic Switch" value="0" /> + + <path name="speaker"> + <ctl name="DMIC Mute Left" value="0" /> + <ctl name="DMIC Mute Right" value="0" /> + <ctl name="Int Spk Switch" value="1" /> + </path> + + <path name="headphone"> + <ctl name="DMIC Mute Left" value="0" /> + <ctl name="DMIC Mute Right" value="0" /> + <ctl name="Int Spk Switch" value="0" /> + <ctl name="Headphone Jack Switch" value="1" /> + </path> + + <path name="main-mic-top"> + <ctl name="DMIC Capture Switch" value="1" /> + <ctl name="DMIC Mute Left" value="0" /> + <ctl name="DMIC Mute Right" value="0" /> + <ctl name="Int Mic Switch" value="1" /> + </path> + + <path name="main-mic-left"> + <ctl name="DMIC Capture Switch" value="1" /> + <ctl name="DMIC Mute Left" value="0" /> + <ctl name="DMIC Mute Right" value="0" /> + <ctl name="Int Mic Switch" value="1" /> + </path> + + <path name="headset-mic"> + </path> +</mixer> diff --git a/proprietary-files.txt b/proprietary-files.txt index f5ed64e..a04c4e8 100644 --- a/proprietary-files.txt +++ b/proprietary-files.txt @@ -65,8 +65,6 @@ lib/egl/libGLESv1_CM_tegra.so lib/egl/libGLESv2_perfhud.so lib/egl/libGLESv2_tegra_impl.so lib/egl/libGLESv2_tegra.so -lib/hw/audio_policy.tegra.so -lib/hw/audio.primary.tegra.so lib/hw/camera.tegra.so lib/hw/gps.tegra.so lib/hw/gralloc.tegra.so |