TingAlsa在android source code的目录为external/tinyalsa。
具体编译脚本如下,使用mmm external/tinyalsa/ 命令可以build出
一个动态库libtinyslas.os,提供接口给audio_hw调用;
三个可执行文件tinyplay/tinymix/tinycap,提供给开发者的调试工具;
tinymix: 查看配置混音器
tinyplay: 播放音频
tinycap: 录音
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_C_INCLUDES:= external/tinyalsa/include LOCAL_SRC_FILES:= mixer.c pcm.c LOCAL_MODULE := libtinyalsa LOCAL_SHARED_LIBRARIES:= libcutils libutils LOCAL_MODULE_TAGS := optional LOCAL_PRELINK_MODULE := false include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_C_INCLUDES:= external/tinyalsa/include LOCAL_SRC_FILES:= tinyplay.c LOCAL_MODULE := tinyplay LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_C_INCLUDES:= external/tinyalsa/include LOCAL_SRC_FILES:= tinycap.c LOCAL_MODULE := tinycap LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_C_INCLUDES:= external/tinyalsa/include LOCAL_SRC_FILES:= tinymix.c LOCAL_MODULE := tinymix LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_C_INCLUDES:= external/tinyalsa/include LOCAL_SRC_FILES:= tinypcminfo.c LOCAL_MODULE := tinypcminfo LOCAL_SHARED_LIBRARIES:= libcutils libutils libtinyalsa LOCAL_MODULE_TAGS := optional include $(BUILD_EXECUTABLE)对于audio hal层的实现(不同厂商,不同平台实现各不相同),以QCOM为例,
Android.mk文件中会依赖TinyAlas的动态库libtinyalsa和libtinycompress,
最后生成audio.primary.xxxx.so
ifeq ($(strip $(BOARD_USES_ALSA_AUDIO)),true) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_ARM_MODE := arm AUDIO_PLATFORM := $(TARGET_BOARD_PLATFORM) ifneq ($(filter msm8974 msm8226 msm8084,$(TARGET_BOARD_PLATFORM)),) # B-family platform uses msm8974 code base AUDIO_PLATFORM = msm8974 ifneq ($(filter msm8226,$(TARGET_BOARD_PLATFORM)),) LOCAL_CFLAGS := -DPLATFORM_MSM8x26 endif ifneq ($(filter msm8084,$(TARGET_BOARD_PLATFORM)),) LOCAL_CFLAGS := -DPLATFORM_MSM8084 endif endif LOCAL_SRC_FILES := \ audio_hw.c \ voice.c \ platform_info.c \ audio_extn/ext_speaker.c \ $(AUDIO_PLATFORM)/platform.c LOCAL_SHARED_LIBRARIES := \ liblog \ libcutils \ libtinyalsa \ libtinycompress \ libaudioroute \ libdl \ libexpat LOCAL_C_INCLUDES += \ external/tinyalsa/include \ external/tinycompress/include \</span> $(call include-path-for, audio-route) \ $(call include-path-for, audio-effects) \ $(LOCAL_PATH)/$(AUDIO_PLATFORM) \ $(LOCAL_PATH)/audio_extn \ $(LOCAL_PATH)/voice_extn \ external/expat/lib ifeq ($(strip $(AUDIO_FEATURE_ENABLED_MULTI_VOICE_SESSIONS)),true) LOCAL_CFLAGS += -DMULTI_VOICE_SESSION_ENABLED LOCAL_SRC_FILES += voice_extn/voice_extn.c endif ifeq ($(strip $(AUDIO_FEATURE_ENABLED_HFP)),true) LOCAL_CFLAGS += -DHFP_ENABLED LOCAL_SRC_FILES += audio_extn/hfp.c endif LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM) LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_MODULE_TAGS := optional include $(BUILD_SHARED_LIBRARY) endif
/* * PCM API */ /* Open and close a stream */ struct pcm *pcm_open(unsigned int card, unsigned int device, unsigned int flags, struct pcm_config *config); int pcm_close(struct pcm *pcm); int pcm_is_ready(struct pcm *pcm); /* Obtain the parameters for a PCM */ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, unsigned int flags); /* Write data to the fifo. * Will start playback on the first write or on a write that * occurs after a fifo underrun. */ int pcm_write(struct pcm *pcm, const void *data, unsigned int count); int pcm_read(struct pcm *pcm, void *data, unsigned int count); /* * mmap() support. */ int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count); int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count); int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, unsigned int *frames); int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames); /* Prepare the PCM substream to be triggerable */ int pcm_prepare(struct pcm *pcm); /* Start and stop a PCM channel that doesn't transfer data */ int pcm_start(struct pcm *pcm); int pcm_stop(struct pcm *pcm); /* Interrupt driven API */ int pcm_wait(struct pcm *pcm, int timeout); /*以下是TinyAlsa提供的MIX相关的API,具体文件在tinyalsa/asoundlib.h中。
/* * MIXER API */ /* Open and close a mixer */ struct mixer *mixer_open(unsigned int card); void mixer_close(struct mixer *mixer); /* Get info about a mixer */ const char *mixer_get_name(struct mixer *mixer); /* Obtain mixer controls */ unsigned int mixer_get_num_ctls(struct mixer *mixer); struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id); struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name); /* Some sound cards update their controls due to external events, * such as HDMI EDID byte data changing when an HDMI cable is * connected. This API allows the count of elements to be updated. */ void mixer_ctl_update(struct mixer_ctl *ctl); /* Set and get mixer controls */ int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id); int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent); int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id); int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count); int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value); int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count); int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string);
下面是QCOM中audio_hw.h和audio_hw.c的具体实现,
#include <hardware/audio.h> #include <tinyalsa/asoundlib.h> #include <tinycompress/tinycompress.h>
static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, size_t bytes) { struct stream_out *out = (struct stream_out *)stream; struct audio_device *adev = out->dev; ssize_t ret = 0; pthread_mutex_lock(&out->lock); if (out->standby) { out->standby = false; pthread_mutex_lock(&adev->lock); ret = start_output_stream(out); pthread_mutex_unlock(&adev->lock); /* ToDo: If use case is compress offload should return 0 */ if (ret != 0) { out->standby = true; goto exit; } } if (out->usecase == USECASE_AUDIO_PLAYBACK_OFFLOAD) { ALOGVV("%s: writing buffer (%d bytes) to compress device", __func__, bytes); if (out->send_new_metadata) { ALOGVV("send new gapless metadata"); compress_set_gapless_metadata(out->compr, &out->gapless_mdata); out->send_new_metadata = 0; } ret = compress_write(out->compr, buffer, bytes); ALOGVV("%s: writing buffer (%d bytes) to compress device returned %d", __func__, bytes, ret); if (ret >= 0 && ret < (ssize_t)bytes) { send_offload_cmd_l(out, OFFLOAD_CMD_WAIT_FOR_BUFFER); } if (!out->playback_started) { compress_start(out->compr); out->playback_started = 1; out->offload_state = OFFLOAD_STATE_PLAYING; } pthread_mutex_unlock(&out->lock); return ret; } else { if (out->pcm) { if (out->muted) memset((void *)buffer, 0, bytes); ALOGVV("%s: writing buffer (%d bytes) to pcm device", __func__, bytes); if (out->usecase == USECASE_AUDIO_PLAYBACK_AFE_PROXY) { ret = pcm_mmap_write(out->pcm, (void *)buffer, bytes); } else ret = pcm_write(out->pcm, (void *)buffer, bytes); 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 %d - %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; }
int start_output_stream(struct stream_out *out) { int ret = 0; struct audio_usecase *uc_info; struct audio_device *adev = out->dev; ALOGV("%s: enter: usecase(%d: %s) devices(%#x)", __func__, out->usecase, use_case_table[out->usecase], out->devices); //根据usecase和device类型获得device_id out->pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK); if (out->pcm_device_id < 0) { ALOGE("%s: Invalid PCM device id(%d) for the usecase(%d)", __func__, out->pcm_device_id, out->usecase); ret = -EINVAL; goto error_config; } uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase)); uc_info->id = out->usecase; uc_info->type = PCM_PLAYBACK; uc_info->stream.out = out; uc_info->devices = out->devices; uc_info->in_snd_device = SND_DEVICE_NONE; uc_info->out_snd_device = SND_DEVICE_NONE; /* This must be called before adding this usecase to the list */ if (out->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) check_and_set_hdmi_channels(adev, out->config.channels); list_add_tail(&adev->usecase_list, &uc_info->list); select_devices(adev, out->usecase); audio_extn_extspk_update(adev->extspk); ALOGV("%s: Opening PCM device card_id(%d) device_id(%d) format(%#x)", __func__, adev->snd_card, out->pcm_device_id, out->config.format); if (out->usecase != USECASE_AUDIO_PLAYBACK_OFFLOAD) { unsigned int flags = PCM_OUT; unsigned int pcm_open_retry_count = 0; if (out->usecase == USECASE_AUDIO_PLAYBACK_AFE_PROXY) { flags |= PCM_MMAP | PCM_NOIRQ; pcm_open_retry_count = PROXY_OPEN_RETRY_COUNT; } else flags |= PCM_MONOTONIC; while (1) { out->pcm = pcm_open(adev->snd_card, out->pcm_device_id, flags, &out->config); if (out->pcm == NULL || !pcm_is_ready(out->pcm)) { ALOGE("%s: %s", __func__, pcm_get_error(out->pcm)); if (out->pcm != NULL) { pcm_close(out->pcm); out->pcm = NULL; } if (pcm_open_retry_count-- == 0) { ret = -EIO; goto error_open; } usleep(PROXY_OPEN_WAIT_TIME * 1000); continue; } break; } } else { out->pcm = NULL; out->compr = compress_open(adev->snd_card, out->pcm_device_id, COMPRESS_IN, &out->compr_config); if (out->compr && !is_compress_ready(out->compr)) { ALOGE("%s: %s", __func__, compress_get_error(out->compr)); compress_close(out->compr); out->compr = NULL; ret = -EIO; goto error_open; } if (out->offload_callback) compress_nonblock(out->compr, out->non_blocking); if (adev->visualizer_start_output != NULL) adev->visualizer_start_output(out->handle, out->pcm_device_id); if (adev->offload_effects_start_output != NULL) adev->offload_effects_start_output(out->handle, out->pcm_device_id); } ALOGV("%s: exit", __func__); return 0; error_open: stop_output_stream(out); error_config: return ret; }
static int out_standby(struct audio_stream *stream) { struct stream_out *out = (struct stream_out *)stream; struct audio_device *adev = out->dev; ALOGV("%s: enter: usecase(%d: %s)", __func__, out->usecase, use_case_table[out->usecase]); pthread_mutex_lock(&out->lock); if (!out->standby) { pthread_mutex_lock(&adev->lock); out->standby = true; if (out->usecase != USECASE_AUDIO_PLAYBACK_OFFLOAD) { if (out->pcm) { pcm_close(out->pcm); out->pcm = NULL; } } else { stop_compressed_output_l(out); out->gapless_mdata.encoder_delay = 0; out->gapless_mdata.encoder_padding = 0; if (out->compr != NULL) { compress_close(out->compr); out->compr = NULL; } } stop_output_stream(out); pthread_mutex_unlock(&adev->lock); } pthread_mutex_unlock(&out->lock); ALOGV("%s: exit", __func__); return 0; }
int pcm_write(struct pcm *pcm, const void *data, unsigned int count) { struct snd_xferi x; if (pcm->flags & PCM_IN) return -EINVAL; x.buf = (void*)data; x.frames = count / (pcm->config.channels * pcm_format_to_bits(pcm->config.format) / 8); for (;;) { if (!pcm->running) { int prepare_error = pcm_prepare(pcm); if (prepare_error) return prepare_error; if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) return oops(pcm, errno, "cannot write initial data"); pcm->running = 1; return 0; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { pcm->prepared = 0; pcm->running = 0; if (errno == EPIPE) { /* we failed to make our window -- try to restart if we are * allowed to do so. Otherwise, simply allow the EPIPE error to * propagate up to the app level */ pcm->underruns++; if (pcm->flags & PCM_NORESTART) return -EPIPE; continue; } return oops(pcm, errno, "cannot write stream data"); } return 0; } }