前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FFmpeg4.0笔记:封装ffmpeg的解码功能类CDecode

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

作者头像
gongluck
发布2019-06-15 15:08:24
1K0
发布2019-06-15 15:08:24
举报
文章被收录于专栏:C++C++

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;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-06-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Github
  • CDecode.h
  • CDecode.cpp
  • 测试
相关产品与服务
腾讯云服务器利旧
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档