基於live555的rtsp播放器:使用SDL2播放音頻

一.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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章