美文网首页
31_音视频播放器_音频解码

31_音视频播放器_音频解码

作者: 咸鱼Jay | 来源:发表于2022-11-16 16:24 被阅读0次

    一、简介

    如上图,我们在主线程中开启一个子线程进行解封装,然后在开两个线程分别进行视频解码和音频解码,其中音频解码我们使用的是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.cppvideoplayert_audio.cppvideoplayert_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);
        /** 获取总时长(单位是微妙,1秒=1000毫秒=1000000微妙)*/
        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();
        /** 初始化SDL */
        int initSDL();
        /** 添加数据包到音频包列表中 */
        void addAudioPkt(AVPacket &pkt);
        /** 清空音频包列表 */
        void clearAudioPktList();
        /** SDL填充缓冲区的回调函数 */
        static void sdlAudioCallbackFunc(void *userdata, Uint8 *stream, int len);
        /** SDL填充缓冲区的回调函数 */
        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 // VIDEOPLAYER_H
    

    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 实现队列添加和清理音频包

    // videoplayert_audio.cpp
    
    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 初始化音频信息

    // videoplayert_audio.cpp
    
    int VideoPlayer::initAudioInfo() {
        int ret = initDecoder(&_aDecodeCtx,&_aStream,AVMEDIA_TYPE_AUDIO);
        RET(initDecoder);
    
        // 初始化frame
        _aFrame = av_frame_alloc();
        if (!_aFrame) {
            qDebug() << "av_frame_alloc error";
            return -1;
        }
    
        // 初始化SDL
        ret = initSDL();
        RET(initSDL);
    
        return 0;
    }
    
    int VideoPlayer::initSDL(){
        // 音频参数
        SDL_AudioSpec spec;
        // 采样率
        spec.freq = 44100;
        // 采样格式(s16le)
        spec.format = AUDIO_S16LSB;
        // 声道数
        spec.channels = 2;
        // 音频缓冲区的样本数量(这个值必须是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回调函数

    // videoplayert_audio.cpp
    
    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){
        // len:SDL音频缓冲区剩余的大小(还未填充的大小)
        while (len > 0) {
            int dataSize = decodeAudio();
            qDebug() <<"解码出来的pcm大小:"<<dataSize;
            if (dataSize <= 0) {
    
            } else {
    
            }
    
    //        // 将一个pkt包解码后的pcm数据填充到SDL的音频缓冲区
    //        SDL_MixAudio(stream, src, srcLen, SDL_MIX_MAXVOLUME);
    
    //        // 移动偏移量
    //        len -= srcLen;
    //        stream += srcLen;
        }
    }
    

    3.3.4 解码音频

    // videoplayert_audio.cpp
    
    /**
     * @brief VideoPlayer::decodeAudio
     * @return 解码出来的pcm大小
     */
    int VideoPlayer::decodeAudio(){
        // 加锁
        _aMutex->lock();
    
    //    while (_aPktList->empty()) {
    //        _aMutex->wait();
    //    }
        if (_aPktList->empty()) {
            _aMutex->unlock();
            return 0;
        }
    
        // 取出头部的数据包
        AVPacket pkt = _aPktList->front();
        // 从头部中删除
        _aPktList->pop_front();
    
        // 解锁
        _aMutex->unlock();
    
        // 发送压缩数据到解码器
        int ret = avcodec_send_packet(_aDecodeCtx, &pkt);
        // 释放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);
    
        // 由于解码出来的PCM。跟SDL要求的PCM格式可能不一致
        // 需要进行重采样
    
    
        return _aFrame->nb_samples
               * _aFrame->channels
               * av_get_bytes_per_sample((AVSampleFormat) _aFrame->format);
    }
    

    代码链接

    相关文章

      网友评论

          本文标题:31_音视频播放器_音频解码

          本文链接:https://www.haomeiwen.com/subject/pwdlxdtx.html