ubuntu播放pcm文件--alsa-lib

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章