FFmpeg4.0笔记:封装ffmpeg的解码功能类CDecode

2019-06-15 15:08:24 浏览数 (2)

Github

https://github.com/gongluck/FFmpeg4.0-study/tree/master/Cff

CDecode.h

代码语言:javascript复制
#ifndef __CDECODE_H__
#define __CDECODE_H__

#ifdef __cplusplus
extern "C"
{
#endif

#include <libavformat/avformat.h>

#ifdef __cplusplus
}
#endif

#include <string>
#include <mutex>
#include <thread>

class CDecode
{
public:
    ~CDecode();

    // 状态
    enum STATUS{STOP, DECODING};
    // 帧类型
    enum FRAMETYPE {ERR, VIDEO, AUDIO};
    // 状态通知回调声明
    typedef void (*DecStatusCallback)(STATUS status, std::string err, void* param);
    // 解码帧回调声明
    typedef void (*DecFrameCallback)(const AVFrame* frame, FRAMETYPE frametype, void* param);

    // 设置输入
    bool set_input(const std::string& input, std::string& err);
    // 获取输入
    const std::string& get_input(std::string& err);

    // 设置解码帧回调 
    bool set_dec_callback(DecFrameCallback cb, void* param, std::string& err);
    // 设置解码状态变化回调
    bool set_dec_status_callback(DecStatusCallback cb, void* param, std::string& err);

    // 开始解码
    bool begindecode(std::string& err);
    // 停止解码
    bool stopdecode(std::string& err);

private:
    // 解码线程
    bool decodethread();

private:
    STATUS status_ = STOP;
    std::recursive_mutex mutex_;

    std::string input_;
    int vindex_ = -1;
    int aindex_ = -1;
    std::thread decodeth_;

    DecStatusCallback decstatuscb_ = nullptr;
    void* decstatuscbparam_ = nullptr;
    DecFrameCallback decframecb_ = nullptr;
    void* decframecbparam_ = nullptr;

    //ffmpeg
    AVFormatContext* fmtctx_ = nullptr;
    AVCodecContext* vcodectx_ = nullptr;
    AVCodecContext* acodectx_ = nullptr;
};

#endif __CDECODE_H__

CDecode.cpp

代码语言:javascript复制
#include "CDecode.h"

// C  中使用av_err2str宏
char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) 
    av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, errnum)

// 递归锁
#define LOCK() std::lock_guard<std::recursive_mutex> _lock(this->mutex_)

// 检查停止状态
#define CHECKSTOP(err) 
if(this->status_ != STOP)
{
    err = "status is not stop.";
    return false;
}

// 检查ffmpeg返回值
#define CHECKFFRET(ret) 
if (ret < 0)
{
    err = av_err2str(ret);
    return false;
}
#define CHECKFFRETANDCTX(ret, codectx) 
if (ret < 0)
{
    avcodec_free_context(&codectx);
    err = av_err2str(ret);
    return false;
}
#define CHECKFFRETANDCTX2(ret, codectx1, codectx2) 
if (ret < 0)
{
    avcodec_free_context(&codectx1);
    avcodec_free_context(&codectx2);
    err = av_err2str(ret);
    return false;
}

CDecode::~CDecode()
{
    std::string err;
    stopdecode(err);
}

bool CDecode::set_input(const std::string& input, std::string& err)
{
    LOCK();
    CHECKSTOP(err);
    if (input.empty())
    {
        err = "input is empty.";
        return false;
    }
    else
    {
        input_ = input;
        err = "opt succeed.";
        return true;
    }
}

const std::string& CDecode::get_input(std::string& err)
{
    LOCK();
    err = "opt succeed.";
    return input_;
}

bool CDecode::set_dec_callback(DecFrameCallback cb, void* param, std::string& err)
{
    LOCK();
    CHECKSTOP(err);
    decframecb_ = cb;
    decframecbparam_ = param;
    err = "opt succeed.";
    return true;
}

bool CDecode::set_dec_status_callback(DecStatusCallback cb, void* param, std::string& err)
{
    LOCK();
    CHECKSTOP(err);
    decstatuscb_ = cb;
    decstatuscbparam_ = param;
    err = "opt succeed.";
    return true;
}

bool CDecode::begindecode(std::string& err)
{
    LOCK();
    CHECKSTOP(err);

    int ret;

    if (!stopdecode(err))
    {
        return false;
    }

    fmtctx_ = avformat_alloc_context();
    if (fmtctx_ == nullptr)
    {
        err = "avformat_alloc_context() return nullptr.";
        return false;
    }

    ret = avformat_open_input(&fmtctx_, input_.c_str(), nullptr, nullptr);
    CHECKFFRET(ret);

    ret = avformat_find_stream_info(fmtctx_, nullptr);
    CHECKFFRET(ret);

    // 查找流
    AVCodec* vcodec = nullptr;
    ret = av_find_best_stream(fmtctx_, AVMEDIA_TYPE_VIDEO, -1, -1, &vcodec, 0);
    vindex_ = ret;
    AVCodec* acodec = nullptr;
    ret = av_find_best_stream(fmtctx_, AVMEDIA_TYPE_AUDIO, -1, -1, &acodec, 0);
    aindex_ = ret;

    if (vindex_ < 0 && aindex_ < 0)
    {
        err = "cant find stream.";
        return false;
    }
    if (vindex_ >= 0)
    {
        vcodectx_ = avcodec_alloc_context3(vcodec);
        if (vcodectx_ == nullptr)
        {
            err = "avcodec_alloc_context3(vcodec) return nullptr.";
            return false;
        }
        ret = avcodec_parameters_to_context(vcodectx_, fmtctx_->streams[vindex_]->codecpar);
        CHECKFFRETANDCTX(ret, vcodectx_);
        ret = avcodec_open2(vcodectx_, vcodec, nullptr);
        CHECKFFRETANDCTX(ret, vcodectx_);
    }
    if (aindex_ >= 0)
    {
        acodectx_ = avcodec_alloc_context3(acodec);
        if (acodectx_ == nullptr)
        {
            err = "avcodec_alloc_context3(acodec) return nullptr.";
            return false;
        }
        ret = avcodec_parameters_to_context(acodectx_, fmtctx_->streams[aindex_]->codecpar);
        CHECKFFRETANDCTX2(ret, vcodectx_, acodectx_);
        ret = avcodec_open2(acodectx_, acodec, nullptr);
        CHECKFFRETANDCTX2(ret, vcodectx_, acodectx_);
    }

    av_dump_format(fmtctx_, 0, input_.c_str(), 0);

    status_ = DECODING;
    std::thread th(&CDecode::decodethread, this);
    decodeth_.swap(th);

    return true;
}
bool CDecode::stopdecode(std::string& err)
{
    LOCK();

    status_ = STOP;
    if (decodeth_.joinable())
    {
        decodeth_.join();
    }
    if (vcodectx_ != nullptr)
    {
        avcodec_free_context(&vcodectx_);
    } 
    if (acodectx_ != nullptr)
    {
        avcodec_free_context(&acodectx_);
    }
    avformat_close_input(&fmtctx_);

    vindex_ = aindex_ = -1;
    err = "opt succeed.";

    return true;
}

bool CDecode::decodethread()
{
    int ret;
    std::string err;

    AVPacket* packet = av_packet_alloc();
    AVFrame* frame = av_frame_alloc();
    if (packet == nullptr || frame == nullptr)
    {
        if (decstatuscb_ != nullptr)
        {
            status_ = STOP;
            decstatuscb_(STOP, "av_packet_alloc() or av_frame_alloc() return nullptr.", decstatuscbparam_);
        }
        av_packet_free(&packet);
        av_frame_free(&frame);
        return false;
    }
    av_init_packet(packet);
    
    while (true)
    {
        if (status_ != DECODING)
            break;

        if (av_read_frame(fmtctx_, packet) < 0)
        {
            if (decstatuscb_ != nullptr)
            {
                status_ = STOP;
                decstatuscb_(STOP, "end of file.", decstatuscbparam_);
            }
            break; //这里认为视频读取完了
        }

        if (packet->stream_index == vindex_)
        {
            // 解码视频帧
            ret = avcodec_send_packet(vcodectx_, packet);
            if (ret < 0)
            {
                if (decstatuscb_ != nullptr)
                {
                    status_ = STOP;
                    decstatuscb_(STOP, av_err2str(ret), decstatuscbparam_);
                }
                break;
            }
            while (ret >= 0)
            {
                ret = avcodec_receive_frame(vcodectx_, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                {
                    break;
                }
                else if (ret < 0)
                {
                    if (decstatuscb_ != nullptr)
                    {
                        decstatuscb_(DECODING, av_err2str(ret), decstatuscbparam_);
                    }
                    break;
                }
                else
                {
                    // 得到解码数据
                    if (decframecb_ != nullptr)
                    {
                        decframecb_(frame, VIDEO, decframecbparam_);
                    }
                }
            }
        }
        else if (packet->stream_index == aindex_)
        {
            // 解码音频帧
            ret = avcodec_send_packet(acodectx_, packet);
            if (ret < 0)
            {
                if (decstatuscb_ != nullptr)
                {
                    status_ = STOP;
                    decstatuscb_(STOP, av_err2str(ret), decstatuscbparam_);
                }
                break;
            }
            while (ret >= 0)
            {
                ret = avcodec_receive_frame(acodectx_, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                {
                    break;
                }
                else if (ret < 0)
                {
                    if (decstatuscb_ != nullptr)
                    {
                        decstatuscb_(DECODING, av_err2str(ret), decstatuscbparam_);
                    }
                    break;
                }
                else
                {
                    // 得到解码数据
                    if (decframecb_ != nullptr)
                    {
                        decframecb_(frame, AUDIO, decframecbparam_);
                    }
                }
            }
        }
        av_packet_unref(packet);
    }

    av_packet_free(&packet);
    av_frame_free(&frame);

    return true;
}

测试

代码语言:javascript复制
#include "CDecode.h"
#include <iostream>
#include <fstream>

void DecStatusCB(CDecode::STATUS status, std::string err, void* param)
{
    std::cout << std::this_thread::get_id() << " got a status " << status << std::endl;
}

void DecFrameCB(const AVFrame* frame, CDecode::FRAMETYPE frametype, void* param)
{
    //std::cout << std::this_thread::get_id() << " got a frame." << frametype << std::endl;
    if (frametype == CDecode::FRAMETYPE::VIDEO)
    {
        if (frame->format == AV_PIX_FMT_YUV420P)
        {
            static std::ofstream video("out.yuv", std::ios::binary | std::ios::trunc);
            static int i = 0;
            if (  i > 10)
                return;
            video.write(reinterpret_cast<const char*>(frame->data[0]), frame->linesize[0] * frame->height);
            video.write(reinterpret_cast<const char*>(frame->data[1]), frame->linesize[1] * frame->height / 2);
            video.write(reinterpret_cast<const char*>(frame->data[2]), frame->linesize[2] * frame->height / 2);
        }
    }
}

int main(int argc, char* argv[])
{
    std::string err;
    bool ret = false;
    CDecode decode;
    ret = decode.set_input("in.flv", err);
    ret = decode.set_dec_callback(DecFrameCB, nullptr, err);
    ret = decode.set_dec_status_callback(DecStatusCB, nullptr, err);

    int i = 0;
    while (i   < 10)
    {
        ret = decode.begindecode(err);

        std::cout << "input to stop decoding." << std::endl;
        getchar();

        ret = decode.stopdecode(err);
    }

    return 0;
}

0 人点赞