linux 音频框架使用 alsa(Advanced Linux Sound Architecture). alsa框架分为两个部分,一个是在内核的driver层,定义驱动的规范,一个是在用户空间的api库,在用户空间的api就是 alsa-lib. 默认的用户空间的alsa-lib ubuntu应该已经装了,当然也可以自己手动下载源码安装,毕竟只是一个应用程序库, 如果熟悉alsa驱动层的接口的话,也可以完全基于驱动来编写播放,录音程序,而不必使用 alsa-lib. 比如android 默认使用的是 tinyalsa, 顾名思义这是一个微型的轻量级alsa-api 接口。抛开了linux默认的alsa-lib,因为alsa-lib很多功能android用不到,比较冗余。
本篇基于 alsa-lib, 在ubuntu18.04中播放pcm文件。ubuntu 默认的已经安装,头文件可以看到存在于 /usr/include/alsa 目录下。代码参考网络其他资源,但有些修改。修改在于:
默认的播放在snd_pcm_writei() 写函数总是返回 -EPIPE, 也就是出现underrun,写数据太慢(可能是因为虚拟机的缘故),直接增大缓冲区,修改period_size.
当一个声卡活动时,数据总是连续地在硬件缓存区和应用程序缓存区间之间传输,硬件按照我们设置的参数 snd_pcm_hw_params_get_period_size() period_size 传输周期来传输数据,单位是“帧”,即每一个传输周期传输 period_size个帧,这个也就是可以理解为“缓冲区”大小,如果设置得太小,并且用户输入数据的速度又太慢,就导致硬件来读数据的时候没有足够的数据可读,即 underrun。 太大,当然延迟就比较大,应用程序可以准备好足够的数据之后在调用snd_pcm_writei() //这个是阻塞的,播放完之后才返回。
/* 参考 https://blog.csdn.net/rookie_wei/article/details/80460114 文章,修改而来
**/
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>
typedef unsigned int u32;
typedef unsigned char u8;
typedef unsigned short u16;
static snd_pcm_t *gp_handle; //调用snd_pcm_open打开PCM设备返回的文件句柄,后续的操作都使用是、这个句柄操作这个PCM设备
static snd_pcm_hw_params_t *gp_params; //设置流的硬件参数
static snd_pcm_uframes_t g_frames; //snd_pcm_uframes_t其实是unsigned long类型
static char *gp_buffer;
static u32 g_bufsize;
int set_hardware_params(int sample_rate, int channels, int format_size)
{
printf("set_hardware_params rate:%d channelse %d format_size %d\n",sample_rate,channels,format_size);
int rc;
/* Open PCM device for playback */
//rc = snd_pcm_open(&gp_handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
rc = snd_pcm_open(&gp_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
{
printf("unable to open pcm device\n");
return -1;
}
/* Allocate a hardware parameters object */
snd_pcm_hw_params_alloca(&gp_params);
/* Fill it in with default values. */
rc = snd_pcm_hw_params_any(gp_handle, gp_params);
if (rc < 0)
{
printf("unable to Fill it in with default values.\n");
goto err1;
}
/* Interleaved mode */ //交错模式,理解应该只是 多通道才有效吧。两个通道数据交错存储传输
rc = snd_pcm_hw_params_set_access(gp_handle, gp_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0)
{
printf("unable to Interleaved mode.\n");
goto err1;
}
snd_pcm_format_t format;
if (8 == format_size)
{
format = SND_PCM_FORMAT_U8;
}
else if (16 == format_size)
{
format = SND_PCM_FORMAT_S16_LE;
}
else if (24 == format_size)
{
format = SND_PCM_FORMAT_U24_LE;
}
else if (32 == format_size)
{
format = SND_PCM_FORMAT_U32_LE;
}
else
{
printf("SND_PCM_FORMAT_UNKNOWN.\n");
format = SND_PCM_FORMAT_UNKNOWN;
goto err1;
}
/* set format */
rc = snd_pcm_hw_params_set_format(gp_handle, gp_params, format);
if (rc < 0)
{
printf("unable to set format.\n");
goto err1;
}
/* set channels (stero) */
snd_pcm_hw_params_set_channels(gp_handle, gp_params, channels);
if (rc < 0)
{
printf("unable to set channels (stero).\n");
goto err1;
}
/* set sampling rate */
u32 dir;
rc = snd_pcm_hw_params_set_rate_near(gp_handle, gp_params, &sample_rate, &dir);
if (rc < 0)
{
printf("unable to set sampling rate.\n");
goto err1;
}
/* Set period size to ** frames. */
g_frames = 1024*6;//默认的1024,个人系统环境来说,太小,增大到6倍,ok
snd_pcm_hw_params_set_period_size_near(gp_handle,gp_params, &g_frames, &dir);
/* Write the parameters to the dirver */
rc = snd_pcm_hw_params(gp_handle, gp_params);
if (rc < 0) {
printf("unable to set hw parameters: %s\n", snd_strerror(rc));
goto err1;
}
snd_pcm_hw_params_get_period_size(gp_params, &g_frames, &dir);
printf("g_framse %lu \n",g_frames);
//g_bufsize = g_frames * 4; //双通道,每个采样样本 16bit,即 2*16=32bit,4字节
g_bufsize = g_frames*channels*format_size/8;
gp_buffer = (u8 *)malloc(g_bufsize);
if (gp_buffer == NULL)
{
printf("malloc failed\n");
goto err1;
}
return 0;
err1:
snd_pcm_close(gp_handle);
return -1;
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
printf("usage: %s filename.pcm sample_rate channels format_size\n like ./pcmplayer 1.pcm 44100 2 16\n", argv[0]);
return -1;
}
FILE * fp = fopen(argv[1], "r");
if (fp == NULL)
{
printf("can't open wav file\n");
return -1;
}
int sample_rate = atoi(argv[2]);
int channels = atoi(argv[3]);
int format_size = atoi(argv[4]);
int ret = set_hardware_params(sample_rate, channels, format_size);
if (ret < 0)
{
printf("set_hardware_params error\n");
return -1;
}
size_t rc;
while (1)
{
rc = fread(gp_buffer, g_bufsize, 1, fp);
if (rc <1)
{
break;
}
ret = snd_pcm_writei(gp_handle, gp_buffer, g_frames);
if (ret == -EPIPE) {
printf("underrun occured!! compute too slow????? maybe need to increate period_size \n");
snd_pcm_prepare(gp_handle);
//break;
}
else if (ret < 0) {
printf("error from writei: %s\n", snd_strerror(ret));
break;
}
}
snd_pcm_drain(gp_handle);
snd_pcm_close(gp_handle);
free(gp_buffer);
fclose(fp);
return 0;
}
编译:#gcc XX.c -lasound 或者 #gcc XX.c -lasound -ldl -lm