31_音視頻播放器_音頻解碼

一、簡(jiǎn)介

如上圖,我們?cè)谥骶€程中開(kāi)啟一個(gè)子線程進(jìn)行解封裝,然后在開(kāi)兩個(gè)線程分別進(jìn)行視頻解碼和音頻解碼,其中音頻解碼我們使用的是SDL去渲染,SDL自己會(huì)管理子線程,不用我們來(lái)創(chuàng)建子線程,而視頻解碼是需要我們自己創(chuàng)建子線程進(jìn)行管理。

解封裝會(huì)解出視頻包和音頻包,分別塞入各自的隊(duì)列中,然后各自解碼器取出各自的包進(jìn)行解碼,這種模式就是典型的生產(chǎn)者和消費(fèi)者模式,
所以這里就需要鎖機(jī)制,我們可以使用《29_SDL多線程與鎖機(jī)制》里分裝好的鎖機(jī)制類。

#include "condmutex.h"

CondMutex::CondMutex() {
    // 創(chuàng)建互斥鎖
    _mutex = SDL_CreateMutex();
    // 創(chuàng)建條件變量
    _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);
}

二、音頻解碼

這節(jié)我們先介紹音頻解碼過(guò)程。如果都在VideoPlayer類里做視頻解碼和音頻解碼,那么這個(gè)類里的代碼會(huì)很多,我們可以拆開(kāi)處理。

videoplayert.cpp、videoplayert_audio.cppvideoplayert_video.cpp三個(gè)文件都公用同一個(gè)videoplayert.h,而videoplayert_audio.cpp專門負(fù)責(zé)音頻相關(guān)的處理,videoplayert_video.cpp專門負(fù)責(zé)視頻相關(guān)的處理,videoplayert.cpp是音視頻共同的處理。

2.1 videoplayert.h添加音頻相關(guān)方法

#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; \
    }

/**
 * 預(yù)處理視頻數(shù)據(jù)(不負(fù)責(zé)顯示、渲染視頻)
 */
class VideoPlayer : public QObject {
    Q_OBJECT
public:
    // 狀態(tài)
    typedef enum {
        Stopped = 0,
        Playing,
        Paused
    } State;

    explicit VideoPlayer(QObject *parent = nullptr);
    ~VideoPlayer();

    /** 播放 */
    void play();
    /** 暫停 */
    void pause();
    /** 停止 */
    void stop();
    /** 是否正在播放中 */
    bool isPlaying();
    /** 獲取當(dāng)前的狀態(tài) */
    State getState();
    /** 設(shè)置文件名 */
    void setFilename(const char *filename);
    /** 獲取總時(shí)長(zhǎng)(單位是微妙,1秒=1000毫秒=1000000微妙)*/
    int64_t getDuration();

signals:
    void stateChanged(VideoPlayer *player);
    void initFinished(VideoPlayer *player);
    void playFailed(VideoPlayer *player);

private:
    /******** 音頻相關(guān) ********/
    /** 解碼上下文 */
    AVCodecContext *_aDecodeCtx = nullptr;
    /** 流 */
    AVStream *_aStream = nullptr;
    /** 存放解碼后的數(shù)據(jù) */
    AVFrame *_aFrame = nullptr;
    /** 存放音頻包的列表 */
    std::list<AVPacket> *_aPktList = nullptr;
    /** 音頻包列表的鎖 */
    CondMutex *_aMutex = nullptr;

    /** 初始化音頻信息 */
    int initAudioInfo();
    /** 初始化SDL */
    int initSDL();
    /** 添加數(shù)據(jù)包到音頻包列表中 */
    void addAudioPkt(AVPacket &pkt);
    /** 清空音頻包列表 */
    void clearAudioPktList();
    /** SDL填充緩沖區(qū)的回調(diào)函數(shù) */
    static void sdlAudioCallbackFunc(void *userdata, Uint8 *stream, int len);
    /** SDL填充緩沖區(qū)的回調(diào)函數(shù) */
    void sdlAudioCallback(Uint8 *stream, int len);
    /** 音頻解碼 */
    int decodeAudio();

    /******** 視頻相關(guān) ********/
    /** 解碼上下文 */
    AVCodecContext *_vDecodeCtx = nullptr;
    /** 流 */
    AVStream *_vStream = nullptr;
    /** 存放解碼后的數(shù)據(jù) */
    AVFrame *_vFrame = nullptr;
    /** 存放視頻包的列表 */
    std::list<AVPacket> *_vPktList = nullptr;
    /** 視頻包列表的鎖 */
    CondMutex *_vMutex = nullptr;

    /** 初始化視頻信息 */
    int initVideoInfo();
    /** 添加數(shù)據(jù)包到視頻包列表中 */
    void addVideoPkt(AVPacket &pkt);
    /** 清空視頻包列表 */
    void clearVideoPktList();


    /******** 其他 ********/
    /** 當(dāng)前的狀態(tài) */
    State _state = Stopped;
    /** 文件名 */
    const char *_filename;
    // 解封裝上下文
    AVFormatContext *_fmtCtx = nullptr;
    /** 初始化解碼器和解碼上下文 */
    int initDecoder(AVCodecContext **decodeCtx,
                    AVStream **stream,
                    AVMediaType type);

    /** 改變狀態(tài) */
    void setState(State state);
    /** 讀取文件數(shù)據(jù) */
    void readFile();
};

#endif // VIDEOPLAYER_H

3.2 videoplayer.cpp中分發(fā)音頻和視頻包

void VideoPlayer::readFile(){
   ......
   // 從輸入文件中讀取數(shù)據(jù)
   while (true) {
       AVPacket pkt;
       ret = av_read_frame(_fmtCtx,&pkt);
       if ( ret == 0) {
           if (pkt.stream_index == _aStream->index) { // 讀取到的是音頻數(shù)據(jù)
               addAudioPkt(pkt);
           }else if(pkt.stream_index == _vStream->index){// 讀取到的是視頻數(shù)據(jù)
               addVideoPkt(pkt);
           }
       }else{
           continue;
       }
   }

......

3.3 實(shí)現(xiàn)各個(gè)方法

3.3.1 實(shí)現(xiàn)隊(duì)列添加和清理音頻包

// 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(){
    // 音頻參數(shù)
    SDL_AudioSpec spec;
    // 采樣率
    spec.freq = 44100;
    // 采樣格式(s16le)
    spec.format = AUDIO_S16LSB;
    // 聲道數(shù)
    spec.channels = 2;
    // 音頻緩沖區(qū)的樣本數(shù)量(這個(gè)值必須是2的冪)
    spec.samples = 512;
    // 回調(diào)
    spec.callback = sdlAudioCallbackFunc;
    // 傳遞給回調(diào)的參數(shù)
    spec.userdata = this;

    // 打開(kāi)音頻設(shè)備
    if (SDL_OpenAudio(&spec, nullptr)) {
        qDebug() << "SDL_OpenAudio error" << SDL_GetError();
        return -1;
    }

    // 開(kāi)始播放
    SDL_PauseAudio(0);

    return 0;
}

3.3.3 SDL回調(diào)函數(shù)

// 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音頻緩沖區(qū)剩余的大小(還未填充的大?。?    while (len > 0) {
        int dataSize = decodeAudio();
        qDebug() <<"解碼出來(lái)的pcm大小:"<<dataSize;
        if (dataSize <= 0) {

        } else {

        }

//        // 將一個(gè)pkt包解碼后的pcm數(shù)據(jù)填充到SDL的音頻緩沖區(qū)
//        SDL_MixAudio(stream, src, srcLen, SDL_MIX_MAXVOLUME);

//        // 移動(dòng)偏移量
//        len -= srcLen;
//        stream += srcLen;
    }
}

3.3.4 解碼音頻

// videoplayert_audio.cpp

/**
 * @brief VideoPlayer::decodeAudio
 * @return 解碼出來(lái)的pcm大小
 */
int VideoPlayer::decodeAudio(){
    // 加鎖
    _aMutex->lock();

//    while (_aPktList->empty()) {
//        _aMutex->wait();
//    }
    if (_aPktList->empty()) {
        _aMutex->unlock();
        return 0;
    }

    // 取出頭部的數(shù)據(jù)包
    AVPacket pkt = _aPktList->front();
    // 從頭部中刪除
    _aPktList->pop_front();

    // 解鎖
    _aMutex->unlock();

    // 發(fā)送壓縮數(shù)據(jù)到解碼器
    int ret = avcodec_send_packet(_aDecodeCtx, &pkt);
    // 釋放pkt
    av_packet_unref(&pkt);
    RET(avcodec_send_packet);

    // 獲取解碼后的數(shù)據(jù)
    ret = avcodec_receive_frame(_aDecodeCtx, _aFrame);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
        return 0;
    } else RET(avcodec_receive_frame);

    // 將解碼后的數(shù)據(jù)寫入文件
    qDebug() << _aFrame->sample_rate
             << _aFrame->channels
             << av_get_sample_fmt_name((AVSampleFormat) _aFrame->format);

    // 由于解碼出來(lái)的PCM。跟SDL要求的PCM格式可能不一致
    // 需要進(jìn)行重采樣


    return _aFrame->nb_samples
           * _aFrame->channels
           * av_get_bytes_per_sample((AVSampleFormat) _aFrame->format);
}

代碼鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容