提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
参考的博客
https://blog.csdn.net/shell812/article/details/73467010?fps=1&locationNum=6
一、Android ALSA音频系统框图
上面那一张是整个Android 音频系统的框图,之前分析过HAL以上的server层的服务启动,见 https://blog.csdn.net/jamecer/article/details/125442294,(有关音频的上行和下行接口代码,可以参考https://blog.csdn.net/jamecer/article/details/133637722中的5.2,这里就没有具体分析了)
HAL中主要还是对各种音频设备进行分类处理,像蓝牙、喇叭、耳机、FM、HDMI等等设备,还有音频PCM设备的打开,播放数据的写,录音数据的读,参数的设置等处理。每种类型的设备使用的PCM声卡是不同的,具体的PCM声卡驱动部分可以参考https://blog.csdn.net/jamecer/article/details/137833426,这篇是未完成的。有了这层概念,对HAL层理解会更容易,具体的HAL可以自行参考上述框图分析即可。
二、先了解一下底层中是怎么让声卡进行播放的,先看一个程序tinyplay.c
1.tinyplay.c如下所示
先push一段音频到机器中:
C:\Users\SM042021179>adb push C:\Users\SM042021179\Desktop\bg.wav sdcard
C:\Users\SM042021179\Desktop\bg.wav: 1 file pushed, 0 skipped. 142.4 MB/s (3050044 bytes in 0.020s)
然后播放音频
C27C4:/ $ su
C27C4:/ # tinyplay sdcard/bg.wav -D 0 -d 0
pcm_open() --------> fn :/dev/snd/pcmC0D0p flag:0
open pcm device success : fd[6] card[0] device[0]
format :0 channels:2 rate: 11025 period_size:1024 period_count:4 avail_min:0 start_threshold:0
Playing sample: 2 ch, 11025 hz, 16 bit 3050000 bytes
Playing sample: 2 ch, 11025 hz, 16 bit 3050000 bytes, period_size=1024, period_count=4, format=0
代码如下,将会在代码里面解析(先从mian函数开始分析):
#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <endian.h>
#include <fcntl.h>
#include <unistd.h>
#include <android/log.h>
#define LOG_NDEBUG 0
#define LOG_TAG "[XUYA]"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT 0x20746d66
#define ID_DATA 0x61746164
struct riff_wave_header {
uint32_t riff_id;
uint32_t riff_sz;
uint32_t wave_id;
};
struct chunk_header {
uint32_t id;
uint32_t sz;
};
struct chunk_fmt {
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;
};
static int close0 = 0;
//这个方法起主要的作用
void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels,
unsigned int rate, unsigned int bits, unsigned int period_size,
unsigned int period_count, uint32_t data_sz);
void stream_close(int sig)
{
/* allow the stream to be closed gracefully */
signal(sig, SIG_IGN);
close0 = 1;
}
//先从main函数开始分析
//**argv 传入的参数,详细见:tinyplay sdcard/bg.wav -D 0 -d 0
int main(int argc, char **argv)
{
FILE *file;
struct riff_wave_header riff_wave_header;
struct chunk_header chunk_header;
struct chunk_fmt chunk_fmt;
unsigned int device = 0;
unsigned int card = 0;
unsigned int period_size = 1024;
unsigned int period_count = 4;
char *filename;
int more_chunks = 1;
if (argc < 2) {
fprintf(stderr, "Usage: %s file.wav [-D card] [-d device] [-p period_size]"
" [-n n_periods] \n", argv[0]);
return 1;
}
//filename是要播放的音频文件
filename = argv[1];
file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Unable to open file '%s'\n", filename);
return 1;
}
//从要播放的音频文件那读取一些信息
fread(&riff_wave_header, sizeof(riff_wave_header), 1, file);
if ((riff_wave_header.riff_id != ID_RIFF) ||
(riff_wave_header.wave_id != ID_WAVE)) {
fprintf(stderr, "Error: '%s' is not a riff/wave file\n", filename);
fclose(file);
return 1;
}
//从要播放的音频文件那读取一些信息
do {
//从wav音频的头部获取一些有关音频的信息
//wav文件头部具体信息格式,见https://blog.csdn.net/m0_58972462/article/details/123593405
fread(&chunk_header, sizeof(chunk_header), 1, file);
switch (chunk_header.id) {
//获取格式的信息,例如:通道数、采样率这些参数,将这些参数保存到chunk_fmt、chunk_header中
case ID_FMT:
fread(&chunk_fmt, sizeof(chunk_fmt), 1, file);
/* If the format header is larger, skip the rest */
if (chunk_header.sz > sizeof(chunk_fmt))
fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR);
break;
//获取数据相关的信息,只有size和data,一般关注size
case ID_DATA:
/* Stop looking for chunks */
more_chunks = 0;
chunk_header.sz = le32toh(chunk_header.sz);
break;
default:
/* Unknown chunk, skip bytes */
fseek(file, chunk_header.sz, SEEK_CUR);
}
} while (more_chunks);
/* parse command line arguments */
//这里就是解析刚才tinyplay sdcard/bg.wav -D 0 -d 0这条指令的参数
//这里是选择声卡0,设备0 ,其他两个参数默认
argv += 2;
while (*argv) {
if (strcmp(*argv, "-d") == 0) {
argv++;
if (*argv)
device = atoi(*argv);
}
if (strcmp(*argv, "-p") == 0) {
argv++;
if (*argv)
period_size = atoi(*argv);
}
if (strcmp(*argv, "-n") == 0) {
argv++;
if (*argv)
period_count = atoi(*argv);
}
if (strcmp(*argv, "-D") == 0) {
argv++;
if (*argv)
card = atoi(*argv);
}
if (*argv)
argv++;
}
//这里是播放的主要方法,将会在这进行重点分析
//传入了要播放的音频文件handle句柄、要使用的声卡、设备、通道数、采样率等,有部分参数是从音频文件中获取的
play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate,
chunk_fmt.bits_per_sample, period_size, period_count, chunk_header.sz);
fclose(file);
return 0;
}
//这个方法,是对wav文件中头部存储的音频参数进行检查,查看该声卡是否可以播放这个参数的音频
int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
char *param_name, char *param_unit)
{
unsigned int min;
unsigned int max;
int is_within_bounds = 1;
min = pcm_params_get_min(params, param);
if (value < min) {
fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
param_unit, min, param_unit);
is_within_bounds = 0;
}
max = pcm_params_get_max(params, param);
if (value > max) {
fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
param_unit, max, param_unit);
is_within_bounds = 0;
}
return is_within_bounds;
}
//跟上面的方法作用差不多
int sample_is_playable(unsigned int card, unsigned int device, unsigned int channels,
unsigned int rate, unsigned int bits, unsigned int period_size,
unsigned int period_count)
{
struct pcm_params *params;
int can_play;
params = pcm_params_get(card, device, PCM_OUT);
if (params == NULL) {
fprintf(stderr, "Unable to open PCM device %u.\n", device);
return 0;
}
can_play = check_param(params, PCM_PARAM_RATE, rate, "Sample rate", "Hz");
can_play &= check_param(params, PCM_PARAM_CHANNELS, channels, "Sample", " channels");
can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, bits, "Bitrate", " bits");
can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, period_size, "Period size", " frames");
can_play &= check_param(params, PCM_PARAM_PERIODS, period_count, "Period count", " periods");
pcm_params_free(params);
return can_play;
}
//将会对这个方法进行重点分析
void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels,
unsigned int rate, unsigned int bits, unsigned int period_size,
unsigned int period_count, uint32_t data_sz)
{
struct pcm_config config;
struct pcm *pcm;
char *buffer;
unsigned int size, read_sz;
int num_read;
memset(&config, 0, sizeof(config));
//config是要播放的配置,像单、双声道、采样率这些
config.channels = channels;
config.rate = rate;
config.period_size = period_size;
config.period_count = period_count;
if (bits == 32)
config.format = PCM_FORMAT_S32_LE;
else if (bits == 24)
config.format = PCM_FORMAT_S24_3LE;
else if (bits == 16)
config.format = PCM_FORMAT_S16_LE;
config.start_threshold = 0;
config.stop_threshold = 0;
config.silence_threshold = 0;
if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) {
return;
}
//pcm_open这个方法是打开pcm声卡设备的一个方法,
//card和device是要打开的逻辑声卡,
//PCM_OUT表示要播放,
//config就是前面的配置
pcm = pcm_open(card, device, PCM_OUT, &config);
if (!pcm || !pcm_is_ready(pcm)) {
fprintf(stderr, "Unable to open PCM device %u (%s)\n",
device, pcm_get_error(pcm));
return;
}
//这个方法主要是计算后面要用到的buffer大小
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
//为buffer分配空间
buffer = malloc(size);
if (!buffer) {
fprintf(stderr, "Unable to allocate %d bytes\n", size);
free(buffer);
pcm_close(pcm);
return;
}
printf("Playing sample: %u ch, %u hz, %u bit %u bytes\n", channels, rate, bits, data_sz);
printf("Playing sample: %u ch, %u hz, %u bit %u bytes, period_size=%u, period_count=%u, format=%u \n", channels, rate, bits, data_sz, period_size, period_count, config.format);
LOGE("Playing sample: %u ch, %u hz, %u bit %u bytes, period_size=%u, period_count=%u", channels, rate, bits, data_sz, period_size, period_count);
/* catch ctrl-c to shutdown cleanly */
signal(SIGINT, stream_close);
do {
//一般开始size都会比data_sz小,data_sz是整个wav音频文件的大小
read_sz = size < data_sz ? size : data_sz;
//从音频文件file中读取音频数据
num_read = fread(buffer, 1, read_sz, file);
if (num_read > 0) {
//将音频数据写入声卡,即可播放
if (pcm_write(pcm, buffer, num_read)) {
fprintf(stderr, "Error playing sample\n");
break;
}
}
} while (!close0 && num_read > 0 && data_sz > 0);
free(buffer);
//关闭声卡
pcm_close(pcm);
}
注意:关键的三个方法为,打开声卡,写入数据,关闭声卡(下面对代码的分析,将会着重关注这几个方法)
pcm_open(card, device, PCM_OUT, &config)
pcm_write(pcm, buffer, num_read)
pcm_close(pcm)
2.对于上述的三个方法是如何实现的,可以从external/tinyalsa/pcm.c这个文件中找到
先关注pcm_open这个方法,这个方法主要是打开pcm设备,然后给它设置一些参数,代码如下:
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
struct pcm *pcm;
struct snd_pcm_info info;
struct snd_pcm_hw_params params;
struct snd_pcm_sw_params sparams;
char fn[256];
int rc;
if (!config) {
return &bad_pcm; /* TODO: could support default config here */
}
pcm = calloc(1, sizeof(struct pcm));
if (!pcm)
return &bad_pcm; /* TODO: could support default config here */
//音频配置传给pcm这个结构中
pcm->config = *config;
//fn为将要打开的声卡路径
//例:pcmC0D0p 声卡0 设备0 p代表播音
//pcmC0D1c 声卡0 设备1 c代表录音
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
flags & PCM_IN ? 'c' : 'p');
LOGI("pcm_open() --------> fn :%s flag:%d",fn,flags);
printf("pcm_open() --------> fn :%s flag:%d \n",fn,flags);
pcm->flags = flags;
//打开声卡
pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
if (pcm->fd < 0) {
oops(pcm, errno, "cannot open device '%s'", fn);
return pcm;
}else{
LOGI("open pcm device success : fd[%d] card[%d] device[%d]",pcm->fd, card, device);
printf("open pcm device success : fd[%d] card[%d] device[%d] \n",pcm->fd, card, device);
}
//对该文件属性进行修改,修改为非阻塞模式
if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) &
~O_NONBLOCK) < 0) {
oops(pcm, errno, "failed to reset blocking mode '%s'", fn);
goto fail_close;
}
//对文件io进行操作,获取SNDRV_PCM_IOCTL_INFO信息
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
oops(pcm, errno, "cannot get info");
goto fail_close;
}
//将获取到的PCM设备信息,赋值给pcm->subdevice
pcm->subdevice = info.subdevice;
LOGI(" format :%d channels:%d rate: %d period_size:%d period_count:%d avail_min:%d start_threshold:%d",
config->format , config->channels , config->rate, config->period_size , config->period_count, config->avail_min, config->start_threshold);
printf("format :%d channels:%d rate: %d period_size:%d period_count:%d avail_min:%d start_threshold:%d\n",
config->format , config->channels , config->rate, config->period_size , config->period_count, config->avail_min, config->start_threshold);
//将config中的音频参数放入params
param_init(¶ms);
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,
pcm_format_to_alsa(config->format));
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT,
SNDRV_PCM_SUBFORMAT_STD);
param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
pcm_format_to_bits(config->format));
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,
pcm_format_to_bits(config->format) * config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,
config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate);
if (flags & PCM_NOIRQ) {
if (!(flags & PCM_MMAP)) {
oops(pcm, EINVAL, "noirq only currently supported with mmap().");
goto fail_close;
}
params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
pcm->noirq_frames_per_msec = config->rate / 1000;
}
if (flags & PCM_MMAP)
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
else
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
//继续操作设备io,将音频参数写入到设备中
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
oops(pcm, errno, "cannot set hw params");
goto fail_close;
}
/* get our refined hw_params */
//获取一些参数
config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);
pcm->buffer_size = config->period_count * config->period_size;
if (flags & PCM_MMAP) {
pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
if (pcm->mmap_buffer == MAP_FAILED) {
oops(pcm, errno, "failed to mmap buffer %d bytes\n",
pcm_frames_to_bytes(pcm, pcm->buffer_size));
goto fail_close;
}
}
memset(&sparams, 0, sizeof(sparams));
sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
sparams.period_step = 1;
if (!config->start_threshold) {
if (pcm->flags & PCM_IN)
pcm->config.start_threshold = sparams.start_threshold = 1;
else
pcm->config.start_threshold = sparams.start_threshold =
config->period_count * config->period_size / 2;
} else
sparams.start_threshold = config->start_threshold;
/* pick a high stop threshold - todo: does this need further tuning */
if (!config->stop_threshold) {
if (pcm->flags & PCM_IN)
pcm->config.stop_threshold = sparams.stop_threshold =
config->period_count * config->period_size * 10;
else
pcm->config.stop_threshold = sparams.stop_threshold =
config->period_count * config->period_size;
}
else
sparams.stop_threshold = config->stop_threshold;
if (!pcm->config.avail_min) {
if (pcm->flags & PCM_MMAP)
pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
else
pcm->config.avail_min = sparams.avail_min = 1;
} else
sparams.avail_min = config->avail_min;
sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
sparams.silence_threshold = config->silence_threshold;
sparams.silence_size = config->silence_size;
pcm->boundary = sparams.boundary = pcm->buffer_size;
while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
pcm->boundary *= 2;
//设置参数
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
oops(pcm, errno, "cannot set sw params");
goto fail;
}
rc = pcm_hw_mmap_status(pcm);
if (rc < 0) {
oops(pcm, errno, "mmap status failed");
goto fail;
}
#ifdef SNDRV_PCM_IOCTL_TTSTAMP
if (pcm->flags & PCM_MONOTONIC) {
int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
if (rc < 0) {
oops(pcm, errno, "cannot set timestamp type");
goto fail;
}
}
#endif
pcm->underruns = 0;
return pcm;
fail:
if (flags & PCM_MMAP)
munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
fail_close:
LOGI("pcm_open() failed !");
printf("pcm_open() failed !\n");
close(pcm->fd);
pcm->fd = -1;
return pcm;
}
再看pcm_write(pcm, buffer, num_read)这个方法,这个方法主要是将数据写入到声卡中:
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
struct snd_xferi x;
//如果声卡flags是PCM_IN,即是录音,是错误的
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);
// LOGI("pcm_write ()");
//这个方法是自己写的,主要是用来看一下上层传进来的数据是否正确,忽略
savePlaybackPcmAudio(data, count);
//写入数据
for (;;) {
//如果进入这个if,说明设备还未运行起来
if (!pcm->running) {
int prepare_error = pcm_prepare(pcm);
if (prepare_error){
LOGE("pcm_prepare failed");
printf("pcm_prepare failed");
return prepare_error;
}
//操作设备io,写入一些数据,即将x写入到设备io
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)){
LOGE("cannot write initial data ");
printf("cannot write initial data ");
return oops(pcm, errno, "cannot write initial data");
}
//这个标志位表示设备已经运行起来了
pcm->running = 1;
// LOGI("pcm_write success ");
return 0;
}
//继续操作设备io,写入一些数据
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){
LOGE("cannot write data ");
printf("cannot write data ");
return -EPIPE;
}
continue;
}
LOGE("cannot write stream data ");
return oops(pcm, errno, "cannot write stream data");
}
// LOGI("pcm_write success ");
return 0;
}
}
最后看pcm_read,这个方法主要是录音时会用到,代码如下所示:
int pcm_read(struct pcm *pcm, void *data, unsigned int count)
{
struct snd_xferi x;
//判断是否是录音
if (!(pcm->flags & PCM_IN)){
LOGI("pcm_read error !");
printf("pcm_read error ! \n");
return -EINVAL;
}
//初始化变量
x.buf = data;
x.frames = count / (pcm->config.channels *
pcm_format_to_bits(pcm->config.format) / 8);
//这个方法是自己写的,主要是用来看一下直接从声卡中获取的数据,忽略
saveCapturePcmAudio(data, count);
for (;;) {
if (!pcm->running) {
if (pcm_start(pcm) < 0) {
fprintf(stderr, "start error");
LOGI("pcm_read start error");
printf("pcm_read start error \n");
return -errno;
}
}
//操作设备io,从声卡中获取录音数据
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
pcm->prepared = 0;
pcm->running = 0;
if (errno == EPIPE) {
/* we failed to make our window -- try to restart */
pcm->underruns++;
continue;
}
LOGI("cannot read stream data");
printf("cannot read stream data \n");
return oops(pcm, errno, "cannot read stream data");
}
return 0;
}
}
三、由上到下进行分析
先从AudioFlinger(这个服务本质上也是调用各种各样的接口,播音、录音、音频参数的设置等等动作,策略可以查看AudioPolicyServer(APS))进行分析,知道这个上层的服务是怎么调用到下层的HAL模块
由APS的启动分析中,我们可以得知服务启动时,会使用AF的loadHwModule方法加载不同的HAL模块(见https://blog.csdn.net/jamecer/article/details/125442294中的第五小节中的onNewAudioModulesAvailableInt分析)
void AudioPolicyManager::onNewAudioModulesAvailableInt(DeviceVector *newDevices)
{
hwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));
}