当前位置:   article > 正文

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

ffmpeg sdl2 sample

        在此我以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文件中添加依赖代码

  1. LIBS += -L$$PWD/lib/ -lavcodec
  2. LIBS += -L$$PWD/lib/ -lavdevice
  3. LIBS += -L$$PWD/lib/ -lavfilter
  4. LIBS += -L$$PWD/lib/ -lavformat
  5. LIBS += -L$$PWD/lib/ -lavutil
  6. LIBS += -L$$PWD/lib/ -lswresample
  7. LIBS += -L$$PWD/lib/ -lswscale

 

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

 

在pro文件中加入代码

INCLUDEPATH +="./include"

添加头文件代码

  1. extern "C"
  2. {
  3. #include "libavcodec/avcodec.h"
  4. #include "libavutil/ffversion.h"
  5. }

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

最后添加测试代码

  1. unsigned codecVer = avcodec_version();
  2. 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文件加入代码

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

 

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

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

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

运行测试结果为:SDL2 Init Succuss

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

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

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

  1. if(fileName == nullptr || m_pSDLPlayer == nullptr) return false;
  2. int ret;
  3. AVAlloc();
  4. // 打开文件
  5. if ((ret = avformat_open_input(&m_audioDecode.avFmtCtx, fileName, NULL, NULL)) < 0)
  6. {
  7. printf("avformat_open_input error");
  8. AVFree();
  9. return false;
  10. }
  11. // 获取流信息
  12. if ((ret = avformat_find_stream_info(m_audioDecode.avFmtCtx, NULL)) < 0)
  13. {
  14. printf("avformat_find_stream_info error");
  15. AVFree();
  16. return false;
  17. }
  18. // 获取输入音乐文件的音频流
  19. if ((ret = av_find_best_stream(m_audioDecode.avFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, (const AVCodec **)&m_audioDecode.avCodec, 0)) < 0)
  20. {
  21. printf("av_find_best_stream error");
  22. AVFree();
  23. return false;
  24. }
  25. int audio_stream_index = ret;
  26. // 获取解码器参数
  27. AVCodecParameters *avCodecParameters = m_audioDecode.avFmtCtx->streams[audio_stream_index]->codecpar;
  28. // 解码器上下文
  29. m_audioDecode.avCodecCtx = avcodec_alloc_context3(m_audioDecode.avCodec);
  30. if(m_audioDecode.avCodecCtx == nullptr)
  31. {
  32. printf("av_find_best_stream error");
  33. AVFree();
  34. return false;
  35. }
  36. // 将解码器参数给解码器上下文
  37. if ((ret = avcodec_parameters_to_context(m_audioDecode.avCodecCtx, avCodecParameters)) < 0)
  38. {
  39. printf("avcodec_parameters_to_context error");
  40. AVFree();
  41. return false;
  42. }
  43. // 打开解码器
  44. if(avcodec_open2(m_audioDecode.avCodecCtx, m_audioDecode.avCodec, NULL) < 0)
  45. {
  46. printf("avcodec_open2 error");
  47. AVFree();
  48. return false;
  49. }
  50. // 转换器上下文
  51. SwrContext *swrContext = swr_alloc();
  52. // 输入文件的参数
  53. // 获取输入采样位数
  54. AVSampleFormat in_sample_fmt = m_audioDecode.avCodecCtx->sample_fmt;
  55. // 获取输入采样率
  56. int in_sample_rate = m_audioDecode.avCodecCtx->sample_rate;
  57. // 获取输入通道数
  58. AVChannelLayout in_channel_layout = m_audioDecode.avCodecCtx->ch_layout;
  59. //输出参数
  60. //定义输出采样位数
  61. AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
  62. //定义输出采样率
  63. int out_sample_rate = 44100;
  64. //定义输出通道
  65. AVChannelLayout out_channel_layout = AV_CHANNEL_LAYOUT_STEREO;
  66. //设置重采样频率,采样位数,通道数
  67. ret = swr_alloc_set_opts2(&swrContext, &out_channel_layout, out_sample_fmt, out_sample_rate,
  68. &in_channel_layout, in_sample_fmt, in_sample_rate, 0, NULL);
  69. if (ret < 0)
  70. {
  71. printf("swr_alloc_set_opts2 error");
  72. AVFree();
  73. return false;
  74. }
  75. //初始化转换器
  76. swr_init(swrContext);
  77. //设置输出缓冲区,大小一般为输出采样率*通道数,上面用的是双通道
  78. uint8_t *out_buffer = (uint8_t *) (av_malloc(2 * 44100));
  79. //打开音频文件解压为wb格式
  80. FILE *fc_pcm = fopen("d:/12.pcm", "wb");
  81. bool hadStart = false;
  82. //读取音频流
  83. while (av_read_frame(m_audioDecode.avFmtCtx, m_audioDecode.avPacket) >= 0)
  84. {
  85. avcodec_send_packet(m_audioDecode.avCodecCtx, m_audioDecode.avPacket);
  86. //拿到解码后的数据(未压缩数据)
  87. int result = avcodec_receive_frame(m_audioDecode.avCodecCtx, m_audioDecode.avFrame);
  88. if (result == AVERROR(EAGAIN)) // 有错误
  89. {
  90. continue;
  91. }
  92. else if (result < 0) //解码完成
  93. {
  94. break;
  95. }
  96. if (m_audioDecode.avPacket->stream_index != audio_stream_index) //判断是否是音频流
  97. {
  98. continue;
  99. }
  100. //将解压后的frame重采样转换成统一格式
  101. int out_len = swr_convert(swrContext, &out_buffer, 2 * 44100, (const uint8_t **) (m_audioDecode.avFrame->data), m_audioDecode.avFrame->nb_samples);
  102. //out_buffer输出到文件
  103. //获取输出的布局通道数
  104. int out_nb_channel = out_channel_layout.nb_channels;
  105. //获取每一帧的实际大小
  106. int out_buffer_size = av_samples_get_buffer_size(NULL, out_nb_channel, m_audioDecode.avFrame->nb_samples, out_sample_fmt, 1);
  107. //写入文件
  108. fwrite(out_buffer, 1, out_buffer_size, fc_pcm);
  109. m_pSDLPlayer->AddPcmData(out_buffer, out_buffer_size);
  110. if(hadStart == false)
  111. {
  112. m_pSDLPlayer->Start();
  113. hadStart = true;
  114. }
  115. }
  116. fclose(fc_pcm);
  117. AVFree();

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

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

  1. void PlayAudioCallback(void *udata, Uint8 *stream, int len)
  2. {
  3. if(udata == nullptr) return;
  4. SDLPlayer* pThis = (SDLPlayer*)udata;
  5. if(pThis->m_pDecodeRingBuffer == nullptr) return;
  6. // 把PCM数据读到SDL播放缓存里
  7. int readLen = RingBuffer_Out(pThis->m_pDecodeRingBuffer, stream, len);
  8. if(readLen <= 0) // 音频数据播放完毕
  9. {
  10. pThis->Stop();
  11. }
  12. }

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

SDL_QueueAudio(m_audioDeviceID, data, len);

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/948411
推荐阅读
  

闽ICP备14008679号