简介
tinyalsa的源代码包括两部分,tinyalsa库文件和小工具。
tinyalsa库文件的源代码有两个,是mixer.c pcm.c。其中mixer.c提供了控制接口。pcm.c提供了PCM播放、录音的接口。
tinyalsa工具包括了四个文件,tinyplay.c、tinycap.c、tinymix.c、tinypcminfo.c。每个文件对应一个可执行文件。tinyplay 是一个简单的播放器,可以播放WAV文件。tinycap是一个简单的录音程序,可以进行录音并保存到一个wav文件中。tinymix用于配置control,例如切换音频通道等等。tinypcminfo获取PCM的参数的示例,例如采样率的范围、通道数的范围等等。
**
PCM结构体说明
struct pcm_config {
unsigned int channels;
unsigned int rate;
unsigned int period_size;
unsigned int period_count;
enum pcm_format format;
unsigned int start_threshold;
unsigned int stop_threshold;
unsigned int silence_threshold;
int avail_min;
};
.channels :通道数
.rate : 采样率
.period_size : 每次传输的数据长度。值越小,时延越小,cpu占用就越高。
.period_count: 缓冲区period的个数。缓冲区越多,发生XRUN的机会就越少。
.format : 定义数据格式,如数据宽度
.start_threshold : 缓冲区的数据超过该值时,硬件开始启动数据传输。
.stop_threshold : 缓冲区空闲区大于该值时,硬件停止传输。默认情况下,这个数 为整个缓冲区的大小,即整个缓冲区空了,就停止传输。
.avail_min : 缓冲区空闲区大于该值时,pcm_mmap_write()往缓冲写数据。
**
tinyalsa头文件asoundlib.h
**
该文件主要包含如下函数
/* PCM runtime states */ //PCM运行状态
#define PCM_STATE_OPEN 0
#define PCM_STATE_SETUP 1
#define PCM_STATE_PREPARED 2
#define PCM_STATE_RUNNING 3
#define PCM_STATE_XRUN 4
#define PCM_STATE_DRAINING 5
#define PCM_STATE_PAUSED 6
#define PCM_STATE_SUSPENDED 7
#define PCM_STATE_DISCONNECTED 8
/* TLV header size*/ //报头大小
#define TLV_HEADER_SIZE (2 * sizeof(unsigned int))
/* Bit formats */ //位格式
enum pcm_format {
PCM_FORMAT_INVALID = -1,
PCM_FORMAT_S16_LE = 0, /* 16-bit signed */
PCM_FORMAT_S32_LE, /* 32-bit signed */
PCM_FORMAT_S8, /* 8-bit signed */
PCM_FORMAT_S24_LE, /* 24-bits in 4-bytes */
PCM_FORMAT_S24_3LE, /* 24-bits in 3-bytes */
PCM_FORMAT_MAX,
};
/* Bitmask has 256 bits (32 bytes) in asound.h */ //位掩码在asound.h中有32位
struct pcm_mask {
unsigned int bits[32 / sizeof(unsigned int)];
};
/* Configuration for a stream */ //流的配置
struct pcm_config {
unsigned int channels;
unsigned int rate;
unsigned int period_size;
unsigned int period_count;
enum pcm_format format;
unsigned int start_threshold;
unsigned int stop_threshold;
unsigned int silence_threshold;
unsigned int silence_size;
int avail_min;
};
/* PCM parameters */ //PCM参数
enum pcm_param
{
/* mask parameters */
PCM_PARAM_ACCESS,
PCM_PARAM_FORMAT,
PCM_PARAM_SUBFORMAT,
/* interval parameters */
PCM_PARAM_SAMPLE_BITS,
PCM_PARAM_FRAME_BITS,
PCM_PARAM_CHANNELS,
PCM_PARAM_RATE,
PCM_PARAM_PERIOD_TIME,
PCM_PARAM_PERIOD_SIZE,
PCM_PARAM_PERIOD_BYTES,
PCM_PARAM_PERIODS,
PCM_PARAM_BUFFER_TIME,
PCM_PARAM_BUFFER_SIZE,
PCM_PARAM_BUFFER_BYTES,
PCM_PARAM_TICK_TIME,
};
/* Mixer control types */ //混音控制类型
enum mixer_ctl_type {
MIXER_CTL_TYPE_BOOL,
MIXER_CTL_TYPE_INT,
MIXER_CTL_TYPE_ENUM,
MIXER_CTL_TYPE_BYTE,
MIXER_CTL_TYPE_IEC958,
MIXER_CTL_TYPE_INT64,
MIXER_CTL_TYPE_UNKNOWN,
MIXER_CTL_TYPE_MAX,
};
/* 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 */ //获取PCM
struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
unsigned int flags);
void pcm_params_free(struct pcm_params *pcm_params); //释放pcm
struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params, //获得PCM掩码
enum pcm_param param);
unsigned int pcm_params_get_min(struct pcm_params *pcm_params, //获得参数最小值
enum pcm_param param);
void pcm_params_set_min(struct pcm_params *pcm_params, //设置参数最小值
enum pcm_param param, unsigned int val);
unsigned int pcm_params_get_max(struct pcm_params *pcm_params, //获取参数最大值
enum pcm_param param);
void pcm_params_set_max(struct pcm_params *pcm_params, //设置参数最大值
enum pcm_param param, unsigned int val);
int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size); //将pcm参数转换成字符串
/* Returns 1 if the pcm_format is present (format bit set) in
* the pcm_params structure; 0 otherwise, or upon unrecognized format.
*/
int pcm_params_format_test(struct pcm_params *params, enum pcm_format format); //pcm参数格式测试
/* Set and get config */
int pcm_get_config(struct pcm *pcm, struct pcm_config *config); //获得配置
int pcm_set_config(struct pcm *pcm, struct pcm_config *config); //设置配置
/* Returns a human readable reason for the last error */
const char *pcm_get_error(struct pcm *pcm); //检查错误
/* Returns the sample size in bits for a PCM format.
* As with ALSA formats, this is the storage size for the format, whereas the
* format represents the number of significant bits. For example,
* PCM_FORMAT_S24_LE uses 32 bits of storage.
*/
unsigned int pcm_format_to_bits(enum pcm_format format); //以位为单位返回pcm的格式
/* Returns the buffer size (int frames) that should be used for pcm_write. */
unsigned int pcm_get_buffer_size(struct pcm *pcm); //获得pcm缓冲区大小
unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames); //PCM框架输出到字节
unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes); //PCM字节输出到框架
/* Returns the pcm latency in ms */
unsigned int pcm_get_latency(struct pcm *pcm); //以秒为单位获得PCM的时间时延
int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
struct timespec *tstamp);
/* Returns the subdevice on which the pcm has been opened */ //返回打开pcm的子设备
unsigned int pcm_get_subdevice(struct pcm *pcm);
/* 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); //将PCM写入到队列中
int pcm_read(struct pcm *pcm, void *data, unsigned int count); //从队列中取得PCM数据
/*
* mmap() support. //内存映射的支持
*/
int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count); //pcm内存映射写操作
int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count); //pcm内存映射读操作
int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, //开始pcm内存映射
unsigned int *frames);
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames); //内存映射提交
int pcm_mmap_avail(struct pcm *pcm); //利用内存映射
/* Returns current read/write position in the mmap buffer with associated time stamp.
*/ //返回mmap缓冲区中当前读/写的位置和相关的时间戳。
int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp);
/* Prepare the PCM substream to be triggerable */ //准备pcm子流为可触发
int pcm_prepare(struct pcm *pcm);
/* Start and stop a PCM channel that doesn't transfer data */ //启动和停止一个PCM通道不传输数据
int pcm_start(struct pcm *pcm);
int pcm_stop(struct pcm *pcm);
/* ioctl function for PCM driver */ //pcm驱动的ioctl函数
int pcm_ioctl(struct pcm *pcm, int request, ...);
/* Interrupt driven API */
int pcm_wait(struct pcm *pcm, int timeout); //中断驱动API接口
int pcm_get_poll_fd(struct pcm *pcm);
int pcm_set_avail_min(struct pcm *pcm, int avail_min);
/*
* MIXER API //-------------------------------混音的API------------------
*/
struct mixer;
struct mixer_ctl;
//-----------------------------------------------------------------------------------------------
/* 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);
//-----------------------------------------------------------------------------------------------
/* Get info about mixer controls */ //得到混音控制器的信息
const char *mixer_ctl_get_name(struct mixer_ctl *ctl);
enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl);
const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl);
unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl);
unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl);
const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
unsigned int enum_id);
/* 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_is_access_tlv_rw(struct mixer_ctl *ctl);
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);
//----------------------------------------------------------------------------------------------------------
/* Determine range of integer mixer controls */ //确定混音器的控制range(范围)
int mixer_ctl_get_range_min(struct mixer_ctl *ctl); //获得混音器控制的最小范围
int mixer_ctl_get_range_max(struct mixer_ctl *ctl); //获得混音器控制的最大范围
int mixer_subscribe_events(struct mixer *mixer, int subscribe); //混音订阅事件
int mixer_wait_event(struct mixer *mixer, int timeout); // 混音等待事件
int mixer_consume_event(struct mixer *mixer); //混音消费事件
#if defined(__cplusplus)
} /* extern "C" */
#endif
#endif
**
mixer_open函数代码解析
**
//mixer_open函数的主要作用
/*
1-根据声卡号来拼凑 kcontrol 节点的字符串名字,并打开节点
2-通过 ioctrl 获取 所有 支持的Kcontrol 的字符串列表
3-分配 Mixer 的内存
4-获得 Mixer 结构体中的 card_info 信息
5-取出kontrol的id 存入 ei 中
6-获取到所有的 kcontrol list*/
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];
//1.根据声卡号来拼凑kcontrol节点的字符串名字,并打开节点
snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
fd = open(fn, O_RDWR); //通过open打开声卡
if (fd < 0) //如果小于0代表打开声卡失败
return 0;
//2.通过ioctrl获取所有支持的kcontrol的数量
memset(&elist, 0, sizeof(elist));
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) //ioctl驱动设备打开函数<0的话就代表打开失败
goto fail;
//3.分配mixer的内存,用于保存kernel kontrol 信息的结构体
mixer = calloc(1, sizeof(*mixer)); //分配所需的内存空间,并且将内存设为0,返回的是一个指向该内存的指针
if (!mixer)
goto fail;
//4.获得mixer结构体中的card_info信息
mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
if (!mixer->ctl || !mixer->elem_info)
goto fail;
if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
goto fail;
//临时存储空间分配空间
eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
if (!eid)
goto fail;
mixer->count = elist.count;
mixer->fd = fd;
elist.space = mixer->count;
elist.pids = eid;
//获取到所有的kcontrol list
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
goto fail;
for (n = 0; n < mixer->count; n++) {
struct mixer_ctl *ctl = mixer->ctl + n;
ctl->mixer = mixer;
ctl->info = mixer->elem_info + n;
ctl->info->id.numid = eid[n].numid;
strncpy((char *)ctl->info->id.name, (char *)eid[n].name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
ctl->info->id.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 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;
}
**
tinycap.c代码解析
**
#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;
//默认使用第一个是录音设备,双声道,44.1kHz踩样率,16bit
//period为1024帧,count一共四个
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++;
}
//wav格式头文件结构填充
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;
//默认的是16bits
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); //通过fseek进行文件大小偏移
//安装信号捕捉程序并开始捕捉
/* 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;
//给config结构体初始化,并且根据参数赋值
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 = 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;
}
//PCM帧变为字节,记录的时候是帧,要把帧变为字节数赋给size
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
//通过size开辟一片buffer大小
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;
}
**
tinymix.c源码解析
**
main()函数
首先来看函数执行流程
1. 处理传入的参数。其中的getopt_long()函数用于处理传入的参数
while (1) {
int option_index = 0;
int option_char = 0;
option_char = getopt_long(argc, argv, tinymix_short_options,
tinymix_long_options, &option_index);
if (option_char == -1)
break;
switch (option_char) {
case 'D':
card = atoi(optarg);
break;
case 'a':
g_all_values = 1;
break;
case 't':
g_tabs_only = 1;
break;
case 'v':
g_value_only = 1;
break;
case 'h':
usage();
return 0;
default:
usage();
return EINVAL;
}
}
2. 打开对应声卡的混音器
mixer = mixer_open(card);
3. 参数为D a t v h以设置好的方式打印出所有控件类似1;主要是打印控件信息;对相关控件进行赋值
if (argc == optind) {
printf("Mixer name: '%s'\n", mixer_get_name(mixer));
tinymix_list_controls(mixer);
} else if (argc == optind + 1) {
ret = tinymix_detail_control(mixer, argv[optind], !g_value_only, !g_value_only);
} else if (argc >= optind + 2) {
ret = tinymix_set_value(mixer, argv[optind], &argv[optind + 1], argc - optind - 1);
}
4. 关闭设备
mixer_close(mixer);
tinymix_list_controls()函数
先来看看函数原型
static void tinymix_list_controls(struct mixer *mixer)
该函数内主要包含以下函数
/*拿到指定混音器控件数目*/
1. unsigned int mixer_get_num_ctls(struct mixer *mixer);
/*拿到指定混音器的指定控件*/
2. struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id);
/*拿到控件名字*/
3. const char *mixer_ctl_get_name(struct mixer_ctl *ctl);
/*拿到控件类型*/
4. const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl);
/*拿到空间具体数值*/
5. unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl);
/*打印信息*/
6. static int tinymix_detail_control(struct mixer *mixer, const char *control,
int prefix, int print_all)
这个函数最主要的部分是
if (g_tabs_only)
printf("ctl\ttype\tnum\tname\tvalue");
else
printf("ctl\ttype\tnum\t%-40s value\n", "name");
if (g_all_values)
printf("\trange/values\n");
else
printf("\n");
for (i = 0; i < num_ctls; i++) {
ctl = mixer_get_ctl(mixer, i);
name = mixer_ctl_get_name(ctl);
type = mixer_ctl_get_type_string(ctl);
num_values = mixer_ctl_get_num_values(ctl);
if (g_tabs_only)
printf("%d\t%s\t%d\t%s\t", i, type, num_values, name);
else
printf("%d\t%s\t%d\t%-40s ", i, type, num_values, name);
tinymix_detail_control(mixer, name, 0, g_all_values);
}
这个函数主要作用还是打印信息
**
tinymix_set_value()函数
**
先来看看函数原型
static int tinymix_set_value(struct mixer *mixer, const char *control,
char **values, unsigned int num_values)
这个函数主要是用来设置参数,涉及到的函数主要是:
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value);
static void tinymix_set_byte_ctl(struct mixer_ctl *ctl, char **values,
unsigned int num_values);
而tinymix_set_byte_ctl()函数又涉及到了
int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count);
同样也是用于设置参数
**
tinyplay.c源码解析
**
#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <endian.h>
#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 close = 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);
close = 1;
}
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;
//确定传入参数目,没指定具体wav文件就打印帮助信息。
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 = argv[1];
//打开wav文件进行操作
file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Unable to open file '%s'\n", filename);
return 1;
}
//读文件开头的信息,确认是wav或riff文件
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 {
fread(&chunk_header, sizeof(chunk_header), 1, file);
switch (chunk_header.id) {
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;
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 */
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++;
}
//开始播放音频文件
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;
}
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;
//填入pcm流的相关信息
memset(&config, 0, sizeof(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设备
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 = 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);
/* catch ctrl-c to shutdown cleanly */
signal(SIGINT, stream_close);
//循环写入数据到pcm设备
do {
read_sz = size < data_sz ? size : data_sz;
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;
}
data_sz -= num_read;
}
} while (!close && num_read > 0 && data_sz > 0);
free(buffer);
pcm_close(pcm);
}