FFmpeg 入门学习 09–音视频解码公共接口实现

使用 FFmpeg 进行音视频解码,其实是一套标准操作,按照固定的流程进行实现即可。使用 FFmpeg 进行音视频解码的基本流程、及用到的关键 API 接口和 结构体,如下图所示:

FFmpeg 入门学习 09--音视频解码公共接口实现

音视频解码管理基类

使用 FFmpeg 对视频流解码和音频流解码的基本流程是一样的,因此可以使用同一个基类对相关资源进行管理。

解码器初始化

在使用 FFmpeg 对视频流解码和音频流解码之前,需要根据封装格式查找并打开对应的音视频解码器

AVPacket 和 AVFrame

AVPacket 对象和 AVFrame 对象非固定一对一的关系:一个 AVPacket 对象可能解出多个 AVFrame 对象;也可能多个 AVPacket 对象才能解出一个 AVFrame 对象。

基类中增加解码公共接口

由于 AVPacket 对象和 AVFrame 对象是多对多的关系,一般正常流程是两层嵌套的循环。为使代码流程更清晰,此处可以把两层嵌套的循环改完串行的两个循环,如下图所示:

FFmpeg 入门学习 09--音视频解码公共接口实现

根据以上流程本文进一步完善基类 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 举报,一经查实,本站将立刻删除。

(0)

相关推荐

  • Android FFmpeg 视频解码

    本文首先以 FFmpeg 视频解码为主题,主要介绍了 FFmpeg 进行解码视频时的主要流程、基本原理;其次,文章还讲述了与 FFmpeg 视频解码有关的简单应用,包括如何在原有的…

    2023年2月18日
  • 视频解码之软解与硬解

    硬解和软解 硬解:从字面意思上理解就是用硬件来进行解码,通过显卡的视频加速功能对高清视频进行解码,很明显就是一个专门的电路板(这样好理解…)来进行视频的解码,是依靠显卡…

    2022年4月15日
  • 什么是音视频解码?音视频解码流程图

    本文分享音视频解码基本概念、解码的大体流程图以及音视频解码的难点,普及音视频开发的基础知识,希望对大家有用。 一 什么是音视频解码 音视频解码,顾名思义就是把已经压缩过后的音视频(…

    2023年2月13日

发表回复

登录后才能评论