音視頻學(xué)習(xí)入門技術(shù)文章連載:
- 技術(shù)開發(fā)故事會(huì)連載
- 【音視頻連載-001】基礎(chǔ)學(xué)習(xí)篇-SDL 介紹以及工程配置
- 【音視頻連載-002】基礎(chǔ)學(xué)習(xí)篇-SDL 創(chuàng)建窗口并顯示顏色
- 【音視頻連載-003】基礎(chǔ)學(xué)習(xí)篇-SDL 消息循環(huán)和事件響應(yīng)
- 【音視頻連載-004】基礎(chǔ)學(xué)習(xí)篇-SDL 加載圖片并顯示
- 【音視頻連載-005】基礎(chǔ)學(xué)習(xí)篇-SDL 加載 YUV 文件并顯示
- 【音視頻連載-006】基礎(chǔ)學(xué)習(xí)篇-SDL 播放 YUV 視頻文件
- 【音視頻連載-007】基礎(chǔ)學(xué)習(xí)篇-SDL 播放 PCM 音頻文件(上)
接上篇 SDL 播放 PCM 音頻文件巡验,已經(jīng)實(shí)現(xiàn)了 推
的模式去播放,接下來看看 拉
的模式如何實(shí)現(xiàn)。
PCM 文件素材準(zhǔn)備
前面的文章中已經(jīng)準(zhǔn)備好了相關(guān)素材臀脏,這里就不重復(fù)了呜象,還是用同樣的 PCM 文件作為這次實(shí)驗(yàn)素材呼寸。
代碼實(shí)踐
首先還是要通過 SDL_OpenAudioDevice
方法打開一個(gè)音頻設(shè)備稳捆。
SDL_AudioSpec audioSpec;
audioSpec.freq = 44100;
audioSpec.format = AUDIO_S16SYS;
audioSpec.channels = 2;
audioSpec.silence = 0;
audioSpec.samples = 1024;
// 拉的模式火的,這里要傳一個(gè)函數(shù)
audioSpec.callback = fill_audio;
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;
}
不同的是躺翻,這里 callback
參數(shù)不能是 nullptr 了,要傳一個(gè)函數(shù)指針卫玖。這個(gè)函數(shù)在 拉
模式下會(huì)不斷回調(diào)公你,從而將音頻數(shù)據(jù)填充給設(shè)備緩沖區(qū)。
函數(shù)聲明如下:
typedef void (SDLCALL * SDL_AudioCallback) (
// 傳用戶自定義的數(shù)據(jù)
void *userdata,
// 指向要填充給設(shè)備緩沖區(qū)的音頻數(shù)據(jù)Buffer的指針
Uint8 * stream,
// 音頻數(shù)據(jù)Buffer的長(zhǎng)度
int len);
參數(shù) stream
是個(gè)指針類型假瞬,它指向要填充給設(shè)備緩沖區(qū)的音頻數(shù)據(jù) Buffer 陕靠,而 len 就是 Buffer 的長(zhǎng)度。userdata
是我們自定義的數(shù)據(jù)脱茉,需要的時(shí)候可以用到剪芥。
在這個(gè)函數(shù)中我們要做的就是將讀取的 PCM 音頻數(shù)據(jù)傳給 stream
指向的 Buffer ,而且還不能超出 len 的長(zhǎng)度琴许,如果超出了截?cái)嘁幌滤胺荆麓位卣{(diào)時(shí)傳剩下的部分。
因此就有了如下的實(shí)現(xiàn):
// 讀取出 pcm 數(shù)據(jù)長(zhǎng)度
static Uint32 audio_len;
// 讀取出的音頻數(shù)據(jù) Buffer
static Uint8 *audio_pos;
// 函數(shù)實(shí)現(xiàn)
void fill_audio(void *udata, Uint8 *stream, int len) {
SDL_memset(stream, 0, len);
if (audio_len == 0) {
return;
}
// 數(shù)據(jù)大小不能超過 len
len = len > audio_len ? audio_len : len;
// 將 stream 和 audio_pos 進(jìn)行混合播放
// SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
// 單獨(dú)播放 audio_pos,也就是解碼出來的音頻數(shù)據(jù)
memcpy(stream, audio_pos, len);
audio_pos += len;
audio_len -= len;
if (audio_len <= 0){
// 讀取完了榜田,通知繼續(xù)讀取數(shù)據(jù)
notifyGetAudioFrame();
}
}
首先將 stream
數(shù)據(jù)清空益兄。然后比較讀出的 pcm 數(shù)據(jù)長(zhǎng)度 audio_len
和 len
的大小,保證數(shù)據(jù)大小不超過 len
的要求箭券。
在播放時(shí)净捅,也就是給 stream
寫數(shù)據(jù)時(shí)有兩種方式。一種時(shí)直接 memcpy
將音頻數(shù)據(jù) audio_pos
拷貝到 Buffer 上就好了辩块。另一種是通過 SDL_MixAudio
方法蛔六。
SDL_MixAudio
方法顧名思義就是混音了,將 stream
和音頻數(shù)據(jù) audio_pos
混合播放废亭,由于一開始就將 stream
數(shù)據(jù)清空為 0 了国章,所以看似混音,實(shí)際上和直接播放音頻數(shù)據(jù)效果一致的豆村。
最后液兽,如果讀出的 pcm 數(shù)據(jù)長(zhǎng)度大于 len
,那說明數(shù)據(jù)還沒有全部填充完你画,下一次回調(diào)把剩下的填充到緩沖區(qū)抵碟,同時(shí)移動(dòng)相應(yīng)的指針位置。
如果小于坏匪,就得通知繼續(xù)讀取數(shù)據(jù)了拟逮,這里自定義了一個(gè)事件去通知應(yīng)用讀取音頻數(shù)據(jù)。
// 自定義事件适滓,通知讀取音頻數(shù)據(jù)
void notifyGetAudioFrame(){
SDL_Event sdlEvent;
sdlEvent.type = SDL_EVENT_BUFFER_END;
SDL_PushEvent(&sdlEvent);
}
// 在程序事件循環(huán)中去響應(yīng)事件敦迄,讀取音頻 Buffer
while (!bQuit) {
while (SDL_PollEvent(&windowEvent)) {
switch (windowEvent.type) {
case SDL_EVENT_BUFFER_END:
// 讀取音頻數(shù)據(jù)
if (fread(buffer, 1, bufferSize, pFile)) {
data_count += bufferSize;
audio_chunk = reinterpret_cast<Uint8 *>(buffer);
audio_len = bufferSize;
audio_pos = audio_chunk;
}
default:
break;
}
}
}
在事件的消息循環(huán)中進(jìn)行響應(yīng),讀取音頻 Buffer 凭迹。如果讀取的到的長(zhǎng)度等于 0 了罚屋,也可以通過 fseek
方法將指針 seek 到 0,循環(huán)讀取嗅绸。
最后運(yùn)行一下程序脾猛,就會(huì)播放出和原來 mp3 一樣的音樂了。
總結(jié)
以上就是音視頻基礎(chǔ)學(xué)習(xí)連載的 008
篇鱼鸠。
通過兩篇文章講解了 SDL 播放音頻的兩種方式猛拴,后續(xù)會(huì)主要以 拉
的模式進(jìn)行開發(fā)。
本文具體代碼見倉(cāng)庫(kù):
本篇文章對(duì)應(yīng)的提交 tag
為 av-beginner-004
蚀狰,可切換至對(duì)應(yīng)源碼查看愉昆。
能力有限,文中有不對(duì)之處麻蹋,歡迎加我微信 ezglumes 進(jìn)行交流~~