使用 FFmpeg 进行音视频解码,其实是一套标准操作,按照固定的流程进行实现即可。使用 FFmpeg 进行音视频解码的基本流程、及用到的关键 API 接口和 结构体,如下图所示:
音视频解码管理基类
使用 FFmpeg 对视频流解码和音频流解码的基本流程是一样的,因此可以使用同一个基类对相关资源进行管理。
解码器初始化
在使用 FFmpeg 对视频流解码和音频流解码之前,需要根据封装格式查找并打开对应的音视频解码器
AVPacket 和 AVFrame
AVPacket 对象和 AVFrame 对象非固定一对一的关系:一个 AVPacket 对象可能解出多个 AVFrame 对象;也可能多个 AVPacket 对象才能解出一个 AVFrame 对象。
基类中增加解码公共接口
由于 AVPacket 对象和 AVFrame 对象是多对多的关系,一般正常流程是两层嵌套的循环。为使代码流程更清晰,此处可以把两层嵌套的循环改完串行的两个循环,如下图所示:
根据以上流程本文进一步完善基类 Decoder ,添加音视频解码公共接口。
Decoder 类中增加公共方法
根据以上流程在基类中增加两个公共方法:getOneFrame 用于读取一帧解码后的数据、sendPacket 用于发送一包未解码的数据到解码器,代码如下:
template<typename T>
class Decoder
{
public:
Decoder(int packetSize,int frameSize, WaitAndNotify &packet_wait):packetQueue( new Queue<AVPacket>(packetSize)),frameQueue( new Queue<AVFrame>(frameSize)), packetFullWait(packet_wait){};
~Decoder() ;
public:
void setAVFormatContext(AVFormatContext* format){ formatContext = format;}
void setStreamIndex(int m_index){ streamIndex = m_index;}
int getStreamIndex() const { return streamIndex;}
int avCodecIni();//解码器初始化
protected:
int getOneFrame();//读取一帧解码后的数据
int sendPacket();//发送一帧数据到解码器
protected:
AVFormatContext* formatContext = nullptr;//封装格式上下文,外部传入
AVCodecContext* codecContext{ nullptr };//解码上下文
const AVCodec* codec{ nullptr };//解码器
AVFrame* currentFrame{ nullptr };//解码临时帧
AVPacket* lastPacket{ nullptr };//上次解码的帧
bool sendPacketFail{ false };//标记上次发送状态
Queue<AVPacket>* packetQueue{ nullptr };// packet 队列
Queue<AVFrame>* frameQueue{ nullptr }; //frame 队列
int streamIndex{ -1 };//流索引,外部传入
std::atomic<int> abort_request{0};//中断解码
std::atomic<int> force_awake{ 0 };//强制唤醒
WaitAndNotify &packetFullWait;// 用于唤醒解封装线程
WaitAndNotify packetEmptyWait;// packet 队列为空 等待
};
getOneFrame 方法实现
getOneFrame 用于读取一帧解码后的数据,实现如下:
template<typename T>
int Decoder<T>::getOneFrame() {
int ret = AVERROR(EAGAIN);
ret = avcodec_receive_frame(codecContext,currentFrame);
if( ret >= 0 )
{
switch (codecContext->codec_type)
{
case AVMEDIA_TYPE_VIDEO:
currentFrame->pts = currentFrame->best_effort_timestamp;
break;
case AVMEDIA_TYPE_AUDIO:
{
AVRational tb = { 1, currentFrame->sample_rate };
if (currentFrame->pts != AV_NOPTS_VALUE)
currentFrame->pts = av_rescale_q(currentFrame->pts, codecContext->pkt_timebase, tb);//时基转换
}
break;
default:
break;
}
}
return ret;
}
sendPacket 方法实现
template<typename T>
int Decoder<T>::sendPacket() {
if( sendPacketFail )//若上次发送失败了,继续发送
{
if (avcodec_send_packet(codecContext, lastPacket) == AVERROR(EAGAIN))
{
sendPacketFail = true;
return -1;
}
else
{
av_packet_unref(lastPacket);//释放读取的packet
return 0;//发送成功
}
}
AVPacket* packet;//缓存中读一帧
if (packetQueue->IsEmpty()) packetFullWait.notify_all();// packet 缓存为空,则唤醒
do
{
if( abort_request == 1 ) return -1;
auto pred = [&]() { return abort_request || !packetQueue->IsEmpty(); };//返回 false 才会阻塞
packetEmptyWait.wait_for(std::chrono::milliseconds(5), pred);//最多等待 5 毫秒
if( abort_request== 1 ) return -1;//唤醒之后,缓存还是空则退出
if (packetQueue->IsEmpty())
{
packetFullWait.notify_all();
return -1;//唤醒之后,缓存还是空则退出
}
packet = packetQueue->front(); packetQueue->popFront();
if (packetQueue->IsEmpty()) packetFullWait.notify_all();// packet 缓存为空,则唤醒
break;
}while( true );//读缓存
if (lastPacket == nullptr) lastPacket = av_packet_alloc();
av_packet_move_ref(lastPacket, packet);
av_packet_unref(packet);
av_packet_free(&packet);
if (avcodec_send_packet(codecContext, lastPacket) == AVERROR(EAGAIN))
{
sendPacketFail = true;
return -1;
}
else
{
av_packet_unref(lastPacket);//释放读取的packet
return 0;
}
}
作者:litanyuan | 来源:公众号——编程猿来如此
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。