基于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

 

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