Linux下使用alsa-lib庫完成音頻開發: 實現放音和錄音(從聲卡獲取PCM數據保存、向聲卡寫PCM數據輸出)

一、環境介紹

系統: 虛擬機運行ubuntu18.04 (64位)

聲卡: 電腦自帶聲卡

二、安裝alsa-lib庫

參考文章: https://blog.csdn.net/xiaolong1126626497/article/details/104916277

三、參考代碼:從聲卡獲取PCM數據,實現錄音功能

 下面代碼在命令行通過gcc編譯運行:  讀取聲卡數據,保存爲文件,結束錄音可以按下Ctrl+C即可結束。

/*
 進行音頻採集,採集pcm數據並直接保存pcm數據
 音頻參數: 
	 聲道數:		1
	 採樣位數:	16bit、LE格式
	 採樣頻率:	44100Hz

運行示例:
$ gcc linux_pcm_save.c -lasound
$ ./a.out hw:0 123.pcm

*/

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <signal.h>

#define AudioFormat SND_PCM_FORMAT_S16_LE  //指定音頻的格式,其他常用格式:SND_PCM_FORMAT_U24_LE、SND_PCM_FORMAT_U32_LE
#define AUDIO_CHANNEL_SET   1  			  //1單聲道   2立體聲
#define AUDIO_RATE_SET 44100   //音頻採樣率,常用的採樣頻率: 44100Hz 、16000HZ、8000HZ、48000HZ、22050HZ

FILE *pcm_data_file=NULL;
int run_flag=0;
void exit_sighandler(int sig)
{
	run_flag=1;
}

int main(int argc, char *argv[])
{
	int i;
	int err;
	char *buffer;
	int buffer_frames = 1024;
	unsigned int rate = AUDIO_RATE_SET;
	snd_pcm_t *capture_handle;// 一個指向PCM設備的句柄
	snd_pcm_hw_params_t *hw_params; //此結構包含有關硬件的信息,可用於指定PCM流的配置
	
	/*註冊信號捕獲退出接口*/
	signal(2,exit_sighandler);

	/*PCM的採樣格式在pcm.h文件裏有定義*/
	snd_pcm_format_t format=AudioFormat; // 採樣位數:16bit、LE格式

	/*打開音頻採集卡硬件,並判斷硬件是否打開成功,若打開失敗則打印出錯誤提示*/
	// SND_PCM_STREAM_PLAYBACK 輸出流
	// SND_PCM_STREAM_CAPTURE  輸入流
	if ((err = snd_pcm_open (&capture_handle, argv[1],SND_PCM_STREAM_CAPTURE,0))<0) 
	{
		printf("無法打開音頻設備: %s (%s)\n",  argv[1],snd_strerror (err));
		exit(1);
	}
	printf("音頻接口打開成功.\n");

	/*創建一個保存PCM數據的文件*/
	if((pcm_data_file = fopen(argv[2], "wb")) == NULL)
	{
		printf("無法創建%s音頻文件.\n",argv[2]);
		exit(1);
	} 
	printf("用於錄製的音頻文件已打開.\n");

	/*分配硬件參數結構對象,並判斷是否分配成功*/
	if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) 
	{
		printf("無法分配硬件參數結構 (%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("硬件參數結構已分配成功.\n");
	
	/*按照默認設置對硬件對象進行設置,並判斷是否設置成功*/
	if((err=snd_pcm_hw_params_any(capture_handle,hw_params)) < 0) 
	{
		printf("無法初始化硬件參數結構 (%s)\n", snd_strerror(err));
		exit(1);
	}
	printf("硬件參數結構初始化成功.\n");

	/*
		設置數據爲交叉模式,並判斷是否設置成功
		interleaved/non interleaved:交叉/非交叉模式。
		表示在多聲道數據傳輸的過程中是採樣交叉的模式還是非交叉的模式。
		對多聲道數據,如果採樣交叉模式,使用一塊buffer即可,其中各聲道的數據交叉傳輸;
		如果使用非交叉模式,需要爲各聲道分別分配一個buffer,各聲道數據分別傳輸。
	*/
	if((err = snd_pcm_hw_params_set_access (capture_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) 
	{
		printf("無法設置訪問類型(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("訪問類型設置成功.\n");

	/*設置數據編碼格式,並判斷是否設置成功*/
	if ((err=snd_pcm_hw_params_set_format(capture_handle, hw_params,format)) < 0) 
	{
		printf("無法設置格式 (%s)\n",snd_strerror(err));
		exit(1);
	}
	fprintf(stdout, "PCM數據格式設置成功.\n");

	/*設置採樣頻率,並判斷是否設置成功*/
	if((err=snd_pcm_hw_params_set_rate_near(capture_handle,hw_params,&rate,0))<0) 
	{
		printf("無法設置採樣率(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("採樣率設置成功\n");

	/*設置聲道,並判斷是否設置成功*/
	if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params,AUDIO_CHANNEL_SET)) < 0) 
	{
		printf("無法設置聲道數(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("聲道數設置成功.\n");

	/*將配置寫入驅動程序中,並判斷是否配置成功*/
	if ((err=snd_pcm_hw_params (capture_handle,hw_params))<0) 
	{
		printf("無法向驅動程序設置參數(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("參數設置成功.\n");

	/*使採集卡處於空閒狀態*/
	snd_pcm_hw_params_free(hw_params);

	/*準備音頻接口,並判斷是否準備好*/
	if((err=snd_pcm_prepare(capture_handle))<0) 
	{
		printf("無法使用音頻接口 (%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("音頻接口準備好.\n");

	/*配置一個數據緩衝區用來緩衝數據*/
	//snd_pcm_format_width(format) 獲取樣本格式對應的大小(單位是:bit)
	int frame_byte=snd_pcm_format_width(format)/8;
	buffer=malloc(buffer_frames*frame_byte*AUDIO_CHANNEL_SET); //2048
	printf("緩衝區分配成功.\n");
	
	/*開始採集音頻pcm數據*/
	printf("開始採集數據...\n");
	while(1) 
	{
		/*從聲卡設備讀取一幀音頻數據:2048字節*/
		if((err=snd_pcm_readi(capture_handle,buffer,buffer_frames))!=buffer_frames) 
		{
			  printf("從音頻接口讀取失敗(%s)\n",snd_strerror(err));
			  exit(1);
		}
		/*寫數據到文件: 音頻的每幀數據樣本大小是16位=2個字節*/
		fwrite(buffer,(buffer_frames*AUDIO_CHANNEL_SET),frame_byte,pcm_data_file);	
		if(run_flag)
		{
			printf("停止採集.\n");
			break;
		}
	}

	/*釋放數據緩衝區*/
	free(buffer);

	/*關閉音頻採集卡硬件*/
	snd_pcm_close(capture_handle);

	/*關閉文件流*/
	fclose(pcm_data_file);
	return 0;
}

四、參考代碼:從文件讀取PCM數據,再寫入到聲卡設備,實現聲音播放功能

 下面代碼在命令行通過gcc編譯運行:  讀取文件PCM音頻數據,寫入到聲卡進行播放,結束播放可以按下Ctrl+C即可結束。

/*
 進行音頻採集,讀取存放pcm數據的文件通過聲卡進行播放
 音頻參數: 
	 聲道數:		1
	 採樣位數:	16bit、LE格式
	 採樣頻率:	44100Hz
	 
運行示例:
$ gcc linux_pcm_save.c -lasound
$ ./a.out hw:0 123.pcm
*/

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <signal.h>

#define AudioFormat SND_PCM_FORMAT_S16_LE  //指定音頻的格式,其他常用格式:SND_PCM_FORMAT_U24_LE、SND_PCM_FORMAT_U32_LE
#define AUDIO_CHANNEL_SET   1  			  //1單聲道   2立體聲
#define AUDIO_RATE_SET 44100   //音頻採樣率,常用的採樣頻率: 44100Hz 、16000HZ、8000HZ、48000HZ、22050HZ

FILE *pcm_data_file=NULL;
int run_flag=0;
void exit_sighandler(int sig)
{
	run_flag=1;
}


//
int main(int argc, char *argv[])
{
	int i;
	int err;
	char *buffer;
	int buffer_frames = 1024;
	unsigned int rate = AUDIO_RATE_SET;
	snd_pcm_t *capture_handle;// 一個指向PCM設備的句柄
	snd_pcm_hw_params_t *hw_params; //此結構包含有關硬件的信息,可用於指定PCM流的配置
	
	/*註冊信號捕獲退出接口*/
	signal(2,exit_sighandler);

	/*PCM的採樣格式在pcm.h文件裏有定義*/
	snd_pcm_format_t format=AudioFormat; // 採樣位數:16bit、LE格式

	/*打開音頻採集卡硬件,並判斷硬件是否打開成功,若打開失敗則打印出錯誤提示*/
	// SND_PCM_STREAM_PLAYBACK 輸出流
	// SND_PCM_STREAM_CAPTURE  輸入流
	if ((err = snd_pcm_open (&capture_handle, argv[1],SND_PCM_STREAM_PLAYBACK,0))<0) 
	{
		printf("無法打開音頻設備: %s (%s)\n",  argv[1],snd_strerror (err));
		exit(1);
	}
	printf("音頻接口打開成功.\n");

	/*打開存放PCM數據的文件*/
	if((pcm_data_file = fopen(argv[2], "rb")) == NULL)
	{
		printf("無法打開%s音頻文件.\n",argv[2]);
		exit(1);
	} 
	printf("用於播放的音頻文件已打開.\n");

	/*分配硬件參數結構對象,並判斷是否分配成功*/
	if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) 
	{
		printf("無法分配硬件參數結構 (%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("硬件參數結構已分配成功.\n");
	
	/*按照默認設置對硬件對象進行設置,並判斷是否設置成功*/
	if((err=snd_pcm_hw_params_any(capture_handle,hw_params)) < 0) 
	{
		printf("無法初始化硬件參數結構 (%s)\n", snd_strerror(err));
		exit(1);
	}
	printf("硬件參數結構初始化成功.\n");

	/*
		設置數據爲交叉模式,並判斷是否設置成功
		interleaved/non interleaved:交叉/非交叉模式。
		表示在多聲道數據傳輸的過程中是採樣交叉的模式還是非交叉的模式。
		對多聲道數據,如果採樣交叉模式,使用一塊buffer即可,其中各聲道的數據交叉傳輸;
		如果使用非交叉模式,需要爲各聲道分別分配一個buffer,各聲道數據分別傳輸。
	*/
	if((err = snd_pcm_hw_params_set_access (capture_handle,hw_params,SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) 
	{
		printf("無法設置訪問類型(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("訪問類型設置成功.\n");

	/*設置數據編碼格式,並判斷是否設置成功*/
	if ((err=snd_pcm_hw_params_set_format(capture_handle, hw_params,format)) < 0) 
	{
		printf("無法設置格式 (%s)\n",snd_strerror(err));
		exit(1);
	}
	fprintf(stdout, "PCM數據格式設置成功.\n");

	/*設置採樣頻率,並判斷是否設置成功*/
	if((err=snd_pcm_hw_params_set_rate_near(capture_handle,hw_params,&rate,0))<0) 
	{
		printf("無法設置採樣率(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("採樣率設置成功\n");

	/*設置聲道,並判斷是否設置成功*/
	if((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params,AUDIO_CHANNEL_SET)) < 0) 
	{
		printf("無法設置聲道數(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("聲道數設置成功.\n");

	/*將配置寫入驅動程序中,並判斷是否配置成功*/
	if ((err=snd_pcm_hw_params (capture_handle,hw_params))<0) 
	{
		printf("無法向驅動程序設置參數(%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("參數設置成功.\n");

	/*使採集卡處於空閒狀態*/
	snd_pcm_hw_params_free(hw_params);

	/*準備音頻接口,並判斷是否準備好*/
	if((err=snd_pcm_prepare(capture_handle))<0) 
	{
		printf("無法使用音頻接口 (%s)\n",snd_strerror(err));
		exit(1);
	}
	printf("音頻接口準備好.\n");

	/*配置一個數據緩衝區用來緩衝數據*/
	//snd_pcm_format_width(format) 獲取樣本格式對應的大小(單位是:bit)
	int frame_byte=snd_pcm_format_width(format)/8;
	buffer=malloc(buffer_frames*frame_byte*AUDIO_CHANNEL_SET);
	printf("緩衝區分配成功.\n");
	
	/*開始採集音頻pcm數據*/
	printf("開始播放音頻數據...\n");
	
	int read_cnt;
	while(1) 
	{
		/*從文件讀取數據: 音頻的每幀數據樣本大小是16位-2個字節*/
		read_cnt=fread(buffer,1,frame_byte*(buffer_frames*AUDIO_CHANNEL_SET),pcm_data_file);
		if(read_cnt<=0)break;
		
		/*向聲卡設備寫一幀音頻數據:2048字節*/
		if((err=snd_pcm_writei(capture_handle,buffer,buffer_frames))!=buffer_frames) 
		{
			  printf("向音頻接口寫數據失敗(%s)\n",snd_strerror(err));
			  exit(1);
		}
			
		if(run_flag)
		{
			printf("停止播放.\n");
			break;
		}
	}
	printf("播放完成.\n");
	/*釋放數據緩衝區*/
	free(buffer);

	/*關閉音頻採集卡硬件*/
	snd_pcm_close(capture_handle);

	/*關閉文件流*/
	fclose(pcm_data_file);
	return 0;
}

 

下面公衆號有全套基礎的C\C++\單片機\QT教程:

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