一、簡(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.cpp和videoplayert_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);
}