侧边栏
首页代码

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);
}

代码链接

posted @   咸鱼Jay  阅读(248)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
页脚HTML代码
点击右上角即可分享
微信分享提示
电磁波切换