用FFMpeg5.0解码SDL2.0播放制作跨平台音乐播放器

        在此我以windows为例,用FFMpeg解码SDL作为音频播放制作一个简单的播放器。同样的做法可以在linux,MacOS,Andriond等系统都是可行的。因为FFMpeg和SDL都是开源的多平台项目。

        我的代码已经上传到Github,大家如果有兴趣还可以下载下来看看。

        https://github.com/xifieer/FFMpegSDLPlayer

        首先我们搭建FFMpeg和SDL的开发环境。我这里就不做下载编译工作了,直接下载适用于windows环境的开发包。有兴趣的朋友可以下载代码下来编译,这样会更深刻理解代码机制。

1)搭建FFMpeg

下载 ffmpeg库的lib和dll。

https://www.gyan.dev/ffmpeg/builds/

下载地址为:https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-5.1.2-full_build-shared.7z

解压后的开发包文件组成如下

 

打开QT先建立一个测试工程

把下载开发包的lib库拷贝到工程目录下,只需要*.lib的就可以了

在pro文件中添加依赖代码

LIBS += -L$$PWD/lib/ -lavcodec
LIBS += -L$$PWD/lib/ -lavdevice
LIBS += -L$$PWD/lib/ -lavfilter
LIBS += -L$$PWD/lib/ -lavformat
LIBS += -L$$PWD/lib/ -lavutil
LIBS += -L$$PWD/lib/ -lswresample
LIBS += -L$$PWD/lib/ -lswscale

 

再把下载开发包的include文件宝贝到工程目录下,并加入到工程中

 

在pro文件中加入代码

INCLUDEPATH +="./include"

添加头文件代码

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavutil/ffversion.h"
}

因为FFMpeg是用C语言编写的,QT是C++,所以大家要用extern “C”包含头文件

最后添加测试代码

    unsigned codecVer = avcodec_version();
    printf("FFmpeg version is: %s, avcodec version is: %d\n.", FFMPEG_VERSION, codecVer);

把最后把开发包bin目录下的dll拷贝到运行目录下,编译运行。

运行结果为:

FFmpeg version is: 5.1.2-full_build-www.gyan.dev, avcodec version is: 3876196

接着我们现在搭建SDL开发环境

下载SDL开发包

下载地址:https://github.com/libsdl-org/SDL/releases/download/release-2.24.1/SDL2-devel-2.24.1-VC.zip

下载后解压缩后的目录

用QT建立工程做一个测试程序

把include文件夹拷贝到工程文件里,然后加入到工程

把lib/x64里的SDL2.lib拷贝到工程文件里的lib文件夹下

在工程pro文件加入代码

INCLUDEPATH +="./include"
LIBS += -L$$PWD/lib/ -lSDL2

 

准备工作做完了,现在写测试代码

extern "C"
{
#include "SDL2/SDL.h"
}
if(SDL_Init(SDL_INIT_AUDIO) == 0) {
    printf( "SDL2 Init Succuss\n");
}

编译成功后,把lib/x64里的SDL2.dll拷贝到运行文件夹下。

运行测试结果为:SDL2 Init Succuss

        我们现在已经知道如何搭建FFMpeg和SDL的开发环境了。接下来我们可以正式写代码了

        编程的思维路线是,用ffmpeg把所有能转码的音频格式转换到pcm格式。同时为了播放方便,我把它转换成44100的采样率,16为的采样精度,双声道。然后用SDL播放声音。SDL有两种播放模式,一种是推流模式,一种是拉流模式。各有优缺点。但对于一般的音频播放问题不大。

        首先我们用FFMpeg把数据转换成PCM格式

    if(fileName == nullptr || m_pSDLPlayer == nullptr) return false;

    int ret;
    AVAlloc();

    // 打开文件
    if ((ret = avformat_open_input(&m_audioDecode.avFmtCtx, fileName, NULL, NULL)) < 0)
    {
        printf("avformat_open_input error");
        AVFree();
        return false;
    }

    // 获取流信息
    if ((ret = avformat_find_stream_info(m_audioDecode.avFmtCtx, NULL)) < 0)
    {
        printf("avformat_find_stream_info error");
        AVFree();
        return false;
    }

    // 获取输入音乐文件的音频流
    if ((ret = av_find_best_stream(m_audioDecode.avFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, (const AVCodec **)&m_audioDecode.avCodec, 0)) < 0)
    {
        printf("av_find_best_stream error");
        AVFree();
        return false;
    }
    int audio_stream_index = ret;

    // 获取解码器参数
    AVCodecParameters *avCodecParameters = m_audioDecode.avFmtCtx->streams[audio_stream_index]->codecpar;

    // 解码器上下文
    m_audioDecode.avCodecCtx = avcodec_alloc_context3(m_audioDecode.avCodec);
    if(m_audioDecode.avCodecCtx == nullptr)
    {
        printf("av_find_best_stream error");
        AVFree();
        return false;
    }

    // 将解码器参数给解码器上下文
    if ((ret = avcodec_parameters_to_context(m_audioDecode.avCodecCtx, avCodecParameters)) < 0)
    {
        printf("avcodec_parameters_to_context error");
        AVFree();
        return false;
    }

    // 打开解码器
    if(avcodec_open2(m_audioDecode.avCodecCtx, m_audioDecode.avCodec, NULL) < 0)
    {
        printf("avcodec_open2 error");
        AVFree();
        return false;
    }

    // 转换器上下文
    SwrContext *swrContext = swr_alloc();
    // 输入文件的参数
    // 获取输入采样位数
    AVSampleFormat in_sample_fmt = m_audioDecode.avCodecCtx->sample_fmt;
    // 获取输入采样率
    int in_sample_rate = m_audioDecode.avCodecCtx->sample_rate;
    // 获取输入通道数
    AVChannelLayout in_channel_layout = m_audioDecode.avCodecCtx->ch_layout;
    //输出参数
    //定义输出采样位数
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    //定义输出采样率
    int out_sample_rate = 44100;
    //定义输出通道
    AVChannelLayout out_channel_layout = AV_CHANNEL_LAYOUT_STEREO;

    //设置重采样频率,采样位数,通道数
    ret = swr_alloc_set_opts2(&swrContext, &out_channel_layout, out_sample_fmt, out_sample_rate,
                                           &in_channel_layout, in_sample_fmt, in_sample_rate, 0, NULL);
    if (ret < 0)
    {
        printf("swr_alloc_set_opts2 error");
        AVFree();
        return false;
    }

    //初始化转换器
    swr_init(swrContext);
    //设置输出缓冲区,大小一般为输出采样率*通道数,上面用的是双通道
    uint8_t *out_buffer = (uint8_t *) (av_malloc(2 * 44100));

    //打开音频文件解压为wb格式
    FILE *fc_pcm = fopen("d:/12.pcm", "wb");

    bool hadStart = false;
    //读取音频流
    while (av_read_frame(m_audioDecode.avFmtCtx, m_audioDecode.avPacket) >= 0)
    {
        avcodec_send_packet(m_audioDecode.avCodecCtx, m_audioDecode.avPacket);
        //拿到解码后的数据(未压缩数据)
        int result = avcodec_receive_frame(m_audioDecode.avCodecCtx, m_audioDecode.avFrame);
        if (result == AVERROR(EAGAIN)) // 有错误
        {
            continue;
        }
        else if (result < 0) //解码完成
        {
            break;
        }

        if (m_audioDecode.avPacket->stream_index != audio_stream_index) //判断是否是音频流
        {
            continue;
        }

        //将解压后的frame重采样转换成统一格式
        int out_len = swr_convert(swrContext, &out_buffer, 2 * 44100, (const uint8_t **) (m_audioDecode.avFrame->data), m_audioDecode.avFrame->nb_samples);

        //out_buffer输出到文件
        //获取输出的布局通道数
        int out_nb_channel = out_channel_layout.nb_channels;
        //获取每一帧的实际大小
        int out_buffer_size = av_samples_get_buffer_size(NULL, out_nb_channel, m_audioDecode.avFrame->nb_samples, out_sample_fmt, 1);

        //写入文件
        fwrite(out_buffer, 1, out_buffer_size, fc_pcm);

        m_pSDLPlayer->AddPcmData(out_buffer, out_buffer_size);

        if(hadStart == false)
        {
           m_pSDLPlayer->Start();
           hadStart = true;
        }
    }

    fclose(fc_pcm);
AVFree();

        接下来就是SDL的主要代码。SDL音频播放有两种模式,一种是拉流模式,一种是推流模式。

        拉流模式需要添加一个播放的回调函数

void PlayAudioCallback(void *udata, Uint8 *stream, int len)
{
    if(udata == nullptr) return;

    SDLPlayer* pThis = (SDLPlayer*)udata;

    if(pThis->m_pDecodeRingBuffer == nullptr) return;

    // 把PCM数据读到SDL播放缓存里
    int readLen = RingBuffer_Out(pThis->m_pDecodeRingBuffer, stream, len);

    if(readLen <= 0) // 音频数据播放完毕
    {
        pThis->Stop();
    }
}

        推流模式比较简单,只需要把PCM数据塞到这里,就可以了

SDL_QueueAudio(m_audioDeviceID, data, len);

以上都是一些主要代码,如果想看整个完整的工程可以到GitHub下载。

代码地址:https://github.com/xifieer/FFMpegSDLPlayer

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值