ALSA编程细节分析
在alsa驱动这一层,目前为止,抽象出了4层设备:
一是hw:0,0;(对于硬件参数软件必须设置一致)不知道设备号的可以用arecord -l和cat /proc/asound/devices命令获取
cat /proc/asound/devices
返回
1: : sequencer
2: [ 0- 3]: digital audio playback
3: [ 0- 7]: digital audio playback
4: [ 0- 8]: digital audio playback
5: [ 0- 9]: digital audio playback
6: [ 0- 0]: hardware dependent
7: [ 0] : control
8: [ 1- 6]: digital audio capture
9: [ 1- 7]: digital audio capture
10: [ 1- 0]: digital audio playback
11: [ 1- 0]: digital audio capture
12: [ 1- 1]: digital audio playback
13: [ 1- 1]: digital audio capture
14: [ 1- 3]: digital audio playback
15: [ 1- 4]: digital audio playback
16: [ 1- 5]: digital audio playback
17: [ 1- 2]: hardware dependent
18: [ 1- 0]: hardware dependent
19: [ 1] : control
33: : timer
其中第8,9,11,13行,就是可用的录音设备,我们用hw:1,6来设设置
二是plughw:0,0;(设备自适应接口,编程者不必关心硬件,如果软件层设置的参数与硬件支持参数不一致则ALSA会自动转换数据区匹配;0:0表示设备号和次设备号subdevice)
三是default:0;
四是default。
具体的步骤,我在下面的代码的注释写的很清楚了.
/***************************************************
** Author: xuhong *
** E-mail: zaitianya999@163.com *
** Date: 2023-06-25 *
** Description: *
** Copyright 2023 by lianruixin.com Rights Reserved *
****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <alsa/asoundlib.h>
//采样深度16位 即每帧占两个字节
#define PCM_FORMAT SND_PCM_FORMAT_S16_LE
//通道数,1为单声道,2为立体声
#define CHANNEL 2
//采样率单位HZ,每秒多少帧
#define SAMPLE_RATE 16000
int main(int argc, char *argv[])
{
/* Handle for the PCM device */
snd_pcm_t *handle;
/* Playback stream */
snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE;
snd_pcm_hw_params_t *hwparams;
//要打开的声卡名称,1.(default)默认2.hw:0,0
//对应的硬件接口,3.plughw:0,0自适应(0,0代表设备号和次设备号)
char *pcm_name = "default";
int err;
int dir = 0;
snd_pcm_format_t format = PCM_FORMAT;
unsigned int nChannels = CHANNEL;
unsigned int rate = SAMPLE_RATE;
//每一次readi(硬中断)有多少帧音频,和采样率有关,和采样深度和通道无关
snd_pcm_uframes_t psize_frames = 64;
// alsa采集时缓存多少帧,一般是psize_frames的倍数,2或4倍
snd_pcm_uframes_t bsize_frames = 256;
char *filename = "record.pcm";
int fd;
char *buffer = NULL;
int count, loop;
//一次硬中断的时间
unsigned int period_time;
fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 666);
if (fd < 0) {
perror("open file failed!\n");
return -1;
}
chmod(filename, 0777);
//打开声卡设备
if (snd_pcm_open(&handle, pcm_name, stream, 0) < 0) {
printf("Error opening PCM device %s\n", pcm_name);
return -1;
}
//分配一个配置结构体
err = snd_pcm_hw_params_malloc(&hwparams);
if (err < 0) {
perror("malloc space for snd_pcm_hw_params_t structure failed !\n");
return -1;
}
//初始化配置结构体
err = snd_pcm_hw_params_any(handle, hwparams);
if (err < 0) {
printf("Broken configuration for this PCM\n");
return err;
}
//设置音频数据格式为交互式,即左右左右,非交互式为左左右右
err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
printf("Access type not available\n");
return err;
}
//设置采样深度为16位,2个字节
err = snd_pcm_hw_params_set_format(handle, hwparams, format);
if (err < 0) {
printf("Sample format non available\n");
return err;
}
//设置通道
err = snd_pcm_hw_params_set_channels(handle, hwparams, nChannels);
if (err < 0) {
printf("Channels count non available\n");
return err;
}
//采样率
err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, &dir);
if (err < 0) {
printf("Set rate failed\n");
return err;
}
//设置缓存大小,单位(帧)
err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &bsize_frames);
if (err < 0) {
printf("Set period size failed\n");
return err;
}
//设置硬中断的包大小,单位(帧)
err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &psize_frames, 0);
if (err < 0) {
printf("Set buffer size failed\n");
return err;
}
//将配置结构体配置到ALSA驱动中
err = snd_pcm_hw_params(handle, hwparams);
if (err < 0) {
printf("failed to set hardware parameters:%s\n", snd_strerror(err));
return err;
}
//获取一次中断的音频数
err = snd_pcm_hw_params_get_period_size(hwparams, &psize_frames, &dir);
if (err < 0) {
printf("get period size fail\n");
return err;
} else {
printf("period size is %d frames.\n", psize_frames);
}
//获取一次中断的时长,单位:us,返回的period_time应该是4000us
snd_pcm_hw_params_get_period_time(hwparams, &period_time, &dir);
printf("period_time is %d\n", period_time);
loop = 10000000 / period_time; //录音10秒,循环250次
//一次硬件中断所要的空间
buffer = (char *)malloc(psize_frames * nChannels * 2); // 2 channel,sizeof(frame) = 2*2byte;
while (loop--) {
//去alsa驱动中取数据
err = snd_pcm_readi(handle, buffer, psize_frames);
if (err == -EAGAIN || (err >= 0 && (size_t)err < psize_frames)) {
//取出的数据有误
snd_pcm_wait(handle, 1000);
} else if (err == -EPIPE) {
//数据没有准备好
snd_pcm_prepare(handle);
printf("<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>/n");
} else if (err == -ESTRPIPE) {
//音频流是暂停的
snd_pcm_resume(handle);
printf("<<<<<<<<<<<<<<< Need suspend >>>>>>>>>>>>>>>/n");
} else if (err < 0) {
printf("Error snd_pcm_writei: [%s]", snd_strerror(err));
return -1;
}
// 将音频数据写入文件
count = write(fd, buffer, psize_frames * 2);
if (count != psize_frames * 2) {
printf("short write: wrote %d bytes\n", count);
}
}
//等alsa中的数据走完再关闭,snd_pcm_drop立即停止
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(buffer);
close(fd);
return 0;
}
: