一、简介

如上图,我们在主线程中开启一个子线程进行解封装,然后在开两个线程分别进行视频解码和音频解码,其中音频解码我们使用的是SDL去渲染,SDL自己会管理子线程,不用我们来创建子线程,而视频解码是需要我们自己创建子线程进行管理。
解封装会解出视频包和音频包,分别塞入各自的队列中,然后各自解码器取出各自的包进行解码,这种模式就是典型的生产者和消费者模式,
所以这里就需要锁机制,我们可以使用《29_SDL多线程与锁机制》里分装好的锁机制类。
| #include "condmutex.h" |
| |
| CondMutex::CondMutex() { |
| |
| _mutex = SDL_CreateMutex(); |
| |
| _cond = SDL_CreateCond(); |
| } |
| |
| CondMutex::~CondMutex() { |
| SDL_DestroyMutex(_mutex); |
| SDL_DestroyCond(_cond); |
| } |
| |
| void CondMutex::lock() { |
| SDL_LockMutex(_mutex); |
| } |
| |
| void CondMutex::unlock() { |
| SDL_UnlockMutex(_mutex); |
| } |
| |
| void CondMutex::signal() { |
| SDL_CondSignal(_cond); |
| } |
| |
| void CondMutex::broadcast() { |
| SDL_CondBroadcast(_cond); |
| } |
| |
| void CondMutex::wait() { |
| SDL_CondWait(_cond, _mutex); |
| } |
二、音频解码
这节我们先介绍音频解码过程。如果都在VideoPlayer
类里做视频解码和音频解码,那么这个类里的代码会很多,我们可以拆开处理。

videoplayert.cpp
、videoplayert_audio.cpp
和videoplayert_video.cpp
三个文件都公用同一个videoplayert.h
,而videoplayert_audio.cpp
专门负责音频相关的处理,videoplayert_video.cpp
专门负责视频相关的处理,videoplayert.cpp
是音视频共同的处理。
2.1 videoplayert.h添加音频相关方法
| #ifndef VIDEOPLAYER_H |
| #define VIDEOPLAYER_H |
| |
| #include <QObject> |
| #include <QDebug> |
| #include <list> |
| #include "condmutex.h" |
| |
| extern "C" { |
| #include <libavcodec/avcodec.h> |
| #include <libavformat/avformat.h> |
| #include <libavutil/avutil.h> |
| } |
| |
| #define ERROR_BUF \ |
| char errbuf[1024]; \ |
| av_strerror(ret, errbuf, sizeof (errbuf)); |
| |
| #define END(func) \ |
| if (ret < 0) { \ |
| ERROR_BUF; \ |
| qDebug() << #func << "error" << errbuf; \ |
| setState(Stopped); \ |
| emit playFailed(this); \ |
| goto end; \ |
| } |
| |
| #define RET(func) \ |
| if (ret < 0) { \ |
| ERROR_BUF; \ |
| qDebug() << #func << "error" << errbuf; \ |
| return ret; \ |
| } |
| |
| |
| |
| |
| class VideoPlayer : public QObject { |
| Q_OBJECT |
| public: |
| |
| typedef enum { |
| Stopped = 0, |
| Playing, |
| Paused |
| } State; |
| |
| explicit VideoPlayer(QObject *parent = nullptr); |
| ~VideoPlayer(); |
| |
| |
| void play(); |
| |
| void pause(); |
| |
| void stop(); |
| |
| bool isPlaying(); |
| |
| State getState(); |
| |
| void setFilename(const char *filename); |
| |
| int64_t getDuration(); |
| |
| signals: |
| void stateChanged(VideoPlayer *player); |
| void initFinished(VideoPlayer *player); |
| void playFailed(VideoPlayer *player); |
| |
| private: |
| |
| |
| AVCodecContext *_aDecodeCtx = nullptr; |
| |
| AVStream *_aStream = nullptr; |
| |
| AVFrame *_aFrame = nullptr; |
| |
| std::list<AVPacket> *_aPktList = nullptr; |
| |
| CondMutex *_aMutex = nullptr; |
| |
| |
| int initAudioInfo(); |
| |
| int initSDL(); |
| |
| void addAudioPkt(AVPacket &pkt); |
| |
| void clearAudioPktList(); |
| |
| static void sdlAudioCallbackFunc(void *userdata, Uint8 *stream, int len); |
| |
| void sdlAudioCallback(Uint8 *stream, int len); |
| |
| int decodeAudio(); |
| |
| |
| |
| AVCodecContext *_vDecodeCtx = nullptr; |
| |
| AVStream *_vStream = nullptr; |
| |
| AVFrame *_vFrame = nullptr; |
| |
| std::list<AVPacket> *_vPktList = nullptr; |
| |
| CondMutex *_vMutex = nullptr; |
| |
| |
| int initVideoInfo(); |
| |
| void addVideoPkt(AVPacket &pkt); |
| |
| void clearVideoPktList(); |
| |
| |
| |
| |
| State _state = Stopped; |
| |
| const char *_filename; |
| |
| AVFormatContext *_fmtCtx = nullptr; |
| |
| int initDecoder(AVCodecContext **decodeCtx, |
| AVStream **stream, |
| AVMediaType type); |
| |
| |
| void setState(State state); |
| |
| void readFile(); |
| }; |
| |
| #endif |
3.2 videoplayer.cpp中分发音频和视频包
| void VideoPlayer::readFile(){ |
| ...... |
| |
| while (true) { |
| AVPacket pkt; |
| ret = av_read_frame(_fmtCtx,&pkt); |
| if ( ret == 0) { |
| if (pkt.stream_index == _aStream->index) { |
| addAudioPkt(pkt); |
| }else if(pkt.stream_index == _vStream->index){ |
| addVideoPkt(pkt); |
| } |
| }else{ |
| continue; |
| } |
| } |
| |
| ...... |
3.3 实现各个方法
3.3.1 实现队列添加和清理音频包
| |
| |
| void VideoPlayer::addAudioPkt(AVPacket &pkt){ |
| _aMutex->lock(); |
| _aPktList->push_back(pkt); |
| _aMutex->signal(); |
| _aMutex->unlock(); |
| } |
| |
| void VideoPlayer::clearAudioPktList(){ |
| _aMutex->lock(); |
| for(AVPacket &pkt : *_aPktList){ |
| av_packet_unref(&pkt); |
| } |
| _aPktList->clear(); |
| _aMutex->unlock(); |
| } |
3.3.2 初始化音频信息
| |
| |
| int VideoPlayer::initAudioInfo() { |
| int ret = initDecoder(&_aDecodeCtx,&_aStream,AVMEDIA_TYPE_AUDIO); |
| RET(initDecoder); |
| |
| |
| _aFrame = av_frame_alloc(); |
| if (!_aFrame) { |
| qDebug() << "av_frame_alloc error"; |
| return -1; |
| } |
| |
| |
| ret = initSDL(); |
| RET(initSDL); |
| |
| return 0; |
| } |
| |
| int VideoPlayer::initSDL(){ |
| |
| SDL_AudioSpec spec; |
| |
| spec.freq = 44100; |
| |
| spec.format = AUDIO_S16LSB; |
| |
| spec.channels = 2; |
| |
| spec.samples = 512; |
| |
| spec.callback = sdlAudioCallbackFunc; |
| |
| spec.userdata = this; |
| |
| |
| if (SDL_OpenAudio(&spec, nullptr)) { |
| qDebug() << "SDL_OpenAudio error" << SDL_GetError(); |
| return -1; |
| } |
| |
| |
| SDL_PauseAudio(0); |
| |
| return 0; |
| } |
3.3.3 SDL回调函数
| |
| |
| void VideoPlayer::sdlAudioCallbackFunc(void *userdata, uint8_t *stream, int len){ |
| VideoPlayer *player = (VideoPlayer *)userdata; |
| player->sdlAudioCallback(stream,len); |
| } |
| |
| |
| void VideoPlayer::sdlAudioCallback(Uint8 *stream, int len){ |
| |
| while (len > 0) { |
| int dataSize = decodeAudio(); |
| qDebug() <<"解码出来的pcm大小:"<<dataSize; |
| if (dataSize <= 0) { |
| |
| } else { |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| } |
| } |
3.3.4 解码音频
| |
| |
| |
| |
| |
| |
| int VideoPlayer::decodeAudio(){ |
| |
| _aMutex->lock(); |
| |
| |
| |
| |
| if (_aPktList->empty()) { |
| _aMutex->unlock(); |
| return 0; |
| } |
| |
| |
| AVPacket pkt = _aPktList->front(); |
| |
| _aPktList->pop_front(); |
| |
| |
| _aMutex->unlock(); |
| |
| |
| int ret = avcodec_send_packet(_aDecodeCtx, &pkt); |
| |
| av_packet_unref(&pkt); |
| RET(avcodec_send_packet); |
| |
| |
| ret = avcodec_receive_frame(_aDecodeCtx, _aFrame); |
| if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { |
| return 0; |
| } else RET(avcodec_receive_frame); |
| |
| |
| qDebug() << _aFrame->sample_rate |
| << _aFrame->channels |
| << av_get_sample_fmt_name((AVSampleFormat) _aFrame->format); |
| |
| |
| |
| |
| |
| return _aFrame->nb_samples |
| * _aFrame->channels |
| * av_get_bytes_per_sample((AVSampleFormat) _aFrame->format); |
| } |
代码链接
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!