Linux音訊採集和在國產化平臺中遇到的坑(一)

2023-01-31 12:01:37

Linux音訊採集和在國產化平臺中遇到的坑(一)

最近在做一個國產化平臺的軟體專案的開發,是基於國產晶片的銀河麒麟系統。其中有一個重要模組,是採集和播放音訊資料,播放不用多說了,採集的話,包括採集麥克風和採集桌面系統聲音。很多人都覺得銀河麒麟不就是linux麼,那不直接用ALSA就好了,我原本也是這麼想的,但是實際開發下來才發現,還是有各種坑需要自己去趟的。這裡我簡單記錄一下。

雖然都是linux,晶片也是基於同樣的架構,同樣的指令集,但是考慮到晶片的實現畢竟是不同的,於是所有涉及到硬體互動的軟體部分,也會有所差異,最終會導致了有些應用層面的介面,不能按照普通linux的通常用法去使用。

linux ALSA音訊採集

首先,銀河麒麟既然是linux系統,那首先考慮到的是通過ALSA(Advanced Linux Sound Architecture)來進行採集,ALSA是linux的預設音效卡驅動,同時在使用者層還有一個ALSA Lib來供應用程式呼叫,它的整體上的結構圖是這個樣子的:

應用程式通常都是通過alsa-lib來使用,如果系統沒有的話,可以通過命令安裝開發庫,就可以使用了。例如

sudo apt-get install libasound2-dev

另外需要注意一點的是,如果是android系統,那麼系統裡通常是不存在alsa的,而是它的簡化版tiny-alsa,介面名稱也不一樣,但是大致呼叫流程是相同的。

alsa音訊採集,有幾個關鍵函數

#include <sys/asoundlib.h>

/***
 建立alsa pcm handle去連線裝置
 @param handle: 返回建立的PCM handle
 @param name: 裝置名稱,ASCII編碼
 @param stream: 標明採集或者播放(SND_PCM_STREAM_CAPTURE, SND_PCM_STREAM_PLAYBACK)
 @param mode: 開啟模式(see SND_PCM_NONBLOCK, SND_PCM_ASYNC)
 @return: 0表示成功,小於0表示錯誤
*/
int snd_pcm_open( snd_pcm_t **handle, const char* name, int stream, int mode );

/***
 讀取音訊幀
 @param handle: PCM handle
 @param buffer: frames containing buffer
 @param size: frames to be read
 @return: 實際讀取的音訊幀個數,小於0表示錯誤
*/
ssize_t snd_pcm_readi( snd_pcm_t *handle, void *buffer, size_t size );

/***
 關閉
 @param handle: PCM handle
 @return: 實際讀取的音訊幀個數,小於0表示錯誤
*/
int snd_pcm_close( snd_pcm_t *handle );

/***
 準備使用PCM
 @param handle: PCM handle
 @return: 實際讀取的音訊幀個數,小於0表示錯誤
*/
int snd_pcm_prepare( snd_pcm_t *handle );

介面簡單,引數也少,所以使用起來很方便,基本上是linux下采集和播放的第一選擇,下面寫個簡單的例子演示下如何呼叫

  1. 開啟音訊裝置並設定引數
SIMPLE_LOG("try open %s\n", device_name_.c_str());

int ret = snd_pcm_open(&alsa_pcm_, device_name_.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (!alsa_pcm_ || ret < 0)
{
	SIMPLE_LOG("open %s failed, ret: %d\n", device_name_.c_str(), ret);

	return false;
}

snd_pcm_hw_params_t* params;

snd_pcm_hw_params_alloca (&params);
snd_pcm_hw_params_any (alsa_pcm_, params);
snd_pcm_hw_params_set_access (alsa_pcm_, params,
	SND_PCM_ACCESS_RW_INTERLEAVED);

snd_pcm_format_t format;
switch (bits_per_sam_) {
case 8:
	format = SND_PCM_FORMAT_S8;
	break;
case 16:
	format = SND_PCM_FORMAT_S16_LE;
	break;
case 24:
	format = SND_PCM_FORMAT_S24_LE;
	break;
case 32:
	format = SND_PCM_FORMAT_S32_LE;
	break;
default:
	format = SND_PCM_FORMAT_S16_LE;
	break;
}
snd_pcm_hw_params_set_format (alsa_pcm_, params, format);

snd_pcm_hw_params_set_channels (alsa_pcm_, params, channel_count_);

unsigned int rate = sample_rate_;
snd_pcm_hw_params_set_rate_near (alsa_pcm_, params, &rate, NULL);

sample_size_ = channel_count_ * (bits_per_sam_/8);

/* Activate the parameters */
ret = snd_pcm_hw_params (alsa_pcm_, params);
if (ret < 0)
{
	SIMPLE_LOG("set param failed, ret: %d\n", ret);

	snd_pcm_close (alsa_pcm_);
	alsa_pcm_ = NULL;
	return false;
}
  1. 讀取音訊資料
bool AlsaCapture::ReadData()
{
    int read_size = 0;
    snd_pcm_uframes_t need_frames = real_sample_count_;
    for (;;)
    {
        if (read_size >= pcm_buf_.size())
        {
            break;
        }

        int ret = 0;
        while (true)
        {
            char* read_buf = &pcm_buf_[0] + read_size;
            ret = snd_pcm_readi(alsa_pcm_, read_buf, need_frames);
            if (ret >= 0)
            {
                break;
            }

            if (ret == -EAGAIN)
            {
                SIMPLE_LOG("snd_pcm_readi EAGAIN\n");
                return false;
            }

            if (AlsaXRunRecover(alsa_pcm_, ret) < 0)
            {
                SIMPLE_LOG("ALSA read error: %s\n", snd_strerror(ret));
                return false;
            }
        }

        read_size += ret * sample_size_;
        need_frames -= ret;
    }
    return true;
}

這樣就可以完成音訊資料的採集,需要注意的是,在第二步讀取資料之前,需要先呼叫snd_pcm_prepare,否則是無法驅動資料採集正常進行的。

在國產化晶片平臺上出現的問題

在普通Linux下,這樣寫下來,就可以實現想要的音訊採集功能了,後面對資料做進一步的規整和編碼就可以傳送了。但是在某個國產晶片平臺的銀河麒麟系統下,我卻遇到了一個問題,那就是開啟裝置的函數呼叫以及所有的引數設定都是成功的,但是資料採集卻總是異常,要麼返回無意義噪聲資料,要麼read介面乾脆就報EAGAIN錯誤。

剛開始我以為是ALSA預設裝置的問題,因為出問題的國產化晶片平臺,有兩個音效卡,其中一個是可以正常使用的,另一個是無效音效卡。這些資訊可以通過使用命令列來檢視,例如:

檢視音效卡:

cat /proc/asound/cards

檢視採集裝置:

sudo arecord -l

檢視播放裝置:

cat aplay -l

於是我嘗試通過系統設定的方式,來設定預設音效卡,這裡推薦一個工具「alsamixer」,是一個字元化介面的ALSA設定工具,可以通過如下命令安裝:

sudo apt-get install alsa-utils

啟動後就是一個這樣的介面

然而,修改以後,發現預設裝置的修改,並不能影響到alsa採集的結果。於是通過羅列所有錄音裝置,並且指定裝置名稱,但是仍然出現同樣的結果。在多次嘗試無果以後,最終只能放棄使用ALSA來進行音訊裝置的資料採集,而採用複雜一些的PulseAudio框架。最後的結果也證明,更加上層的PulseAudio還是正確的處理了有效的音訊裝置和無效音訊裝置,並正確返回了麥克風/桌面系統聲音。具體過程我下一篇再寫。

合作請加QQ或微信hbstream。(轉載請註明作者和出處)