1692 lines
50 KiB
C
Executable file
1692 lines
50 KiB
C
Executable file
/*
|
|
* 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 <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <sys/time.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <dlfcn.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/prctl.h>
|
|
|
|
#include <cutils/log.h>
|
|
#include <cutils/str_parms.h>
|
|
#include <cutils/properties.h>
|
|
#include <cutils/atomic.h>
|
|
#include <cutils/sched_policy.h>
|
|
#include <cutils/list.h>
|
|
#include <unistd.h>
|
|
#include <hardware/audio.h>
|
|
#include <audio_route/audio_route.h>
|
|
#include <audio_utils/resampler.h>
|
|
#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,
|
|
},
|
|
};
|