一.pcm文件準備
找一個mp3文件,然後用FFmpeg命令將它轉換成pcm文件,這裏使用NorwayForest-500.mp3(挪威的森林-伍佰)。
首先要使用ffmpeg查看mp3文件的一些信息,比如採樣率、聲道數等。
ffmpeg -i NorwayForest-500.mp3
從上圖所示的信息,可以看到mp3文件採樣率是44100Hz,雙聲道,在轉換成pcm文件時要用到上面的信息。
ffmpeg -i NorwayForest-500.mp3 -acodec pcm_s16le -f s16le -ac 2 -ar 44100 NorwayForest-500.pcm
其中:
-acodec pcm_s16le 指定編碼器
-f s16le 指定文件格式,是大端模式還是小端模式
-ac 2 指定通道數,2 代表雙通道
-ar 44100 指定採樣率,這裏是 44100 Hz
在轉換時要根據原文件的採樣率和聲道數進行轉換,否則轉換後的pcm文件播放聲音不對,下面用ffplay播放一下。
ffplay -ar 44100 -channels 2 -f s16le -i NorwayForest-500.pcm
二.SDL音頻播放
SDL(Simple DirectMedia Layer)是一套開放源代碼的跨平臺多媒體開發庫,使用C語言寫成。SDL提供了數種控制圖像、聲音、輸出入的函數,讓開發者只要用相同或是相似的代碼就可以開發出跨多個平臺(Linux、Windows、Mac OS X等)的應用軟件。目前SDL多用於開發遊戲、模擬器、媒體播放器等多媒體應用領域。
在SDL2中,共提供了兩套用於播放音頻數據的API:
採用第二套API時,ADL_AudioSpec的callback參數設置爲nullptr。
SDL_AudioSpec wantedSpec
wantedSpec.freq = 44100;
wantedSpec.format = AUDIO_S16SYS;
wantedSpec.channels = 2;
wantedSpec.silence = 0;
wantedSpec.samples = 1024;
wantedSpec.callback = nullptr;
第二套API主動填數據到設備緩衝區:
int bufferSize = 4096;
char* buffer = (char *)malloc(bufferSize);
num = fread(buffer,1,bufferSize,pFile);
if (num)
{
SDL_QueueAudio(deviceId,buffer,bufferSize);
}
這種方法可以省去寫回調函數的麻煩,但是有個缺點,無法設置音量。
因此,這裏採用第一套API。
三.代碼實踐
#include "QHAudioPlayer.h"
#include <QDebug>
QHAudioPlayer::QHAudioPlayer()
{
m_volume=90;
m_audioOpened=false;
m_ringBuffer=new QHRingBuffer();
m_ringBuffer->initial(MAX_AUDIO_FRAME_SIZE*100);
if(SDL_Init(SDL_INIT_AUDIO)<0)
{
qDebug()<<"sdl init failed: "<<SDL_GetError();
}
}
QHAudioPlayer::~QHAudioPlayer()
{
SDL_Quit();
}
void QHAudioPlayer::audioPlayCallBack(void *userData, uint8_t *stream, int len)
{
SDL_memset(stream, 0, len);
QHAudioPlayer *player=(QHAudioPlayer *)userData;
static uint8_t audioBuffer[MAX_AUDIO_FRAME_SIZE];
bool result=player->m_ringBuffer->get(audioBuffer,len);
if(result)
{
SDL_MixAudioFormat(stream, (uint8_t *)audioBuffer, AUDIO_S16SYS, len, player->m_volume);
}
}
int QHAudioPlayer::audioReadThread(void *userData)
{
QHAudioPlayer *player=(QHAudioPlayer *)userData;
FILE *pFile = fopen(player->m_fileName.toStdString().c_str(), "rb");
if (pFile == nullptr)
{
qDebug()<<"fopen failed";
}
uint32_t bufferSize = 1024*2*2;
uint8_t *buffer = (uint8_t *) malloc(bufferSize);
while (true)
{
int count=player->m_ringBuffer->usedSize()/bufferSize;
if(count>1)
{
SDL_Delay(1);
continue;
}
if (fread(buffer, 1, bufferSize, pFile) != bufferSize)
{
qDebug()<<"end of file";
break;
}
player->m_ringBuffer->put(buffer,bufferSize);
}
player->closeAudio();
return 0;
}
void QHAudioPlayer::openAudio(const QString &fileName)
{
//SDL_AudioSpec
//freq:音頻數據的採樣率。常用的有48000,44100等.
//format:音頻數據的格式。舉例幾種格式:.
//AUDIO_U16SYS:Unsigned 16-bit samples
//AUDIO_S16SYS:Signed 16-bit samples
//AUDIO_S32SYS:32-bit integer samples
//AUDIO_F32SYS:32-bit floating point samples
//channels:聲道數。例如單聲道取值爲1,立體聲取值爲2.
//silence:設置靜音的值。
//samples:音頻緩衝區中的採樣個數,要求必須是2的n次方.
//padding:考慮到兼容性的一個參數。
//size:音頻緩衝區的大小,以字節爲單位。
//callback:填充音頻緩衝區的回調函數。
//userdata:用戶自定義的數據。
SDL_AudioSpec wantedSpec;
wantedSpec.freq = 44100;
wantedSpec.format = AUDIO_S16SYS;
wantedSpec.channels = 2;
wantedSpec.silence = 0;
wantedSpec.samples = 1024;
wantedSpec.callback = audioPlayCallBack;
wantedSpec.userdata = this;
if(SDL_OpenAudio(&wantedSpec, nullptr)==0)
{
m_audioOpened=true;
// 開始播放.
SDL_PauseAudio(0);
qDebug()<<"QHAudioDecoder audio open succeed";
}
else
{
m_audioOpened=false;
qDebug()<<"QHAudioDecoder audio open failed";
}
m_fileName=fileName;
SDL_CreateThread(audioReadThread,"AudioRead",this);
}
void QHAudioPlayer::closeAudio()
{
if(m_audioOpened)
{
m_audioOpened=false;
SDL_CloseAudio();
qDebug()<<"QHAudioDecoder audio close succeed";
}
}
#include <QCoreApplication>
#include "QHAudioPlayer.h"
#undef main /* Prevents SDL from overriding main() */
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QHAudioPlayer *player=new QHAudioPlayer();
player->openAudio("NorwayForest-500.pcm");
return a.exec();
}
注意這行代碼uint32_t bufferSize = 1024*2*2,爲什麼buffferSize要這設置爲1024*2*2呢,因爲音頻buffer的大小爲nbSamples*channels*bytesPerSample,nbSamples是採樣個數,也就是SDL_AudioSpec的samples參數;channels爲SDL_AudioSpec的channel參數;bytesPerSample可以用FFmpeg算出int bytesPerSample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16),值爲2,這裏AV_SAMPLE_FMT_S16對應SDL_AudioSpec的AUDIO_S16SYS參數,表示16位採樣。
代碼中將讀文件得到的音頻數據放大環形緩衝區中,SDL回調播放時再才從環形緩衝區中獲取,這樣做的好處是,在實際使用的時候,audioReadThread可改寫成FFmpeg解碼音頻線程,方便在解碼層和視頻做同步。
還要注意一點的是:在main.cpp調用SDL之前,要添加#undef main宏,否則會報error LNK2019: 無法解析的外部符號 _main,該符號在函數 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) 中被引用。
源碼鏈接:https://download.csdn.net/download/caoshangpa/14026908
原創不易,轉載請標明出處:https://blog.csdn.net/caoshangpa/article/details/112226484