Android Tinyalsa
一 、tinyalsa简介
ALSA全称是Advanced Linux Sound Architecture,是Linux内核2.6后续版本中支持音频系统的标准接口程序,由ALSA库、内核驱动和相关测 试开发工具组成。tinyalsa是一个用来和内核交互的接口库。在aosp的/external/tinyalsa/下可以看到相关的应用。
二、相关模块及功能
- 驱动部分
Android中,查看/dev/snd下面节点:
ls -l /dev/snd/
total 0
crw-rw---- 1 system audio 116, 0 2023-07-17 17:26:28.290000000 +0800 controlC0
crw-rw---- 1 system audio 116, 24 2023-07-17 17:26:28.290000000 +0800 pcmC0D0c
crw-rw---- 1 system audio 116, 16 2023-07-17 17:26:28.290000000 +0800 pcmC0D0p
crw-rw---- 1 system audio 116, 33 2023-07-17 17:26:28.300000000 +0800 timer
其中/dev/snd/controlC%u
,一般是C0
表示音频控制设备文件,例如通道选择、混音、麦克风控制等。pcm设备中的格式/dev/snd/pcmC%uD%u%p
,C
代表card,D
代表device,c
代表capture,p
代表playback。timer:
定时器。
- 音频工具
tinycap.c
: 设备采集pcm格式的码流,并保存为wav格式的文件。
tinymix.c
: pcm设备的控制,可得到音频通路相关的配置参数。
tinypcminfo.c
:查看pcm设备具备什么能力。
tinyplay.c
:pcm播放wav格式的音频文件。
- goldfish中hal的实现
/device/generic/goldfish/audio/
//定义
struct audio_module HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = AUDIO_MODULE_API_VERSION_0_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = AUDIO_HARDWARE_MODULE_ID,
.name = "Generic audio HW HAL",
.author = "The Android Open Source Project",
.methods = &hal_module_methods,
},
//打开实现
static struct hw_module_methods_t hal_module_methods = {
.open = adev_open,
};
- 工具使用
/data/local/tmp # ./tinymix
Mixer name: 'HDA Intel'
Number of controls: 8
ctl type num name value
0 INT 2 Master Playback Volume 74 74
1 BOOL 2 Master Playback Switch On On
2 INT 2 Capture Volume 74 74
3 BOOL 2 Capture Switch On On
4 BOOL 1 Line Phantom Jack On
5 BOOL 1 Line Out Phantom Jack On
6 INT 2 Playback Channel Map 3 4
7 INT 2 Capture Channel Map 0 0
录音:
/data/local/tmp #./tinycap
Usage: ./tinycap file.wav [-D card] [-d device] [-c channels] [-r rate] [-b bits] [-p period_size] [-n n_periods] [-T capture time]
播放:
/data/local/tmp # ./tinyplay
Usage: ./tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]
- 相关代码
goldfish 中hal层入口:
static int adev_open(const hw_module_t* module, const char* name,
hw_device_t** device)
{
....
adev = calloc(1, sizeof(struct generic_audio_device));
pthread_mutex_init(&adev->lock, (const pthread_mutexattr_t *) NULL);
....
//注册函数
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; // no op
adev->device.set_voice_volume = adev_set_voice_volume; // no op
adev->device.set_master_volume = adev_set_master_volume; // no op
adev->device.get_master_volume = adev_get_master_volume; // no op
adev->device.set_master_mute = adev_set_master_mute; // no op
adev->device.get_master_mute = adev_get_master_mute; // no op
adev->device.set_mode = adev_set_mode; // no op
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; // no op
adev->device.get_parameters = adev_get_parameters; // no op
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;
//PCM_CARD =0 调用tinyalsa的mixer_open函数
adev->mixer = mixer_open(PCM_CARD);
struct mixer_ctl *ctl;
......
}
mixer_open是tinyalsa提供的api,打开为/dev/snd/controlC%u
.
struct mixer *mixer_open(unsigned int card)
{
struct snd_ctl_elem_list elist;
struct snd_ctl_elem_id *eid = NULL;
struct mixer *mixer = NULL;
unsigned int n;
int fd;
char fn[256];
snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
fd = open(fn, O_RDWR);
if (fd < 0)
return 0;
....
free(eid);
return mixer;
fail:
/* TODO: verify frees in failure case */
if (eid)
free(eid);
if (mixer)
mixer_close(mixer);
else if (fd >= 0)
close(fd);
return 0;
}
录音代码:
#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT 0x20746d66
#define ID_DATA 0x61746164
#define FORMAT_PCM 1
struct wav_header {
uint32_t riff_id;
uint32_t riff_sz;
uint32_t riff_fmt;
uint32_t fmt_id;
uint32_t fmt_sz;
uint16_t audio_format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
uint32_t data_id;
uint32_t data_sz;
};
int capturing = 1;
unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
unsigned int channels, unsigned int rate,
enum pcm_format format, unsigned int period_size,
unsigned int period_count, unsigned int cap_time);
void sigint_handler(int sig __unused)
{
capturing = 0;
}
int main(int argc, char **argv)
{
FILE *file;
struct wav_header header;
unsigned int card = 0;
unsigned int device = 0;
unsigned int channels = 2;
unsigned int rate = 44100;
unsigned int bits = 16;
unsigned int frames;
unsigned int period_size = 1024;
unsigned int period_count = 4;
unsigned int cap_time = 0;
enum pcm_format format;
if (argc < 2) {
fprintf(stderr, "Usage: %s file.wav [-D card] [-d device]"
" [-c channels] [-r rate] [-b bits] [-p period_size]"
" [-n n_periods] [-T capture time]\n", argv[0]);
return 1;
}
file = fopen(argv[1], "wb");
if (!file) {
fprintf(stderr, "Unable to create file '%s'\n", argv[1]);
return 1;
}
/* parse command line arguments */
argv += 2;
while (*argv) {
if (strcmp(*argv, "-d") == 0) {
argv++;
if (*argv)
device = atoi(*argv);
} else if (strcmp(*argv, "-c") == 0) {
argv++;
if (*argv)
channels = atoi(*argv);
} else if (strcmp(*argv, "-r") == 0) {
argv++;
if (*argv)
rate = atoi(*argv);
} else if (strcmp(*argv, "-b") == 0) {
argv++;
if (*argv)
bits = atoi(*argv);
} else if (strcmp(*argv, "-D") == 0) {
argv++;
if (*argv)
card = atoi(*argv);
} else if (strcmp(*argv, "-p") == 0) {
argv++;
if (*argv)
period_size = atoi(*argv);
} else if (strcmp(*argv, "-n") == 0) {
argv++;
if (*argv)
period_count = atoi(*argv);
} else if (strcmp(*argv, "-T") == 0) {
argv++;
if (*argv)
cap_time = atoi(*argv);
}
if (*argv)
argv++;
}
header.riff_id = ID_RIFF;
header.riff_sz = 0;
header.riff_fmt = ID_WAVE;
header.fmt_id = ID_FMT;
header.fmt_sz = 16;
header.audio_format = FORMAT_PCM;
header.num_channels = channels;
header.sample_rate = rate;
switch (bits) {
case 32:
format = PCM_FORMAT_S32_LE;
break;
case 24:
format = PCM_FORMAT_S24_LE;
break;
case 16:
format = PCM_FORMAT_S16_LE;
break;
default:
fprintf(stderr, "%u bits is not supported.\n", bits);
fclose(file);
return 1;
}
header.bits_per_sample = pcm_format_to_bits(format);
header.byte_rate = (header.bits_per_sample / 8) * channels * rate;
header.block_align = channels * (header.bits_per_sample / 8);
header.data_id = ID_DATA;
/* leave enough room for header */
fseek(file, sizeof(struct wav_header), SEEK_SET);
/* install signal handler and begin capturing */
signal(SIGINT, sigint_handler);
signal(SIGHUP, sigint_handler);
signal(SIGTERM, sigint_handler);
frames = capture_sample(file, card, device, header.num_channels,
header.sample_rate, format,
period_size, period_count, cap_time);
printf("Captured %u frames\n", frames);
/* write header now all information is known */
header.data_sz = frames * header.block_align;
header.riff_sz = header.data_sz + sizeof(header) - 8;
fseek(file, 0, SEEK_SET);
fwrite(&header, sizeof(struct wav_header), 1, file);
fclose(file);
return 0;
}
unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
unsigned int channels, unsigned int rate,
enum pcm_format format, unsigned int period_size,
unsigned int period_count, unsigned int cap_time)
{
struct pcm_config config;
struct pcm *pcm;
char *buffer;
unsigned int size;
unsigned int bytes_read = 0;
unsigned int frames = 0;
struct timespec end;
struct timespec now;
memset(&config, 0, sizeof(config));
config.channels = channels;
config.rate = rate;
config.period_size = period_size;
config.period_count = period_count;
config.format = format;
config.start_threshold = 0;
config.stop_threshold = 0;
config.silence_threshold = 0;
pcm = pcm_open(card, device, PCM_IN, &config);
if (!pcm || !pcm_is_ready(pcm)) {
fprintf(stderr, "Unable to open PCM device (%s)\n",
pcm_get_error(pcm));
return 0;
}
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
buffer = malloc(size);
if (!buffer) {
fprintf(stderr, "Unable to allocate %u bytes\n", size);
free(buffer);
pcm_close(pcm);
return 0;
}
printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
pcm_format_to_bits(format));
clock_gettime(CLOCK_MONOTONIC, &now);
end.tv_sec = now.tv_sec + cap_time;
end.tv_nsec = now.tv_nsec;
while (capturing && !pcm_read(pcm, buffer, size)) {
if (fwrite(buffer, 1, size, file) != size) {
fprintf(stderr,"Error capturing sample\n");
break;
}
bytes_read += size;
if (cap_time) {
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > end.tv_sec ||
(now.tv_sec == end.tv_sec && now.tv_nsec >= end.tv_nsec))
break;
}
}
frames = pcm_bytes_to_frames(pcm, bytes_read);
free(buffer);
pcm_close(pcm);
return frames;
}