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

一、简介

如上图,我们在主线程中开启一个子线程进行解封装,然后在开两个线程分别进行视频解码和音频解码,其中音频解码我们使用的是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);
}

代码链接

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容