作者:星隕
來源:
音視訊開發進階 SDL 播放音頻檔案有兩種方法,可以了解成推(push)
和 拉(pull)
兩種模式。 推
就是我們主動向裝置緩沖區填充 Buffer ,而 拉
就是由裝置拉取 Buffer
填充到緩沖區。
在一些開發模型中,如果資料傳遞能夠抽象成
流
的形式,那麼肯定就會有 推
拉
本篇文章主要是講解 SDL 以推的形式播放音頻檔案。 PCM 檔案素材準備
首先還是得準備素材,做音視訊相關實驗就是這麼麻煩~~
找一個 mp3 檔案,使用 FFmpeg 指令将它轉換成 pcm 檔案,友善的話可以直接使用代碼倉庫提供的 mp3 檔案。
不像在視訊播放中準備素材那樣簡單,音頻檔案對于參數的資訊要求多一點。首先要使用 ffmpeg 檢視 mp3 檔案的一些資訊,比如采樣率、聲道數等。
ffmpeg -i file_name.mp3

得到如圖所示的資訊,可以看到 mp3 檔案采樣率是 44100 Hz,雙聲道,再使用 FFmpeg 轉換時要用到上面的資訊。
ffmpeg -i file_name.mp3 -acodec pcm_s16le -f s16le -ac 2 -ar 44100 file_name.pcm
其中:
-acodec pcm_s16le
·指定編碼器
-f s16le
·指定檔案格式,是大端模式還是小端模式
-ac 2
·指定通道數,2 代表雙通道
-ar 44100
·指定采樣率,這裡是 44100 Hz
在轉換時要根據原檔案的采樣率和聲道數進行轉換,否則轉換後的 pcm 檔案播放聲音不對了。
ffplay -ar 44100 -channels 2 -f s16le -i file_name.pcm
通過上面的指令可以驗證轉換是否正确,還是要注意聲道數和采樣率的設定,如果沒問題的話,說明 PCM 檔案素材就準備完畢,可以進行代碼實踐了。
代碼實踐
首先要通過
SDL_OpenAudioDevice
方法打開一個音頻裝置。
SDL_OpenAudioDevice(const char
*device,
int iscapture,
const SDL_AudioSpec * desired,
SDL_AudioSpec *obtained,
int allowed_changes);
其中結構體
SDL_AudioSpec
指定了一系列音頻相關的參數,具體如下:
typedef struct SDL_AudioSpec
{
int freq; /**< DSP frequency -- samples per second */
SDL_AudioFormat format; /**< Audio data format */
Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */
Uint8 silence; /**< Audio buffer silence value (calculated) */
Uint16 samples; /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */
Uint16 padding; /**< Necessary for some compile environments */
Uint32 size; /**< Audio buffer size in bytes (calculated) */
SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */
void *userdata; /**< Userdata passed to callback (ignored for NULL callbacks). */
} SDL_AudioSpec;
這些參數和音頻是息息相關的,比如采樣率、聲道、音頻資料格式、采樣個數等,後面會專門去介紹它們。
SDL_OpenAudioDevice
方法有兩個參數
desired
obtained
都是
SDL_AudioSpec
類型的。
這裡的意思是我們傳入
desired
指定的音頻參數,但不一定是 SDL 支援的,是以 SDL 會傳回一個它支援的參數資訊放在
obtained
裡面。
不過為了簡單就先把它寫死好了,但即使寫死了有些資訊還是要和你的 PCM 檔案對應上才行,比如
freg
采樣率和
channels
通道數等。
SDL_AudioSpec audioSpec;
audioSpec.freq = 44100;
audioSpec.format = AUDIO_S16SYS;
audioSpec.channels = 2;
audioSpec.silence = 0;
audioSpec.samples = 1024;
// 因為是推模式,是以這裡為 nullptr
audioSpec.callback = nullptr;
SDL_AudioDeviceID deviceId;
if ((deviceId = SDL_OpenAudioDevice(nullptr,0,&audioSpec, nullptr,SDL_AUDIO_ALLOW_ANY_CHANGE)) < 2){
cout << "open audio device failed " << endl;
return -1;
}
注意到
SDL_AudioSpec
有個參數
callback
設定為了 nullptr 。這個回調是為了在
拉
模式中從回調取資料的,因為這裡暫時用不到就寫成了 nullptr ,下一篇文章就會用到了。
這樣就打開了音頻裝置,傳回一個檔案 Id,如果結果小于 2 說明打開失敗了。
接下來通過
SDL_PauseAudioDevice
方法去播放或者暫停音樂。
SDL_PauseAudioDevice(SDL_AudioDeviceID dev,
int pause_on);
SDL_AudioDeviceID
參數就是上面傳回的檔案 Id,
pause_on
為 0 的話代表播放,1 代表暫停。
最後就要開始主動向裝置緩沖區填充 Buffer 了。
就向 SDL 播放 YUV 視訊那樣,要從 PCM 檔案中讀取一塊 Buffer ,然後通過
SDL_QueueAudio
方法進行填充。
int bufferSize = 4096;
char* buffer = (char *)malloc(bufferSize);
// 省略中間代碼
num = fread(buffer,1,bufferSize,pFile);
if (num){
// 填充
SDL_QueueAudio(deviceId,buffer,bufferSize);
}
如上代碼,首先定義了緩沖區的大小 4096,然後 fread 方法讀取這麼大的内容,最後把它填充進去。
此時運作程式,就會聽到和原來 mp3 檔案一樣的聲音了。
不過這裡有要注意的地方,并不是填充了一下 Buffer 就馬上會有聲音播放出來的,要多填充一些才會有聲音播放。
另外,當播放聲音時,必須要讓程式不能退出,因為音頻播放并不是一個阻塞目前主線程的方法,填充完資料就不管了的話,是聽不到聲音的。要麼加個 SDL_Delay 方法要麼就把
SDL_QueueAudio
方法放在接受消息隊列資訊的循環中,我采用的就是後者。
總結
以上就是音視訊基礎學習連載的
007
篇。
本文具體代碼見倉庫:
https://github.com/glumes/av-beginner本篇文章對應的送出
tag
為
av-beginner-004
,可切換至對應源碼檢視。
能力有限,文中有不對之處,歡迎加我微信 ezglumes 進行交流~~
SDL 系列文章「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。