加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 前序
    • ALSA 介紹
    • 實例
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

ALSA的入門介紹

2022/12/12
2076
閱讀需 50 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

前序

這里了解一下各個參數(shù)的含義以及一些基本概念。

聲音是連續(xù)模擬量,計算機將它離散化之后用數(shù)字表示,就有了以下幾個名詞術(shù)語。

樣本長度(sample):樣本是記錄音頻數(shù)據(jù)最基本的單位,計算機對每個通道采樣量化時數(shù)字比特位數(shù),常見的有8位和16位。

通道數(shù)(channel):該參數(shù)為1表示單聲道,2則是立體聲。

(frame):幀記錄了一個聲音單元,其長度為樣本長度與通道數(shù)的乘積,一段音頻數(shù)據(jù)就是由苦干幀組成的。

采樣率(rate):每秒鐘采樣次數(shù),該次數(shù)是針對幀而言,常用的采樣率如8KHz的人聲, 44.1KHz的mp3音樂, 96Khz的藍(lán)光音頻。

周期(period):音頻設(shè)備一次處理所需要的楨數(shù),對于音頻設(shè)備的數(shù)據(jù)訪問以及音頻數(shù)據(jù)的存儲,都是以此為單位。

交錯模式(interleaved):是一種音頻數(shù)據(jù)的記錄方式。在交錯模式下,數(shù)據(jù)以連續(xù)楨的形式存放,即首先記錄完楨1的左聲道樣本和右聲道樣本(假設(shè)為立體聲格式),再開始楨2的記錄。而在非交錯模式下,首先記錄的是一個周期內(nèi)所有楨的左聲道樣本,再記錄右聲道樣本,數(shù)據(jù)是以連續(xù)通道的方式存儲。不過多數(shù)情況下,我們只需要使用交錯模式就可以了。

period(周期): 硬件中中斷間的間隔時間。它表示輸入延時。

比特率(Bits Per Second):比特率表示每秒的比特數(shù),比特率=采樣率×通道數(shù)×樣本長度

ALSA 介紹

    ALSA基礎(chǔ)

ALSA由許多聲卡的聲卡驅(qū)動程序組成,同時它也提供一個稱為libasound的API庫。

應(yīng)用程序開發(fā)者應(yīng)該使用libasound而不是內(nèi)核中的 ALSA接口。因為libasound提供最高級并且編程方便的編程接口。并且提供一個設(shè)備邏輯命名功能,這樣開發(fā)者甚至不需要知道類似設(shè)備文件這樣的低層接口。

相反,OSS/Free驅(qū)動是在內(nèi)核系統(tǒng)調(diào)用級上編程,它要求開發(fā)者提供設(shè)備文件名并且利用ioctrl來實現(xiàn)相應(yīng)的功能。

為了向后兼容,ALSA提供內(nèi)核模塊來模擬OSS,這樣之前的許多在OSS基礎(chǔ)上開發(fā)的應(yīng)用程序不需要任何改動就可以在ALSA上運行。另外,libaoss庫也可以模擬OSS,而它不需要內(nèi)核模塊。

ALSA包含插件功能,使用插件可以擴展新的聲卡驅(qū)動,包括完全用軟件實現(xiàn)的虛擬聲卡。ALSA提供一系列基于命令行的工具集,比如混音器(mixer),音頻文件播放器(aplay),以及控制特定聲卡特定屬性的工具。

    ALSA體系結(jié)構(gòu)

ALSA API可以分解成以下幾個主要的接口:

    控制接口:提供管理聲卡注冊和請求可用設(shè)備的通用功能PCM接口:管理數(shù)字音頻回放(playback)和錄音(capture)的接口。本文后續(xù)總結(jié)重點放在這個接口上,因為它是開發(fā)數(shù)字音頻程序最常用到的接口。Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),標(biāo)準(zhǔn)的電子樂器。這些API提供對聲卡上MIDI總線的訪問。這個原始接口基于MIDI事件工作,由程序員負(fù)責(zé)管理協(xié)議以及時間處理。定時器(Timer)接口:為同步音頻事件提供對聲卡上時間處理硬件的訪問。時序器(Sequencer)接口混音器(Mixer)接口
    設(shè)備命名

API庫使用邏輯設(shè)備名而不是設(shè)備文件。設(shè)備名字可以是真實的硬件名字也可以是插件名字。硬件名字使用hw:i,j這樣的格式。其中i是卡號,j是這塊聲卡上的設(shè)備號。

第一個聲音設(shè)備是hw:0,0.這個別名默認(rèn)引用第一塊聲音設(shè)備并且在本文示例中一真會被用到。

插件使用另外的唯一名字,比如 plughw:,表示一個插件,這個插件不提供對硬件設(shè)備的訪問,而是提供像采樣率轉(zhuǎn)換這樣的軟件特性,硬件本身并不支持這樣的特性。

    聲音緩存和數(shù)據(jù)傳輸

每個聲卡都有一個硬件緩存區(qū)來保存記錄下來的樣本。當(dāng)緩存區(qū)足夠滿時,聲卡將產(chǎn)生一個中斷
內(nèi)核聲卡驅(qū)動然后使用直接內(nèi)存(DMA)訪問通道將樣本傳送到內(nèi)存中的應(yīng)用程序緩存區(qū)。類似地,對于回放,任何應(yīng)用程序使用DMA將自己的緩存區(qū)數(shù)據(jù)傳送到聲卡的硬件緩存區(qū)中。這樣硬件緩存區(qū)是環(huán)緩存。也就是說當(dāng)數(shù)據(jù)到達(dá)緩存區(qū)末尾時將重新回到緩存區(qū)的起始位置。

ALSA維護一個指針來指向硬件緩存以及應(yīng)用程序緩存區(qū)中數(shù)據(jù)操作的當(dāng)前位置。從內(nèi)核外部看,我們只對應(yīng)用程序的緩存區(qū)感興趣,所以本文只討論應(yīng)用程序緩存區(qū)。應(yīng)用程序緩存區(qū)的大小可以通過ALSA庫函數(shù)調(diào)用來控制。緩存區(qū)可以很大,一次傳輸操作可能會導(dǎo)致不可接受的延遲,我們把它稱為延時(latency)。

為了解決這個問題,ALSA將緩存區(qū)拆分成一系列周期(period)(OSS/Free中叫片斷fragments).ALSA以period為單元來傳送數(shù)據(jù)。一個周期(period)存儲一些幀(frames)。每一幀包含時間上一個點所抓取的樣本。對于立體聲設(shè)備,一個幀會包含兩個信道上的樣本。

分解過程:一個緩存區(qū)分解成周期,然后是幀,然后是樣本。左右信道信息被交替地存儲在一個幀內(nèi)。這稱為交錯 (interleaved)模式。在非交錯模式中,一個信道的所有樣本數(shù)據(jù)存儲在另外一個信道的數(shù)據(jù)之后。

    Over and Under Run

當(dāng)一個聲卡活動時,數(shù)據(jù)總是連續(xù)地在硬件緩存區(qū)和應(yīng)用程序緩存區(qū)間傳輸。

但是也有例外。在錄音例子中,如果應(yīng)用程序讀取數(shù)據(jù)不夠快,循環(huán)緩存區(qū)將會被新的數(shù)據(jù)覆蓋。這種數(shù)據(jù)的丟失被稱為"overrun"。在回放例子中,如果應(yīng)用程序?qū)懭霐?shù)據(jù)到緩存區(qū)中的速度不夠快,緩存區(qū)將會"餓死"。這樣的錯誤被稱為"underrun"。

在ALSA文檔中,有時將這兩種情形統(tǒng)稱為"XRUN"。適當(dāng)?shù)卦O(shè)計應(yīng)用程序可以最小化XRUN并且可以從中恢復(fù)過來。XRUN狀態(tài)又分有兩種,在播放時,用戶空間沒及時寫數(shù)據(jù)導(dǎo)致緩沖區(qū)空了,硬件沒有可用數(shù)據(jù)播放導(dǎo)致"underrun"; 錄制時,用戶空間沒有及時讀取數(shù)據(jù)導(dǎo)致緩沖區(qū)滿后溢出,硬件錄制的數(shù)據(jù)沒有空閑緩沖可寫導(dǎo)致"overrun".

當(dāng)用戶空間由于系統(tǒng)繁忙等原因,導(dǎo)致hw_ptr>appl_ptr時,緩沖區(qū)已空,內(nèi)核這里有兩種方案:

停止DMA傳輸,進(jìn)入XRUN狀態(tài)。這是內(nèi)核默認(rèn)的處理方法。 繼續(xù)播放緩沖區(qū)的重復(fù)的音頻數(shù)據(jù)或靜音數(shù)據(jù)。

用戶空間配置stop_threshold可選擇方案1或方案2,配置silence_threshold選擇繼續(xù)播放的原有的音頻數(shù)據(jù)還是靜意數(shù)據(jù)了。個人經(jīng)驗,偶爾的系統(tǒng)繁忙導(dǎo)致的這種狀態(tài),重復(fù)播放原有的音頻數(shù)據(jù)會顯得更平滑,效果更好。

音頻參數(shù)(ALSA 用戶空間之 TinyAlsa)

    TinyAlsa是 Android 默認(rèn)的 alsalib, 封裝了內(nèi)核 ALSA 的接口,用于簡化用戶空 間的 ALSA 編程。

合理的pcm_config可以做到更好的低時延和功耗,移動設(shè)備的開發(fā)優(yōu)為敏感。

struct?pcm_config?{
????unsigned?int?channels;
????unsigned?int?rate;
????unsigned?int?period_size;
????unsigned?int?period_count;
????enum?pcm_format?format;
????unsigned?int?start_threshold;
????unsigned?int?stop_threshold;
????unsigned?int?silence_threshold;
????int?avail_min;
};

解釋一下結(jié)構(gòu)中的各個參數(shù),每個參數(shù)的單位都是frame(1幀 = 通道*采樣位深):

    period_size. 每次傳輸?shù)臄?shù)據(jù)長度。值越小,時延越小,cpu占用就越高。period_count. 緩之沖區(qū)period的個數(shù)。緩沖區(qū)越大,發(fā)生XRUN的機會就越少。format. 定義數(shù)據(jù)格式,如采樣位深,大小端。start_threshold. 緩沖區(qū)的數(shù)據(jù)超過該值時,硬件開始啟動數(shù)據(jù)傳輸。如果太大, 從開始播放到聲音出來時延太長,甚至可導(dǎo)致太短促的聲音根本播不出來;如果太小, 又可能容易導(dǎo)致XRUN.stop_threshold. 緩沖區(qū)空閑區(qū)大于該值時,硬件停止傳輸。默認(rèn)情況下,這個數(shù) 為整個緩沖區(qū)的大小,即整個緩沖區(qū)空了,就停止傳輸。但偶爾的原因?qū)е戮彌_區(qū)空, 如CPU忙,增大該值,繼續(xù)播放緩沖區(qū)的歷史數(shù)據(jù),而不關(guān)閉再啟動硬件傳輸(一般此 時有明顯的聲音卡頓),可以達(dá)到更好的體驗。silence_threshold. 這個值本來是配合stop_threshold使用,往緩沖區(qū)填充靜音 數(shù)據(jù),這樣就不會重播歷史數(shù)據(jù)了。但如果沒有設(shè)定silence_size,這個值會生效嗎? 求解??avail_min. 緩沖區(qū)空閑區(qū)大于該值時,pcm_mmap_write()才往緩沖寫數(shù)據(jù)。這個 值越大,往緩沖區(qū)寫入數(shù)據(jù)的次數(shù)就越少,面臨XRUN的機會就越大。Android samsung tuna 設(shè)備在screen_off時增大該值以減小功耗,在screen_on時減小該 值以減小XRUN的機會。

在不同的場景下,合理的參數(shù)就是在性能、時延、功耗等之間達(dá)到較好的平衡。

有朋友問為什么在pcm_write()/pcm_mmap_write(),而不在pcm_open()調(diào)用pcm_start()? 這是因為音頻流與其它的數(shù)據(jù)不同,實時性要求很高。作為 TinyAlsa 的實現(xiàn)者,不能假定在調(diào)用者open之后及時的write數(shù)據(jù),所以只能在有數(shù)據(jù)寫入的時候start設(shè)備了。

    一個典型的聲音程序

    1. 使用PCM的程序通常類似下面的偽代碼:打開回放或錄音接口設(shè)置硬件參數(shù)(訪問模式,數(shù)據(jù)格式,信道數(shù),采樣率,等等)while 有數(shù)據(jù)要被處理:讀PCM數(shù)據(jù)(錄音) 或 寫PCM數(shù)據(jù)(回放)關(guān)閉接口

實例

顯示了一些ALSA使用的PCM數(shù)據(jù)類型和參數(shù)。

#include?<alsa/asoundlib.h>

int?main()?
{
????int?val;

????printf("ALSA?library?version:?%sn",
???????????????????????SND_LIB_VERSION_STR);

????printf("nPCM?stream?types:n");
????for?(val?=?0;?val?<=?SND_PCM_STREAM_LAST;?val++)
????????????printf("?%sn",
??????????????????snd_pcm_stream_name((snd_pcm_stream_t)val));

????printf("nPCM?access?types:n");
????for?(val?=?0;?val?<=?SND_PCM_ACCESS_LAST;?val++)
????{
????????????printf("?%sn",
??????????????????snd_pcm_access_name((snd_pcm_access_t)val));
????}

????printf("nPCM?formats:n");
????for?(val?=?0;?val?<=?SND_PCM_FORMAT_LAST;?val++)
????????{
????????if?(snd_pcm_format_name((snd_pcm_format_t)val)!=?NULL)
????????{
??????????????????printf("?%s?(%s)n",
????????????????????snd_pcm_format_name((snd_pcm_format_t)val),
????????????????????snd_pcm_format_description(
????????????????????????????(snd_pcm_format_t)val));
????????}
????}
????printf("nPCM?subformats:n");
????for?(val?=?0;?val?<=?SND_PCM_SUBFORMAT_LAST;val++)
????????{
????????printf("?%s?(%s)n",
??????????????????snd_pcm_subformat_name((
????????????????snd_pcm_subformat_t)val),
??????????????????snd_pcm_subformat_description((
????????????????snd_pcm_subformat_t)val));
????}
????printf("nPCM?states:n");
????for?(val?=?0;?val?<=?SND_PCM_STATE_LAST;?val++)
????????????printf("?%sn",
???????????????????snd_pcm_state_name((snd_pcm_state_t)val));

????return?0;
}

首先需要做的是包括頭文件。這些頭文件包含了所有庫函數(shù)的聲明。其中之一就是顯示ALSA庫的版本。這個程序剩下的部分的迭代一些PCM數(shù)據(jù)類型,以流類型開始。ALSA為每次迭代的最后值提供符號常量名,并且提供功能函數(shù)以顯示某個特定值的描述字符串。你將會看到,ALSA支持許多格式,在我的1.0.15版本里,支持多達(dá)36種格式。

這個程序必須鏈接到alsalib庫,通過在編譯時需要加上-lasound選項。有些alsa庫函數(shù)使用dlopen函數(shù)以及浮點操作,所以您可能還需要加上-ldl,-lm選項。編譯:gcc -o main test.c -lasound

打開默認(rèn)的PCM設(shè)備,設(shè)置一些硬件參數(shù)并且打印出最常用的硬件參數(shù)值

Int32?Audio_alsaSetparams(Alsa_Env?*pEnv,?int?verbose)
{
????????Int32?err?=?0;
????????Uint32?rate,?n;

????????snd_pcm_t?*handle;
????????snd_output_t?*log;

????????snd_pcm_hw_params_t?*params;
????????snd_pcm_sw_params_t?*swparams;?

????????snd_pcm_uframes_t?buffer_size;
????????snd_pcm_uframes_t?start_threshold,?stop_threshold;

????????unsigned?int?buffer_time,?period_time;

????????handle?=?pEnv->handle;

????????err?=?snd_output_stdio_attach(&log,?stderr,?0);
????????OSA_assert(err?>=?0);

????????snd_pcm_hw_params_alloca(&params);
????????snd_pcm_sw_params_alloca(&swparams);

????????err?=?snd_pcm_hw_params_any(handle,?params);
????????if?(err?<?0)?{?
????????????????AUD_DEVICE_PRINT_ERROR_AND_RETURN("Broken?configuration?for?this?PCM:"
??????????????????????????"no?configurations?available(%s)n",?err,?handle);?
????????}?

????????err?=?snd_pcm_hw_params_set_access(handle,?params,?SND_PCM_ACCESS_RW_INTERLEAVED);
????????if?(err?<?0)?{
????????????????AUD_DEVICE_PRINT_ERROR_AND_RETURN("cannot?set?access?type?(%s)n",?err,?handle);?
????????}?

????????err?=?snd_pcm_hw_params_set_format(handle,?params,?pEnv->format);
????????if?(err?<?0)?{?
????????????????AUD_DEVICE_PRINT_ERROR_AND_RETURN("cannot?set?sample?format?(%s)n",?err,?handle);?
????????}

????????err?=?snd_pcm_hw_params_set_channels(handle,?params,?pEnv->channels);?
????????if?(err?<?0)?{?
????????????????AUD_DEVICE_PRINT_ERROR_AND_RETURN("cannot?set?channel?count?(%s)n",?err,?handle);?
????????}

????????rate?=?pEnv->rate;
????????err?=?snd_pcm_hw_params_set_rate_near(handle,?params,?&pEnv->rate,?0);
????????OSA_assert(err?>=?0);

????????if?((float)rate?*?1.05?<?pEnv->rate?||?(float)rate?*?0.95?>?pEnv->rate)?{
????????????????fprintf(stderr,?"Warning:?rate?is?not?accurate"
????????????????????????"(requested?=?%iHz,?got?=?%iHz)n",?rate,?pEnv->rate);
????????}
????????rate?=?pEnv->rate;

????????/*?following?setting?of?period?size?is?done?only?for?AIC3X.?Leaving?default?for?HDMI?*/
????????if?(pEnv->resample)?{
????????????????/*?Restrict?a?configuration?space?to?contain?only?real?hardware?rates.?*/
????????????????snd_pcm_hw_params_set_rate_resample(handle,?params,?1);
????????}?

????????buffer_time?=?0;
????????period_time?=?0;
????????if?(pEnv->periods?==?0)?{
????????????????err?=?snd_pcm_hw_params_get_buffer_time_max(params,?&buffer_time,?0);
????????OSA_assert(err?>=?0);

????????/*?in?microsecond?*/
????????if?(buffer_time?>?500000)
????????????????buffer_time?=?500000;?/*?500ms?*/?
????????}

????????if?(buffer_time?>?0)
????????????????period_time?=?buffer_time?/?4;

????????if?(period_time?>?0)
????????????????err?=?snd_pcm_hw_params_set_period_time_near(handle,?params,
?????????????????????????????&period_time,?0);
????????else
????????????????err?=?snd_pcm_hw_params_set_period_size_near(handle,?params,
?????????????????????????????&pEnv->periods,?0);
????????OSA_assert(err?>=?0);

????????if?(period_time?>?0)?{
????????????????err?=?snd_pcm_hw_params_set_buffer_time_near(handle,?params,
?????????????????????????????&buffer_time,?0);
????????}?else?{
????????????????buffer_size?=?pEnv->periods?*?4;
????????????????err?=?snd_pcm_hw_params_set_buffer_size_near(handle,?params,
?????????????????????????????&buffer_size);
????????}
????????OSA_assert(err?>=?0);

????????err?=?snd_pcm_hw_params(handle,?params);
????????if?(err?<?0)?{?
????????????????fprintf(stderr,?"cannot?set?alsa?hw?parameters?%dn",?err);
????????????????return?err;?
????????}?

????????/*?Get?alsa?interrupt?duration?*/
????????snd_pcm_hw_params_get_period_size(params,?&pEnv->periods,?0);
????????snd_pcm_hw_params_get_buffer_size(params,?&buffer_size);
????????if?(pEnv->periods?==?buffer_size)?{
????????????????fprintf(stderr,?"Can't?use?period?equal?to?buffer?size?(%lu?==?%lu)n",
????????????????????????????????????????pEnv->periods,?buffer_size);
????????????????return?-1;
????????}

????????/*?set?software?params?*/
????????snd_pcm_sw_params_current(handle,?swparams);

????????n?=?pEnv->periods;
????????/*?set?minimum?avil?size?->?1?period?size?*/
????????err?=?snd_pcm_sw_params_set_avail_min(handle,?swparams,?n);
????????OSA_assert(err?>=?0);

????????n?=?buffer_size;
????????/*?in?microsecond?->?divide?1000000?*/
????????if?(pEnv->start_delay?<=?0)?
????????????????start_threshold?=?n?+?(double)rate?*?pEnv->start_delay?/?1000000;
????????else
????????????????start_threshold?=?(double)rate?*?pEnv->start_delay?/?1000000;

????????if?(start_threshold?<?1)
????????????????start_threshold?=?1;

????????if?(start_threshold?>?n)
????????????????start_threshold?=?n;

????????/*?set?pcm?auto?start?condition?*/
????????err?=?snd_pcm_sw_params_set_start_threshold(handle,?swparams,?start_threshold);
????????OSA_assert(err?>=?0);

????????/*?in?microsecond?->?divide?1000000?*/
????????if?(pEnv->stop_delay?<=?0)?
????????????????stop_threshold?=?buffer_size?+?(double)rate?*?pEnv->stop_delay?/?1000000;
????????else
????????????????stop_threshold?=?(double)rate?*?pEnv->stop_delay?/?1000000;

????????err?=?snd_pcm_sw_params_set_stop_threshold(handle,?swparams,?stop_threshold);
????????OSA_assert(err?>=?0);

????????err?=?snd_pcm_sw_params(handle,?swparams);
????????if?(err?<?0)?{
????????????????fprintf(stderr,?"unable?to?install?sw?paramsn");
????????????????return?err;
????????}

????????if?(verbose)
????????????????snd_pcm_dump(handle,?log);

????????snd_output_close(log);

????????return?err;
}
    snd_pcm_open打開默認(rèn)的PCM 設(shè)備并設(shè)置訪問模式為PLAYBACK。這個函數(shù)返回一個句柄,這個句柄保存在第一個函數(shù)參數(shù)中。該句柄會在隨后的函數(shù)中用到。像其它函數(shù)一樣,這個函數(shù)返回一個整數(shù)。如果返回值小于0,則代碼函數(shù)調(diào)用出錯。如果出錯,我們用snd_errstr打開錯誤信息并退出。為了設(shè)置音頻流的硬件參數(shù),我們需要分配一個類型為snd_pcm_hw_param的變量。分配用到函數(shù)宏 snd_pcm_hw_params_alloca。下一步,我們使用函數(shù)snd_pcm_hw_params_any來初始化這個變量,傳遞先前打開的 PCM流句柄。接下來,我們調(diào)用API來設(shè)置我們所需的硬件參數(shù)。
    這些函數(shù)需要三個參數(shù):PCM流句柄,參數(shù)類型,參數(shù)值。我們設(shè)置流為交錯模式,16位的樣本大小,2 個信道,44100bps的采樣率。對于采樣率而言,聲音硬件并不一定就精確地支持我們所定的采樣率,但是我們可以使用函數(shù) snd_pcm_hw_params_set_rate_near來設(shè)置最接近我們指定的采樣率的采樣率。其實只有當(dāng)我們調(diào)用函數(shù) snd_pcm_hw_params后,硬件參數(shù)才會起作用。程序的剩余部分獲得并打印一些PCM流參數(shù),包括周期和緩沖區(qū)大小。結(jié)果可能會因為聲音硬件的不同而不同。

運行該程序后,做實驗,改動一些代碼。把設(shè)備名字改成hw:0,0,然后看結(jié)果是否會有變化。設(shè)置不同的硬件參數(shù)然后觀察結(jié)果的變化。

添加聲音回放

/*

This?example?reads?standard?from?input?and?writes
to?the?default?PCM?device?for?5?seconds?of?data.

*/

/*?Use?the?newer?ALSA?API?*/
#define?ALSA_PCM_NEW_HW_PARAMS_API

#include?<alsa/asoundlib.h>

int?main()?
{
??long?loops;
??int?rc;
??int?size;
??snd_pcm_t?*handle;
??snd_pcm_hw_params_t?*params;
??unsigned?int?val;
??int?dir;
??snd_pcm_uframes_t?frames;
??char?*buffer;

??/*?Open?PCM?device?for?playback.?*/
??rc?=?snd_pcm_open(&handle,?"default",
????????????????????SND_PCM_STREAM_PLAYBACK,?0);
??if?(rc?<?0)
??{
????fprintf(stderr,"unable?to?open?pcm?device:?%sn",snd_strerror(rc));
????exit(1);
??}

??/*?Allocate?a?hardware?parameters?object.?*/
??snd_pcm_hw_params_alloca(?ms);

??/*?Fill?it?in?with?default?values.?*/
??snd_pcm_hw_params_any(handle,?params);

??/*?Set?the?desired?hardware?parameters.?*/

??/*?Interleaved?mode?*/
??snd_pcm_hw_params_set_access(handle,?params,
??????????????????????SND_PCM_ACCESS_RW_INTERLEAVED);

??/*?Signed?16-bit?little-endian?format?*/
??snd_pcm_hw_params_set_format(handle,?params,
??????????????????????????????SND_PCM_FORMAT_S16_LE);

??/*?Two?channels?(stereo)?*/
??snd_pcm_hw_params_set_channels(handle,?params,?2);

??/*?44100?bits/second?sampling?rate?(CD?quality)?*/
??val?=?44100;
??snd_pcm_hw_params_set_rate_near(handle,?params,
??????????????????????????????????&val,?&dir);

??/*?Set?period?size?to?32?frames.?*/
??frames?=?32;
??snd_pcm_hw_params_set_period_size_near(handle,
??????????????????????????????params,?&frames,?&dir);

??/*?Write?the?parameters?to?the?driver?*/
??rc?=?snd_pcm_hw_params(handle,?params);
??if?(rc?<?0)?{
????fprintf(stderr,
????????????"unable?to?set?hw?parameters:?%sn",
????????????snd_strerror(rc));
????exit(1);
??}

??/*?Use?a?buffer?large?enough?to?hold?one?period?*/
??snd_pcm_hw_params_get_period_size(params,?&frames,
????????????????????????????????????&dir);
??size?=?frames?*?4;?/*?2?bytes/sample,?2?channels?*/
??buffer?=?(char?*)?malloc(size);

??/*?We?want?to?loop?for?5?seconds?*/
??snd_pcm_hw_params_get_period_time(params,&val,?&dir);
??/*?5?seconds?in?microseconds?divided?by
???*?period?time?*/
??loops?=?5000000?/?val;

??while?(loops?>?0)?//循環(huán)錄音?5?s??
??{
????loops--;
????rc?=?read(0,?buffer,?size);
????if?(rc?==?0)?//沒有讀取到數(shù)據(jù)?
????{
??????fprintf(stderr,?"end?of?file?on?inputn");
??????break;
????}?
????else?if?(rc?!=?size)//實際讀取?的數(shù)據(jù)?小于?要讀取的數(shù)據(jù)?
????{
??????fprintf(stderr,"short?read:?read?%d?bytesn",?rc);
????}
????
????rc?=?snd_pcm_writei(handle,?buffer,?frames);//寫入聲卡??(放音)?
????if?(rc?==?-EPIPE)?
????{
??????/*?EPIPE?means?underrun?*/
??????fprintf(stderr,?"underrun?occurredn");
??????snd_pcm_prepare(handle);
????}?else?if?(rc?<?0)?{
??????fprintf(stderr,"error?from?writei:?%sn",snd_strerror(rc));
????}??else?if?(rc?!=?(int)frames)?{
??????fprintf(stderr,"short?write,?write?%d?framesn",?rc);
????}
??}

??snd_pcm_drain(handle);
??snd_pcm_close(handle);
??free(buffer);

??return?0;
}

在這個例子中,我們從標(biāo)準(zhǔn)輸入中讀取數(shù)據(jù),每個周期讀取足夠多的數(shù)據(jù),然后將它們寫入到聲卡中,直到5秒鐘的數(shù)據(jù)全部傳輸完畢。

這個程序的開始處和之前的版本一樣---打開PCM設(shè)備、設(shè)置硬件參數(shù)。我們使用由ALSA自己選擇的周期大小,申請該大小的緩沖區(qū)來存儲樣本。然后我們找出周期時間,這樣我們就能計算出本程序為了能夠播放5秒鐘,需要多少個周期。

在處理數(shù)據(jù)的循環(huán)中,我們從標(biāo)準(zhǔn)輸入中讀入數(shù)據(jù),并往緩沖區(qū)中填充一個周期的樣本。然后檢查并處理錯誤,這些錯誤可能是由到達(dá)文件結(jié)尾,或讀取的數(shù)據(jù)長度與我期望的數(shù)據(jù)長度不一致導(dǎo)致的。

我們調(diào)用snd_pcm_writei來發(fā)送數(shù)據(jù)。它操作起來很像內(nèi)核的寫系統(tǒng)調(diào)用,只是這里的大小參數(shù)是以幀來計算的。我們檢查其返回代碼值。返回值為EPIPE表明發(fā)生了underrun,使得PCM音頻流進(jìn)入到XRUN狀態(tài)并停止處理數(shù)據(jù)。從該狀態(tài)中恢復(fù)過來的標(biāo)準(zhǔn)方法是調(diào)用snd_pcm_prepare()函數(shù),把PCM流置于PREPARED狀態(tài),這樣下次我們向該PCM流中數(shù)據(jù)時,它就能重新開始處理數(shù)據(jù)。如果我們得到的錯誤碼不是EPIPE,我們把錯誤碼打印出來,然后繼續(xù)。最后,如果寫入的幀數(shù)不是我們期望的,則打印出錯誤消息。

這個程序一直循環(huán),直到5秒鐘的幀全部傳輸完,或者輸入流讀到文件結(jié)尾。然后我們調(diào)用snd_pcm_drain把所有掛起沒有傳輸完的聲音樣本傳輸完全,最后關(guān)閉該音頻流,釋放之前動態(tài)分配的緩沖區(qū),退出。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

針對嵌入式人工智能,物聯(lián)網(wǎng)等專業(yè)技術(shù)分享和交流平臺,內(nèi)容涉及arm,linux,android等各方面。