赞
踩
在此我以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下载。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。