/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "audio_hw_primary" //#define LOG_NDEBUG 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tinyalsa/asoundlib.h" #include "audio_hw.h" #include "platform.h" #define UNUSED(x) (void)(x) #define USE_RESAMPLER 1 /* debug flag */ int AUDIO_HAL_DEBUG = 0; inline int update_debug_flag() { char val[PROPERTY_VALUE_MAX]; property_get("debug.audio.hal_debug", val, "0"); if (!strcmp(val, "0")) { AUDIO_HAL_DEBUG = 0; } else { AUDIO_HAL_DEBUG = 1; } return AUDIO_HAL_DEBUG; } /* audio debug log */ #ifndef ADLOG #define ADLOG(...) \ ALOGD_IF(AUDIO_HAL_DEBUG, __VA_ARGS__) #endif /* changed when the stream is opened */ struct pcm_config out_pcm_config = { .channels = DEFAULT_CHANNEL_COUNT, .rate = DEFAULT_OUTPUT_SAMPLING_RATE, .period_size = DEFAULT_OUTPUT_PERIOD_SIZE, .period_count = DEFAULT_OUTPUT_PERIOD_COUNT, .format = PCM_FORMAT_S16_LE, }; /* changed when the stream is opened */ struct pcm_config in_pcm_config = { .channels = DEFAULT_CHANNEL_COUNT, .rate = DEFAULT_INPUT_SAMPLING_RATE, .period_size = DEFAULT_INPUT_PERIOD_SIZE, .period_count = DEFAULT_INPUT_PERIOD_COUNT, .format = PCM_FORMAT_S16_LE, }; static int do_out_standby(struct sunxi_stream_out *out); static int do_in_standby(struct sunxi_stream_in *in); #ifdef USE_RESAMPLER struct resampler_itfe *in_resampler; struct resampler_itfe *out_resampler; struct resampler_buffer_provider in_buf_provider; void *out_buffer; void *in_buffer; struct sunxi_stream_in *resamp_stream_in; size_t in_frames_in; static int get_next_buffer(struct resampler_buffer_provider *buffer_provider, struct resampler_buffer* buffer) { struct sunxi_stream_in *in = resamp_stream_in; size_t frame_size = audio_stream_in_frame_size(&in->stream); int read_status = 0; if (buffer_provider == NULL || buffer == NULL) return -EINVAL; if (in->pcm == NULL) { buffer->raw = NULL; buffer->frame_count = 0; return -ENODEV; } if (in_frames_in == 0) { read_status = pcm_read(in->pcm, in_buffer, in->config.period_size * frame_size); if (read_status) { ALOGE("get_next_buffer() pcm_read error %d, %s", read_status, strerror(errno)); buffer->raw = NULL; buffer->frame_count = 0; return read_status; } in_frames_in = in->config.period_size; } buffer->frame_count = (buffer->frame_count > in_frames_in) ? in_frames_in : buffer->frame_count; buffer->i16 = (short*)in_buffer + (in->config.period_size - in_frames_in) * in->config.channels; return read_status; } static void release_buffer(struct resampler_buffer_provider *buffer_provider, struct resampler_buffer* buffer) { UNUSED(buffer_provider); if (buffer == NULL) return; 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 sunxi_stream_in *in, void *buffer, ssize_t frames) { ssize_t frames_wr = 0; size_t frame_size = audio_stream_in_frame_size(&in->stream); int read_status = 0; resamp_stream_in = in; while (frames_wr < frames) { size_t frames_rd = frames - frames_wr; if (in_resampler) { 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, }; read_status = 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 (read_status) return read_status; frames_wr += frames_rd; } return frames_wr; } #endif //USE_RESAMPLER static inline void print_sunxi_audio_device(const struct sunxi_audio_device *adev) { if (!adev) { ADLOG("can't print sunxi_audio_device:adev == NULL"); return; } ADLOG("print sunxi_audio_device:\n" "active_input:%p\n" "active_output:%p\n" "out_devices:%#x\n" "in_devices:%#x\n" "platform:%p\n" "mode:%#x\n" "mic_muted:%d\n", adev->active_input, adev->active_output, adev->out_devices, adev->in_devices, adev->platform, adev->mode, adev->mic_muted); } static inline void print_sunxi_stream_out(const struct sunxi_stream_out *out) { if (!out) { ADLOG("can't print sunxi_stream_out:out == NULL"); return; } ADLOG("print sunxi_stream_out:\n" "standby:%d\n" "sample_rate:%d\n" "channel_mask:%#x\n" "format:%#x\n" "devices:%#x\n" "flags:%#x\n" "muted:%d\n" "card:%d\n" "port:%d\n" "pcm:%p\n" "dev:%p\n", out->standby, out->sample_rate, out->channel_mask, out->format, out->devices, out->flags, out->muted, out->card, out->port, out->pcm, out->dev); ADLOG("print out config:\n" "channels:%d\n" "rate:%d\n" "period_size:%d\n" "period_count:%d\n" "format:%#x\n", (int)out->config.channels, (int)out->config.rate, (int)out->config.period_size, (int)out->config.period_count, out->config.format); } static inline void print_sunxi_stream_in(const struct sunxi_stream_in *in) { if (!in) { ADLOG("can't print sunxi_stream_in:in == NULL"); return; } ADLOG("print sunxi_stream_in:\n" "standby:%d\n" "sample_rate:%d\n" "channel_mask:%#x\n" "format:%#x\n" "devices:%#x\n" "flags:%#x\n" "muted:%d\n" "card:%d\n" "port:%d\n" "pcm:%p\n" "dev:%p\n", in->standby, in->sample_rate, in->channel_mask, in->format, in->devices, in->flags, in->muted, in->card, in->port, in->pcm, in->dev); ADLOG("print in config:\n" "channels:%d\n" "rate:%d\n" "period_size:%d\n" "period_count:%d\n" "format:%#x\n", (int)in->config.channels, (int)in->config.rate, (int)in->config.period_size, (int)in->config.period_count, in->config.format); } static uint32_t out_get_sample_rate(const struct audio_stream *stream) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; ALOGV("out_set_sample_rate: %d", out->sample_rate); return out->sample_rate; } static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) { UNUSED(stream); UNUSED(rate); ALOGV("out_set_sample_rate: %d", 0); return -ENOSYS; } static size_t out_get_buffer_size(const struct audio_stream *stream) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; return out->config.period_size * audio_stream_out_frame_size((const struct audio_stream_out *)stream); } static audio_channel_mask_t out_get_channels(const struct audio_stream *stream) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; ALOGV("out_get_channels: %#x", out->channel_mask); char val[PROPERTY_VALUE_MAX]; property_get("vts.native_server.on", val, "0"); if (strcmp(val, "0") == 0) { return out->channel_mask; } else { return out->channel_mask_vts; } } static audio_format_t out_get_format(const struct audio_stream *stream) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; ALOGV("out_get_format: %#x", out->format); return out->format; } static int out_set_format(struct audio_stream *stream, audio_format_t format) { UNUSED(stream); ALOGV("out_set_format: %#x",format); return -ENOSYS; } /* must be called with hw device and output stream mutexes locked */ static int do_out_standby(struct sunxi_stream_out *out) { if (!out->standby) { if (out->pcm) { pcm_close(out->pcm); out->pcm = NULL; } /* audio dump data close*/ close_dump_flags(&out->dd_write_out); out->standby = true; out->dev->active_output = NULL; } return 0; } static int out_standby(struct audio_stream *stream) { ALOGD("out_standby"); struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; struct sunxi_audio_device *adev = out->dev; pthread_mutex_lock(&out->lock); pthread_mutex_lock(&adev->lock); do_out_standby(out); pthread_mutex_unlock(&adev->lock); pthread_mutex_unlock(&out->lock); platform_plugins_process(adev->platform, ON_OUT_STANDBY); return 0; } static int out_dump(const struct audio_stream *stream, int fd) { ALOGV("out_dump"); struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; dprintf(fd, "\tout_dump:\n" "\t\tstandby:%d\n" "\t\tdevices:%#x\n" "\t\tdev:%p\n" "\t\tflags:%#x\n" "\t\tmuted:%d\n" "\t\twritten:%ld\n" "\t\tformat:%#x\n" "\t\tchannel_mask:%#x\n" "\t\tsample_rate:%d\n" "\t\tcard:%d\n" "\t\tport:%d\n" "\t\tpcm:%p\n", out->standby, out->devices, out->dev, out->flags, out->muted, (long int)out->written, out->format, out->channel_mask, out->sample_rate, out->card, out->port, out->pcm); /* dump stream_out pcm_config */ dprintf(fd, "\t\tstream_out pcm_config dump:\n" "\t\t\tavail_min:%d\n" "\t\t\tchannels:%d\n" "\t\t\tformat:%#x\n" "\t\t\tperiod_count:%d\n" "\t\t\tperiod_size:%d\n" "\t\t\trate:%d\n" "\t\t\tsilence_threshold:%d\n" "\t\t\tstart_threshold:%d\n" "\t\t\tstop_threshold:%d\n", out->config.avail_min, out->config.channels, out->config.format, out->config.period_count, out->config.period_size, out->config.rate, out->config.silence_threshold, out->config.start_threshold, out->config.stop_threshold); return 0; } static void select_devices(struct sunxi_audio_device *adev) { ALOGV("select_devices"); char phone_path[50] = "null"; char out_path[50] = "null"; char in_path[50] = "null"; char pdev_name[50] =""; int phone_pdev = 0; int out_pdev = 0; int in_pdev = 0; ADLOG("select_devices:mode(%#x),out_devices(%#x),in_devices(%#x)," "active_output(%p),active_input(%p).", adev->mode, adev->out_devices, adev->in_devices, adev->active_output, adev->active_input); disable_platform_backend_pcm(adev->platform, PCM_OUT); disable_platform_backend_pcm(adev->platform, PCM_IN); if (adev->active_input || adev->active_output || adev->mode == AUDIO_MODE_IN_CALL) { reset_platform_path(adev->platform); } /* select phone device */ if (adev->mode == AUDIO_MODE_IN_CALL) { phone_pdev = get_platform_phone_device(adev->out_devices, adev->platform); get_platform_path(phone_path, adev->platform, phone_pdev); apply_platform_path(adev->platform, phone_path); ALOGD("select device(phone):pdev:%s, path:%s", pdev2str(pdev_name, phone_pdev), phone_path); } /* select output device */ if (adev->active_output) { out_pdev = get_platform_device(adev->mode, adev->out_devices, adev->platform); get_platform_path(out_path, adev->platform, out_pdev); apply_platform_path(adev->platform, out_path); ALOGD("select device(out):pdev:%s, path:%s", pdev2str(pdev_name, out_pdev), out_path); } /* select input device */ if (adev->active_input) { in_pdev = get_platform_device(adev->mode, adev->in_devices, adev->platform); get_platform_path(in_path, adev->platform, in_pdev); apply_platform_path(adev->platform, in_path); ALOGD("select device(in):pdev:%s, path:%s", pdev2str(pdev_name, in_pdev), in_path); } if (adev->active_input || adev->active_output || adev->mode == AUDIO_MODE_IN_CALL) { update_platform_path(adev->platform); } if (phone_pdev) { enable_platform_backend_pcm(phone_pdev, adev->platform, PCM_OUT); enable_platform_backend_pcm(phone_pdev, adev->platform, PCM_IN); } if (out_pdev) enable_platform_backend_pcm(out_pdev, adev->platform, PCM_OUT); if (in_pdev) enable_platform_backend_pcm(in_pdev, adev->platform, PCM_IN); platform_plugins_process_select_devices(adev->platform, ON_SELECT_DEVICES, adev->mode, adev->out_devices, adev->in_devices); } static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; struct sunxi_audio_device *adev = out->dev; struct str_parms *parms; char value[32]; int val; int ret = 0; ALOGD("%s:kvpairs: %s", __func__, kvpairs); parms = str_parms_create_str(kvpairs); /* stream out routing */ ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); if (ret >= 0) { val = atoi(value); pthread_mutex_lock(&adev->lock); pthread_mutex_lock(&out->lock); if (adev->out_devices != val && val) { /* force standby if moving to/from HDMI, BT SCO */ int dev_mask = AUDIO_DEVICE_OUT_AUX_DIGITAL | AUDIO_DEVICE_OUT_ALL_SCO; if ((adev->out_devices & dev_mask) || (val & dev_mask)) { do_out_standby(out); } adev->out_devices = val; select_devices(adev); } pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&adev->lock); } /* TODO: process other parameters settings */ str_parms_destroy(parms); return 0; } static char * out_get_parameters(const struct audio_stream *stream, const char *keys) { UNUSED(stream); UNUSED(keys); ALOGV("out_get_parameters:%s", keys); return strdup(""); } static uint32_t out_get_latency(const struct audio_stream_out *stream) { ALOGV("out_get_latency"); struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; return (out->config.period_count * out->config.period_size * 1000) / (out->config.rate); } static int out_set_volume(struct audio_stream_out *stream, float left, float right) { UNUSED(stream); ALOGV("out_set_volume: Left:%f Right:%f", left, right); return 0; } int start_output_stream(struct sunxi_stream_out *out) { ALOGD("start_output_stream"); int ret = 0; struct sunxi_audio_device *adev = out->dev; int platform_device; /* audio dump data init */ init_dump_flags(true, &out->dd_write_out); adev->active_output = out; select_devices(adev); platform_device = get_platform_device(adev->mode, adev->out_devices, adev->platform); get_platform_snd_card_config(&out->card, &out->port, &out->config, platform_device, adev->platform); update_debug_flag(); print_sunxi_stream_out(out); #ifdef USE_RESAMPLER if (out->sample_rate != out->config.rate) { if (out_resampler) { release_resampler(out_resampler); out_resampler = NULL; } ret = create_resampler(out->sample_rate, out->config.rate, 2, RESAMPLER_QUALITY_DEFAULT, NULL, &out_resampler); if (ret != 0) { ALOGE("create out resampler(%d->%d) failed.", out->sample_rate, out->config.rate); return ret; } else { ALOGD("create out resampler(%d->%d) ok.", out->sample_rate, out->config.rate); } if (out_resampler) out_resampler->reset(out_resampler); if (out_buffer) free(out_buffer); out_buffer = calloc(1, out->config.period_size * 8 * 4); if (!out_buffer) { ALOGE("can't calloc out_buffer"); return -1; } } #endif platform_plugins_process_start_stream(adev->platform, ON_START_OUTPUT_STREAM, out->config); ADLOG("+++++++++++++++ start_output_stream: pcm sample_rate: %d,pcm fmt: 0x%08x,pcm channels: %d", out->config.rate, out->config.format, out->config.channels); out->pcm = pcm_open(out->card, out->port, PCM_OUT | PCM_MONOTONIC, &out->config); if (!pcm_is_ready(out->pcm)) { ALOGE("cannot open pcm_out driver: %s", pcm_get_error(out->pcm)); pcm_close(out->pcm); adev->active_output = NULL; return -ENOMEM; } return ret; } static ssize_t out_write(struct audio_stream_out *stream, const void* buffer, size_t bytes) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; struct sunxi_audio_device *adev = out->dev; void *buf = (void*)buffer; size_t frame_size = audio_stream_out_frame_size(stream); size_t out_frames = bytes / frame_size; ssize_t ret = 0; ALOGV("out_write"); pthread_mutex_lock(&out->lock); if (out->standby) { out->standby = 0; pthread_mutex_lock(&adev->lock); ret = start_output_stream(out); pthread_mutex_unlock(&adev->lock); } #ifdef USE_RESAMPLER if (out_resampler) { size_t in_frames = bytes / frame_size; out_frames = in_frames * 8; out_resampler->resample_from_input(out_resampler,(int16_t *)buffer, &in_frames, (int16_t *)out_buffer, &out_frames); buf = out_buffer; } #endif platform_plugins_process_read_write(adev->platform, ON_OUT_WRITE, out->config, (void*)buffer, out_frames * frame_size); /* audio dump data write */ debug_dump_data(buf, out_frames * frame_size, &out->dd_write_out); /* write audio data to kernel */ if (out->pcm) { if (out->muted) memset(buf, 0, bytes); ret = pcm_write(out->pcm, buf, out_frames * frame_size); if (ret == 0) out->written += bytes / (out->config.channels * sizeof(short)); } exit: pthread_mutex_unlock(&out->lock); if (ret != 0) { if (out->pcm) ALOGE("%s: error %zu - %s", __func__, ret, pcm_get_error(out->pcm)); out_standby(&out->stream.common); usleep(bytes * 1000000 / audio_stream_out_frame_size(stream) / out_get_sample_rate(&out->stream.common)); } return bytes; } static int out_get_render_position(const struct audio_stream_out *stream, uint32_t *dsp_frames) { UNUSED(stream); *dsp_frames = 0; ALOGV("out_get_render_position: dsp_frames: %p", dsp_frames); return 0; } static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { UNUSED(stream); ALOGV("out_add_audio_effect: %p", effect); return 0; } static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { UNUSED(stream); ALOGV("out_remove_audio_effect: %p", effect); return 0; } static int out_get_next_write_timestamp(const struct audio_stream_out *stream, int64_t *timestamp) { UNUSED(stream); *timestamp = 0; //ALOGV("out_get_next_write_timestamp: %ld", (long int)(*timestamp)); return 0; } static int out_get_presentation_position(const struct audio_stream_out *stream, uint64_t *frames, struct timespec *timestamp) { struct sunxi_stream_out *out = (struct sunxi_stream_out *)stream; int ret = -1; pthread_mutex_lock(&out->lock); int i; /* There is a question how to implement this correctly when there is more * than one PCM stream. * We are just interested in the frames pending for playback in the kernel * buffer here, not the total played since start. * The current behavior should be safe because the cases where both cards * are active are marginal. */ if (out->pcm) { size_t avail; if (pcm_get_htimestamp(out->pcm, &avail, timestamp) == 0) { size_t kernel_buffer_size = out->config.period_size * out->config.period_count; /* FIXME This calculation is incorrect if there is buffering after * app processor */ int64_t signed_frames = out->written - kernel_buffer_size + avail; /* It would be unusual for this value to be negative, but check * just in case ... */ if (signed_frames >= 0) { *frames = signed_frames; ret = 0; } } } pthread_mutex_unlock(&out->lock); return ret; } /** audio_stream_in implementation **/ static uint32_t in_get_sample_rate(const struct audio_stream *stream) { struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; ALOGV("in_get_sample_rate: %d", in->sample_rate); return in->sample_rate; } static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) { UNUSED(stream); ALOGV("in_set_sample_rate: %d", rate); return -ENOSYS; } static size_t in_get_buffer_size(const struct audio_stream *stream) { ALOGV("in_get_buffer_size"); struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; size_t size = 0; /* 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->config.period_size * in->sample_rate) / in->config.rate; size = ((size + 15) / 16) * 16; return size * in->config.channels * sizeof(short); } static audio_channel_mask_t in_get_channels(const struct audio_stream *stream) { ALOGV("in_get_channels"); struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; return in->channel_mask; } static audio_format_t in_get_format(const struct audio_stream *stream) { UNUSED(stream); ALOGV("in_get_format"); return AUDIO_FORMAT_PCM_16_BIT; } static int in_set_format(struct audio_stream *stream, audio_format_t format) { UNUSED(stream); UNUSED(format); ALOGV("in_set_format"); return -ENOSYS; } static int do_in_standby(struct sunxi_stream_in *in) { if (!in->standby) { in->standby = true; if (in->pcm) { pcm_close(in->pcm); in->pcm = NULL; } /* audio dump data close*/ close_dump_flags(&in->dd_read_in); } return 0; } static int in_standby(struct audio_stream *stream) { struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; struct sunxi_audio_device *adev = in->dev; int status = 0; ALOGD("%s: enter", __func__); pthread_mutex_lock(&in->lock); pthread_mutex_lock(&adev->lock); do_in_standby(in); pthread_mutex_unlock(&adev->lock); pthread_mutex_unlock(&in->lock); platform_plugins_process(adev->platform, ON_IN_STANDBY); ALOGV("%s: exit: status(%d)", __func__, status); return status; } static int in_dump(const struct audio_stream *stream, int fd) { ALOGV("in_dump"); struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; dprintf(fd, "\tin_dump:\n" "\t\tstandby:%d\n" "\t\tdevices:%#x\n" "\t\tdev:%p\n" "\t\tflags:%#x\n" "\t\tmuted:%d\n" "\t\tformat:%#x\n" "\t\tchannel_mask:%#x\n" "\t\tsample_rate:%d\n" "\t\tframes_read:%ld\n" "\t\tcard:%d\n" "\t\tport:%d\n" "\t\tpcm:%p\n", in->standby, in->devices, in->dev, in->flags, in->muted, in->format, in->channel_mask, in->sample_rate, (long int)in->frames_read, in->card, in->port, in->pcm); /* dump stream_in pcm_config */ dprintf(fd, "\t\tstream_in pcm_config dump:\n" "\t\t\tavail_min:%d\n" "\t\t\tchannels:%d\n" "\t\t\tformat:%#x\n" "\t\t\tperiod_count:%d\n" "\t\t\tperiod_size:%d\n" "\t\t\trate:%d\n" "\t\t\tsilence_threshold:%d\n" "\t\t\tstart_threshold:%d\n" "\t\t\tstop_threshold:%d\n", in->config.avail_min, in->config.channels, in->config.format, in->config.period_count, in->config.period_size, in->config.rate, in->config.silence_threshold, in->config.start_threshold, in->config.stop_threshold ); return 0; } static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) { struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; struct sunxi_audio_device *adev = in->dev; struct str_parms *parms; char *str; char value[32]; int ret, val = 0; int status = 0; ALOGD("%s: enter: kvpairs=%s", __func__, kvpairs); parms = str_parms_create_str(kvpairs); /* in stream routing */ ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); pthread_mutex_lock(&in->lock); pthread_mutex_lock(&adev->lock); if (ret >= 0) { val = atoi(value); if ((adev->in_devices != val) && (val != 0)) { adev->in_devices = val; select_devices(adev); } } pthread_mutex_unlock(&adev->lock); pthread_mutex_unlock(&in->lock); /*TODO: process other parameters setting */ str_parms_destroy(parms); ALOGV("%s: exit: status(%d)", __func__, status); return status; } static char * in_get_parameters(const struct audio_stream *stream, const char *keys) { UNUSED(stream); UNUSED(keys); ALOGV("start_input_stream"); return strdup(""); } static int in_set_gain(struct audio_stream_in *stream, float gain) { UNUSED(stream); UNUSED(gain); ALOGV("in_set_gain"); return 0; } int start_input_stream(struct sunxi_stream_in *in) { ALOGD("start_input_stream"); int ret = 0; struct sunxi_audio_device *adev = in->dev; int platform_device; /* audio dump data init */ init_dump_flags(false, &in->dd_read_in); adev->active_input = in; adev->in_devices = in->devices; select_devices(adev); platform_device = get_platform_device(adev->mode, adev->in_devices, adev->platform); get_platform_snd_card_config(&in->card, &in->port, &in->config, platform_device, adev->platform); update_debug_flag(); print_sunxi_stream_in(in); ADLOG("+++++++++++++++ start_input_stream: pcm sample_rate: %d,pcm fmt: 0x%08x,pcm channels: %d", in->config.rate, in->config.format, in->config.channels); in->pcm = pcm_open(in->card, in->port, PCM_IN | PCM_MONOTONIC, &in->config); if (!pcm_is_ready(in->pcm)) { ALOGE("cannot open pcm_in driver: %s", pcm_get_error(in->pcm)); pcm_close(in->pcm); adev->active_input = NULL; return -ENOMEM; } #ifdef USE_RESAMPLER if (in->sample_rate != in->config.rate) { if (in_resampler) { release_resampler(in_resampler); in_resampler = NULL; } in_buf_provider.get_next_buffer = get_next_buffer; in_buf_provider.release_buffer = release_buffer; ret = create_resampler(in->config.rate, in->sample_rate, in->config.channels, RESAMPLER_QUALITY_DEFAULT, &in_buf_provider, &in_resampler); if (ret != 0) { ALOGE("create in resampler(%d->%d) failed.", in->config.rate, in->sample_rate); if (in_resampler) { release_resampler(in_resampler); in_resampler = NULL; return -1; } } else { ALOGD("create in resampler(%d->%d) ok.", in->config.rate, in->sample_rate); if (in_resampler) { in_resampler->reset(in_resampler); in_frames_in = 0; } } if(in_resampler) in_resampler->reset(in_resampler); if (in_buffer) free(in_buffer); in_buffer = calloc(1, in->config.period_size * audio_stream_in_frame_size(&in->stream) * 8); if (!in_buffer) { ALOGE("can't calloc in_buffer"); return -1; } } #endif platform_plugins_process_start_stream(adev->platform, ON_START_INPUT_STREAM, in->config); return ret; } static ssize_t in_read(struct audio_stream_in *stream, void* buffer, size_t bytes) { struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; struct sunxi_audio_device *adev = in->dev; size_t frames = bytes / audio_stream_in_frame_size(stream); int i; int ret = -1; ALOGV("in_read"); pthread_mutex_lock(&in->lock); if (in->standby) { pthread_mutex_lock(&adev->lock); ret = start_input_stream(in); pthread_mutex_unlock(&adev->lock); if (ret != 0) { goto exit; } in->standby = 0; } #ifdef USE_RESAMPLER if (in_resampler && in->pcm) { ret = read_frames(in, buffer, frames); ret = ret>0 ? 0:ret; } else if (!in_resampler && in->pcm) { ret = pcm_read(in->pcm, buffer, bytes); } #else if (in->pcm) { ret = pcm_read(in->pcm, buffer, bytes); } #endif platform_plugins_process_read_write(adev->platform, ON_IN_READ, in->config, buffer, bytes); /* audio dump data write */ debug_dump_data(buffer, bytes, &in->dd_read_in); if (ret == 0 && adev->mic_muted) memset(buffer, 0, bytes); exit: pthread_mutex_unlock(&in->lock); if (ret != 0) { in_standby(&in->stream.common); ALOGV("%s: read failed - sleeping for buffer duration", __func__); usleep(bytes * 1000000 / audio_stream_in_frame_size(stream) / in_get_sample_rate(&in->stream.common)); } if (bytes > 0) { in->frames_read += bytes / audio_stream_in_frame_size(stream); } return bytes; } static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) { UNUSED(stream); return 0; } static int in_get_capture_position(const struct audio_stream_in *stream, int64_t *frames, int64_t *time) { if (stream == NULL || frames == NULL || time == NULL) { return -EINVAL; } struct sunxi_stream_in *in = (struct sunxi_stream_in *)stream; int ret = -ENOSYS; pthread_mutex_lock(&in->lock); if (in->pcm) { struct timespec timestamp; unsigned int avail; if (pcm_get_htimestamp(in->pcm, &avail, ×tamp) == 0) { *frames = in->frames_read + avail; *time = timestamp.tv_sec * 1000000000LL + timestamp.tv_nsec; ret = 0; } } pthread_mutex_unlock(&in->lock); return ret; } static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { UNUSED(stream); UNUSED(effect); ALOGV("in_add_audio_effect"); return 0; } static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { UNUSED(stream); UNUSED(effect); ALOGV("in_remove_audio_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, const char *address __unused) { UNUSED(handle); ALOGD("adev_open_output_stream"); struct sunxi_audio_device *adev = (struct sunxi_audio_device *)dev; struct sunxi_stream_out *out; int ret; out = (struct sunxi_stream_out *)calloc(1, sizeof(struct sunxi_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->stream.get_presentation_position = NULL;//out_get_presentation_position; *stream_out = &out->stream; out->standby = true; out->flags = flags; out->devices = devices; out->dev = adev; out->card = 0; out->port = 0; /* audio data dump */ out->dd_write_out.file = NULL; out->dd_write_out.enable_flags = false; if (AUDIO_DEVICE_OUT_AUX_DIGITAL & devices) { out->format = AUDIO_FORMAT_PCM_16_BIT; get_platform_snd_card_config(&out->card, &out->port, &out->config, OUT_HDMI, adev->platform); out->sample_rate = out->config.rate; if (1 == out->config.channels) { out->channel_mask = AUDIO_CHANNEL_OUT_MONO; } else { out->channel_mask = AUDIO_CHANNEL_OUT_STEREO; } } else { out->format = config->format; out->sample_rate = config->sample_rate; ALOGV("+++++++++++++++ channel_mask: add out-get-channel_mask for vts!!"); out->channel_mask_vts = config->channel_mask; out->channel_mask = AUDIO_CHANNEL_OUT_STEREO; } memcpy(&out->config, &out_pcm_config, sizeof(struct pcm_config)); /* FIXME: when we support multiple output devices, we will want to * do the following: * adev->out_device = out->device; * select_output_device(adev); * This is because out_set_parameters() with a route is not * guaranteed to be called after an output stream is opened. */ 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); ADLOG("+++++++++++++++ adev_open_output_stream: req_sample_rate: %d, fmt: 0x%08x, channel_mask: 0x%08x", config->sample_rate, config->format, config->channel_mask); platform_plugins_process(adev->platform, ON_OPEN_OUTPUT_STREAM); print_sunxi_stream_out(out); return 0; err_open: ALOGE("%s: err_open", __func__); free(out); *stream_out = NULL; return ret; } static void adev_close_output_stream(struct audio_hw_device *dev, struct audio_stream_out *stream) { ALOGV("adev_close_output_stream..."); struct sunxi_audio_device *adev = (struct sunxi_audio_device*)dev; platform_plugins_process(adev->platform, ON_CLOSE_OUTPUT_STREAM); adev->active_output = NULL; free(stream); } static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) { ALOGD("adev_set_parameters, %s", kvpairs); struct sunxi_audio_device *adev = (struct sunxi_audio_device *)dev; struct sunxi_stream_out *out = adev->active_output; struct str_parms *parms; char value[32]; int val; int ret = 0; parms = str_parms_create_str(kvpairs); /* stream out routing */ ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); if (ret >= 0) { val = atoi(value); pthread_mutex_lock(&adev->lock); pthread_mutex_lock(&out->lock); if (adev->out_devices != val && val) { /* force standby if moving to/from HDMI, BT SCO */ int dev_mask = AUDIO_DEVICE_OUT_AUX_DIGITAL | AUDIO_DEVICE_OUT_ALL_SCO; if ((adev->out_devices & dev_mask) || (val & dev_mask)) { do_out_standby(out); } adev->out_devices = val; select_devices(adev); } pthread_mutex_unlock(&out->lock); pthread_mutex_unlock(&adev->lock); } /* TODO: process other parameters settings */ str_parms_destroy(parms); return 0; } static char * adev_get_parameters(const struct audio_hw_device *dev, const char *keys) { UNUSED(dev); UNUSED(keys); ALOGV("adev_set_parameters, %s", keys); return 0; } static int adev_init_check(const struct audio_hw_device *dev) { UNUSED(dev); ALOGV("adev_init_check"); return 0; } static int adev_set_voice_volume(struct audio_hw_device *dev, float volume) { UNUSED(dev); ALOGV("adev_set_voice_volume: %f", volume); return 0; } static int adev_set_master_volume(struct audio_hw_device *dev, float volume) { UNUSED(dev); ALOGV("adev_set_master_volume: %f", volume); return -ENOSYS; } static int adev_get_master_volume(struct audio_hw_device *dev, float *volume) { UNUSED(dev); ALOGV("adev_get_master_volume: %f", *volume); return -ENOSYS; } static int adev_set_master_mute(struct audio_hw_device *dev, bool muted) { UNUSED(dev); ALOGV("adev_set_master_mute: %d", muted); return -ENOSYS; } static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted) { UNUSED(dev); ALOGV("adev_get_master_mute: %d", *muted); return -ENOSYS; } static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode) { ALOGV("adev_set_mode: %d", mode); struct sunxi_audio_device *adev = (struct sunxi_audio_device *)dev; pthread_mutex_lock(&adev->lock); if (adev->mode != mode) { adev->mode = mode; select_devices(adev); } pthread_mutex_unlock(&adev->lock); return 0; } static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) { ALOGV("%s: state %d\n", __func__, state); struct sunxi_audio_device *adev = (struct sunxi_audio_device *)dev; pthread_mutex_lock(&adev->lock); adev->mic_muted = state; pthread_mutex_unlock(&adev->lock); return 0; } static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) { ALOGV("adev_get_mic_mute"); struct sunxi_audio_device *adev = (struct sunxi_audio_device*)dev; *state = adev->mic_muted; return 0; } static int check_input_parameters(uint32_t sample_rate, audio_format_t format, int channel_count) { if (format != AUDIO_FORMAT_PCM_16_BIT) { ALOGE("%s: unsupported AUDIO FORMAT (%d) ", __func__, format); return -EINVAL; } if ((channel_count < 1) || (channel_count > 2)) { ALOGE("%s: unsupported channel count (%d)", __func__, channel_count); return -EINVAL; } switch (sample_rate) { case 8000: case 11025: case 12000: case 16000: case 22050: case 24000: case 32000: case 44100: case 48000: break; default: ALOGE("%s: unsupported (%d) samplerate", __func__, sample_rate); return -EINVAL; } return 0; } static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format, int channel_count) { size_t size = 0; if (check_input_parameters(sample_rate, format, channel_count) != 0) return 0; /* 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 */ /* TODO: use platform pcm_config config !!!!!!!! */ size = (in_pcm_config.period_size * sample_rate) / in_pcm_config.rate; size = ((size + 15) / 16) * 16; return size * channel_count * sizeof(short); } static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, const struct audio_config *config) { UNUSED(dev); ALOGV("adev_get_input_buffer_size"); size_t size; int channel_count = popcount(config->channel_mask); if (check_input_parameters(config->sample_rate, config->format, channel_count)) return 0; return get_input_buffer_size(config->sample_rate, config->format, channel_count); } 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 __unused, const char *address __unused, audio_source_t source __unused) { UNUSED(handle); ALOGD("adev_open_input_stream..."); struct sunxi_audio_device *adev = (struct sunxi_audio_device *)dev; struct sunxi_stream_in *in; int channel_count = audio_channel_count_from_in_mask(config->channel_mask); int ret; in = (struct sunxi_stream_in *)calloc(1, sizeof(struct sunxi_stream_in)); if (!in) return -ENOMEM; /* 1. init input stream common func */ 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->stream.get_capture_position = in_get_capture_position; /* 2. init other parameters */ in->standby = true; in->card = 0; in->port = 0; in->devices = devices; in->channel_mask = config->channel_mask; in->sample_rate = config->sample_rate; in->format = config->format; in->dev = adev; /* audio data dump */ in->dd_read_in.file = NULL; in->dd_read_in.enable_flags = false; memcpy(&in->config, &in_pcm_config, sizeof(struct pcm_config)); in->config.channels = channel_count; ADLOG("+++++++++++++++ adev_open_input_stream: req_sample_rate: %d, fmt: 0x%08x, channel_mask: 0x%08x", config->sample_rate, config->format, config->channel_mask); *stream_in = &in->stream; platform_plugins_process(adev->platform, ON_OPEN_INPUT_STREAM); print_sunxi_stream_in(in); return 0; err_open: ALOGE("%s:err_open", __func__); free(in); *stream_in = NULL; return ret; } static void adev_close_input_stream(struct audio_hw_device *dev, struct audio_stream_in *in) { ALOGV("adev_close_input_stream..."); struct sunxi_audio_device *adev = (struct sunxi_audio_device*)dev; adev->active_input = NULL; platform_plugins_process(adev->platform, ON_CLOSE_INPUT_STREAM); free(in); } static int adev_dump(const audio_hw_device_t *device, int fd) { ALOGV("adev_dump"); struct sunxi_audio_device *adev = (struct sunxi_audio_device*)device; dprintf(fd, "\tadev_dump:\n" "\t\tactive_input:%p\n" "\t\tactive_output:%p\n" "\t\tmode:%#x\n" "\t\tin_devices:%#x\n" "\t\tout_devices:%#x\n" "\t\tmic_muted:%d\n", adev->active_input, adev->active_output, adev->mode, adev->in_devices, adev->out_devices, adev->mic_muted); /* dump out stream */ if (adev->active_output) { out_dump((const struct audio_stream *)adev->active_output, fd); } /* dump in stream */ if (adev->active_input) { in_dump((const struct audio_stream *)adev->active_input, fd); } /* dump platform */ if (adev->platform) { platform_dump((const struct platform *)adev->platform, fd); } return 0; } static int adev_close(hw_device_t *device) { ALOGV("adev_close"); struct sunxi_audio_device *adev = (struct sunxi_audio_device*)device; platform_plugins_process(adev->platform, ON_ADEV_CLOSE); platform_exit(adev->platform); free(adev); return 0; } static int adev_open(const hw_module_t* module, const char* name, hw_device_t** device) { ALOGD("adev_open: %s", name); struct sunxi_audio_device *adev; int ret; if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) return -EINVAL; adev = calloc(1, sizeof(struct sunxi_audio_device)); if (!adev) { ALOGE("can't calloc sunxi_audio_device"); return -ENOMEM; } adev->device.common.tag = HARDWARE_DEVICE_TAG; adev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0; adev->device.common.module = (struct hw_module_t *) module; adev->device.common.close = adev_close; adev->device.init_check = adev_init_check; adev->device.set_voice_volume = adev_set_voice_volume; adev->device.set_master_volume = adev_set_master_volume; adev->device.get_master_volume = adev_get_master_volume; adev->device.set_master_mute = adev_set_master_mute; adev->device.get_master_mute = adev_get_master_mute; adev->device.set_mode = adev_set_mode; adev->device.set_mic_mute = adev_set_mic_mute; adev->device.get_mic_mute = adev_get_mic_mute; adev->device.set_parameters = adev_set_parameters; adev->device.get_parameters = adev_get_parameters; adev->device.get_input_buffer_size = adev_get_input_buffer_size; adev->device.open_output_stream = adev_open_output_stream; adev->device.close_output_stream = adev_close_output_stream; adev->device.open_input_stream = adev_open_input_stream; adev->device.close_input_stream = adev_close_input_stream; adev->device.dump = adev_dump; *device = &adev->device.common; update_debug_flag(); ADLOG("%s: hal debug enable", __func__); /* load platform config */ adev->platform = platform_init(); print_sunxi_audio_device(adev); /* plugins process */ platform_plugins_process(adev->platform, ON_ADEV_OPEN); 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 = 2, .hal_api_version = 0, .id = AUDIO_HARDWARE_MODULE_ID, .name = "SUNXI Audio HAL", .author = "ywx@Allwinnertech", .methods = &hal_module_methods, }, };