ubuntu播放pcm文件--alsa-lib

本文深入探讨Linux ALSA(高级Linux声音架构)音频框架,介绍其内核驱动层和用户空间API库alsa-lib的基本原理。通过修改示例代码解决在Ubuntu18.04上播放PCM文件时遇到的underrun问题,详细解析如何调整缓冲区大小以优化数据传输效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.youkuaiyun.com/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

<think>我们使用ALSA播放音频文件的主要步骤包括: 1. 打开PCM设备 2. 设置硬件参数(采样率、声道数、样本格式等) 3. 写入音频数据 4. 关闭设备 下面是一个使用ALSA库(alsa-lib播放音频文件的简单示例代码。这个示例假设音频文件是原始PCM数据(没有头部信息,如WAV文件头),因此如果是WAV文件,需要先跳过头部,或者使用其他库(如libsndfile)来解析。 但是,为了简单起见,我们先演示播放原始PCM数据的步骤。如果用户需要播放WAV文件,可以在读取文件时跳过头部(通常44字节)。 注意:以下代码需要安装alsa-lib开发包。在Ubuntu上,可以通过`sudo apt install libasound2-dev`安装。 示例代码(C语言):</think>### 使用ALSA在Linux系统播放音频文件的方法与示例 在Linux系统中使用ALSA播放音频文件需要直接操作PCM设备,处理音频数据的格式转换和设备控制。以下是完整实现方法和示例代码,基于ALSA库的核心功能实现[^1][^2]。 #### 核心步骤 1. **打开PCM设备**:初始化音频设备句柄 2. **配置硬件参数**:设置采样率、格式、声道数等 3. **准备音频数据**:读取文件并转换为PCM格式 4. **写入数据**:将音频数据写入设备缓冲区 5. **清理资源**:播放完成后释放资源 #### 完整示例代码(C语言) ```c #include <alsa/asoundlib.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define PCM_DEVICE "default" // 默认音频设备 #define BUFFER_SIZE 4096 // 缓冲区大小(帧数) int play_audio(const char *filename) { snd_pcm_t *pcm_handle; snd_pcm_hw_params_t *params; FILE *audio_file; char *buffer; int err; // 1. 打开PCM播放设备 if ((err = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "无法打开PCM设备: %s\n", snd_strerror(err)); return -1; } // 2. 分配硬件参数结构 snd_pcm_hw_params_alloca(&params); snd_pcm_hw_params_any(pcm_handle, params); // 3. 设置硬件参数 // 设置交错模式(标准音频数据布局) snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); // 设置16位小端格式(CD音质) snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE); // 设置2声道(立体声) snd_pcm_hw_params_set_channels(pcm_handle, params, 2); // 设置44100Hz采样率 unsigned int sample_rate = 44100; snd_pcm_hw_params_set_rate_near(pcm_handle, params, &sample_rate, 0); // 设置缓冲区大小(帧数) snd_pcm_uframes_t buffer_frames = BUFFER_SIZE; snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &buffer_frames); // 4. 应用参数到设备 if ((err = snd_pcm_hw_params(pcm_handle, params)) < 0) { fprintf(stderr, "无法设置硬件参数: %s\n", snd_strerror(err)); return -1; } // 5. 准备音频数据缓冲区 buffer = malloc(BUFFER_SIZE * 4); // 16位立体声 = 4字节/帧 if (!buffer) { fprintf(stderr, "内存分配失败\n"); return -1; } // 6. 打开音频文件 audio_file = fopen(filename, "rb"); if (!audio_file) { fprintf(stderr, "无法打开音频文件: %s\n", filename); free(buffer); return -1; } printf("开始播放: %s\n", filename); // 7. 播放循环 while (!feof(audio_file)) { size_t read = fread(buffer, 1, BUFFER_SIZE * 4, audio_file); if (read == 0) break; snd_pcm_sframes_t frames_written = snd_pcm_writei(pcm_handle, buffer, read / 4); // 错误处理:恢复中断状态 if (frames_written == -EPIPE) { fprintf(stderr, "缓冲区欠载\n"); snd_pcm_prepare(pcm_handle); } else if (frames_written < 0) { fprintf(stderr, "写入错误: %s\n", snd_strerror(frames_written)); } } // 8. 等待所有数据播放完成 snd_pcm_drain(pcm_handle); // 9. 清理资源 fclose(audio_file); free(buffer); snd_pcm_close(pcm_handle); printf("播放完成\n"); return 0; } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "用法: %s <音频文件>\n", argv[0]); return 1; } return play_audio(argv[1]); } ``` #### 编译与运行 1. 安装ALSA开发库: ```bash sudo apt-get install libasound2-dev # Debian/Ubuntu ``` 2. 编译程序: ```bash gcc alsa_player.c -o alsa_player -lasound ``` 3. 播放音频文件: ```bash ./alsa_player audio.pcm # 播放原始PCM文件 ``` #### 关键函数说明 1. `snd_pcm_open()`:打开PCM设备 2. `snd_pcm_hw_params_set_*()`:设置硬件参数(格式/声道/采样率) 3. `snd_pcm_writei()`:写入交错格式音频数据 4. `snd_pcm_drain()`:等待缓冲区数据全部播放完成 5. `snd_pcm_recover()`:处理缓冲区欠载错误(示例中通过`snd_pcm_prepare`恢复) #### 支持的文件格式 1. **原始PCM文件**:直接使用上述代码播放 2. **WAV文件**:需要跳过44字节文件头后再播放 ```c // 播放WAV文件时添加: fseek(audio_file, 44, SEEK_SET); // 跳过WAV文件头 ``` 3. **MP3/OGG等**:需要先用`libmad`或`libvorbis`解码为PCM格式 #### 注意事项 1. **权限问题**:确保用户有访问音频设备的权限(通常需要加入`audio`用户组) 2. **采样率匹配**:音频文件采样率应与设备设置一致 3. **缓冲区设置**:根据系统性能调整`BUFFER_SIZE`(值小=低延迟但易欠载,值大=高延迟更稳定)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值