概念
交错与非交叉
例:双声道,16位
交错:一次记录一个完整帧(左、右声道)。
记录顺序:左(16 bit)、右(16 bit)、左(16 bit)、右(16 bit)
非交错:一次记录完一个周期内声道数据,再记录另一个声道。
记录顺序:左(32 bit)、右(32 bit)
基本都是采用交错模式
阻塞打开和非阻塞打开区别
阻塞状态打开,读取时候,若无数据则等待。写入时候,若缓冲区满了,则等待。
非阻塞打开:读写不会等待,而是会以错误形式立刻返回
帧、周期、缓冲区
一帧 = 一个完整的声音单元
如双声道16bit : 一帧 = 16bit * 2
周期 等于 写(读) 完整一次传输的帧数量
一个周期一个硬件中断
一般一个缓冲区内有多个周期,用于读取或者写入数据
内存大小: 缓冲区 ≥ 周期 > 帧
同步方式
static int pcm_init(snd_pcm_t **pcm_handle)
{
snd_pcm_t *pcm_handle = NULL;
snd_pcm_open(&pcm_handle,"hw:0,0",SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_hw_params_t *hwparams = NULL;
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_any(pcm_handle, hwparams);
snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(pcm_handle, hwparams,SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(pcm_handle, hwparams,2);
snd_pcm_hw_params_set_rate(pcm_handle, hwparams,48000,0);
snd_pcm_hw_params_set_period_size(pcm_handle, hwparams, 1024, 0);
snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, 4*1024);
snd_pcm_hw_params(pcm_handle, hwparams);
snd_pcm_hw_params_free(hwparams);
}
int main()
{
snd_pcm_t *pcm_handle = NULL;
pcm_init(&pcm_handle);
SND_PCM_STREAM_PLAYBACK -> snd_pcm_writei(pcm_handle ,buf,1024);
SND_PCM_STREAM_CAPTURE -> snd_pcm_readi(pcm_handle ,buf,1024);
snd_pcm_close(pcm_handle);
}
播放器示例代码
#include "alsa/asoundlib.h"
#include <stdio.h>
typedef struct
{
unsigned int id;
unsigned int size;
unsigned int format;
}RIFF_S;
typedef struct
{
unsigned int id;
unsigned int size;
unsigned short audioformat;
unsigned short channels;
unsigned int sampleRate;
unsigned int ByteRate;
unsigned short BlockAlign;
unsigned short SampleBit;
}FMT_S;
typedef struct
{
unsigned int id;
unsigned int size;
}Data_s;
snd_pcm_t *pcm_handle = NULL;
static int pcm_init(int channels,int rate)
{
/*
打开PCM设备
SND_PCM_STREAM_PLAYBACK : 播放 传输数据 DA
SND_PCM_STREAM_CAPTURE : 采集 收集数据 AD
最后一个参数代表打开模式,0代表阻塞打开。SND_PCM_NONBLOCK无阻塞打开
*/
snd_pcm_open(&pcm_handle,"hw:0,0",SND_PCM_STREAM_PLAYBACK, 0);
/*
实例化snd_pcm_hw_params_t对象
snd_pcm_hw_params_t 用于描述PCM设备硬件配置参数
*/
snd_pcm_hw_params_t *hwparams = NULL;
snd_pcm_hw_params_malloc(&hwparams);
/*初始化snd_pcm_hw_params_t对象*/
snd_pcm_hw_params_any(pcm_handle, hwparams);
//设置交叉访问模式
snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
//设置音频格式
snd_pcm_hw_params_set_format(pcm_handle, hwparams,SND_PCM_FORMAT_S16_LE);
//设置声道数
snd_pcm_hw_params_set_channels(pcm_handle, hwparams,channels);
//设置采样率大小
snd_pcm_hw_params_set_rate(pcm_handle, hwparams,rate,0);
/*设置周期大小
原型:
int snd_pcm_hw_params_set_period_size(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_uframes_t val,
int dir )
alsa-lib使用snd_pcm_uframes_t类型表示帧的数量
val形参的单位是帧,代表多少帧,而不是代表多少字节。
一帧= 位数 * 通道数
*/
snd_pcm_hw_params_set_period_size(pcm_handle, hwparams, 1024, 0);
/*设置buffer大小
int snd_pcm_hw_params_set_buffer_size(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_uframes_t val )
val的单位也是帧
下面两个函数都是设置buffer缓冲区大小的
snd_pcm_hw_params_set_periods的val值是以周期为单位
*/
snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, 4*1024);
//snd_pcm_hw_params_set_periods(pcm_handle, hwparams, 4, 0);
//安装/加载硬件配置参数
snd_pcm_hw_params(pcm_handle, hwparams);
snd_pcm_hw_params_free(hwparams);
}
static int open_wav_file(char const *wav_name)
{
int fd;
RIFF_S riff;
FMT_S fmt;
fd = open(wav_name,O_RDONLY);
read(fd,&riff,sizeof(riff));
read(fd,&fmt,sizeof(fmt));
close(fd);
printf("ch:%d rate:%d\n",fmt.channels,fmt.sampleRate);
pcm_init(fmt.channels,fmt.sampleRate);
}
int main(int argc, char const *argv[])
{
int fd,ret;
short *audio_data_buf = malloc(4*1024);
open_wav_file(argv[1]);
fd = open(argv[1],O_RDONLY);
lseek(fd,SEEK_SET,44);
while(1)
{
memset(audio_data_buf,0,4*1024);
ret = read(fd,audio_data_buf,4*1024);
if( ret <= 0 )
break;
ret = snd_pcm_writei(pcm_handle,audio_data_buf,1024);
if( ret < 1024 )
lseek(fd, (ret-1024) * 4, SEEK_CUR);
}
free(audio_data_buf);
close(fd);
snd_pcm_close(pcm_handle);
return 0;
}
异步方式
pcm_init用于初始化pcm这个参数
用于注册异步处理函数
int snd_async_add_pcm_handler ( snd_async_handler_t **handler,
snd_pcm_t *pcm,
snd_async_callback_t callback, <-回调函数
void *private_data ) <-传给回调函数的实参
当缓冲区有空闲的周期时,音频设备驱动会向应用程序发送信号触发回调函数
录音情况下,用于获取当前可读取的帧数
播放情况下,用于获取当前可写入的帧数
snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
用于在回调函数中获取snd_pcm_t句柄
snd_async_handler_get_pcm(snd_async_handler_t *handler)
编译问题
没有alsa库
官网:https://www.alsa-project.org/main/index.php/Main_Page
下载网址:https://www.alsa-project.org/files/pub/lib/
1.1.6版本:
tar -jxf alsa-lib-1.1.6.tar.bz2
cd alsa-lib-1.1.6
//编译alsa-lib
./configure
--host=arm-linux-gnueabihf(根据自己情况修改)
--disable-python
--prefix=xxx(根据自己情况修改)
make
make install
//交叉编译程序
arm-linux-gnueabihf-gcc
pcm.c -o pcm
-I ~/alsa-lib/include/ // <- 重要
-L ~/alsa-lib/lib/ // <- 重要
-lasound // <- 重要
底层关系
常用函数
暂停PCM流
int snd_pcm_drop(snd_pcm_t * pcm)
立刻暂停pcm,缓冲区样本丢弃
int snd_pcm_drain(snd_pcm_t * pcm)
不是立刻暂停pcm,会对缓冲区进行处理