ffmpeg实现通用的解码类

一 很多同学刚开始接触ffmpeg,会犯一些错误,比如如何创建解码上下文。

1.1 要记住一点,解码参数不要自己随便填,ffmpeg已经帮你解析了参数了,存放在AVStream里面的codecpar里面,你需要拿来初始化解码器,这样才能正常解码,不要自己alloc AVCodecContext之后,自己随便填参数。

二 这里我分享一个我之前实现的通用的解码类实现,任何数据帧类型都支持。

大概的方法是

2.1 先读取视频文件,拿到AVFormatContext。

第一步先demux视频文件,demux详细实现就不写了

ffmpeg实现通用的解码类_第1张图片

 ffmpeg实现通用的解码类_第2张图片

用AVCodecContext上下文初始化通用解码器 

2.2 拿到AVFormatContext后用AVFormatContext中的AVStream去初始化解码器。

ffmpeg实现通用的解码类_第3张图片

解码参数要从传进来的AVCodecContext copy过来,老得ffmpeg版本,AVStream中就有AVCodecContext,可以直接拿来用。不过新版本,这个已经过时了。

三 上代码:

#include "comm_decoder.h"
#include "log.h"
#include "transcode_monitor.h"
#include 

#define DECODER_CODECID_ERROR (-101)
#define DECODER_FIND_FAILED (-102)
#define DECODER_GETCTX_FAILED (-103)
#define DECODER_ALLOCCTX_FAILED (-104)
#define DECODER_COPYCTX_FAILED (-105)
#define DECODER_OPENCTX_FAILED (-106)
#define DECODER_ALLOCFRAME_FAILED (-107)
#define DECODER_DECODE_FAILED (-108)
#define DECODER_READQUEUE_FAILED (-109)
#define DECIDER_DECODEPKT_FAILED (-110)
#define DECODER_PASSTHROUGH_FAIELD (-111)
#define DECODER_AUDIO_PASS_FAILED (-112)
#define DECODER_FLUSH_DECODER_FAILED (-113)
#define DECODER_FLUSH_ALLFRAME_FAILED (-114)
#define DECODER_FLUSH_EOF (0)
#define DECODER_FLUSH_SEND_FAIELD (-115)
#define DECODER_FLUSH_RECV_FAILED (-116)
#define DECODER_SET_STOP (-117)
#define DECODER_UNKONW_MEDIA_TYPE (-118)
#define DECODER_FLUSH_FAILED (-119)
#define DECODER_CODECCONTEXT_NULL (-120)
#define DECODER_MEDIACUT_FAILED  (-121)

static const char nalu_head_[4] = {0x0, 0x0, 0x0, 0x1};

int CommDecoder::Init(const AVCodecContext *inctx, const MediaType &t, const bool &async)
{
    int ret = 0;
    AVCodecContext *ctx = NULL;
    defer[&] {
        if (ret < 0 && ctx) {
            avcodec_free_context(&ctx);
            ctx = NULL;
        }
    }; 

    if (!inctx) {
        LLOG_ERROR("inctx null, init avc decoder failed,");
        ret = DECODER_GETCTX_FAILED;
        lastState_ = DECODER_CODECID_ERROR;
        return ret;
    }

    AVCodec *dcodec = avcodec_find_decoder(inctx->codec_id);
    if (dcodec == NULL) {
        ret = DECODER_FIND_FAILED;
        lastState_ = DECODER_FIND_FAILED;
        return ret;
    }

    if ((ctx = avcodec_alloc_context3(dcodec)) == NULL) {
        ret = DECODER_ALLOCCTX_FAILED;
        lastState_ = DECODER_ALLOCCTX_FAILED;
        return ret;
    }

    if ((avcodec_copy_context(ctx, inctx)) < 0) {
        ret = DECODER_COPYCTX_FAILED;
        lastState_ = DECODER_COPYCTX_FAILED;
        return ret;
    }

    ctx->refcounted_frames = 1;
    if (avcodec_open2(ctx, dcodec, NULL) < 0) {
        ret = DECODER_OPENCTX_FAILED;
        lastState_ = DECODER_OPENCTX_FAILED;
        return ret;
    }

    ctx_.reset(ctx, [](AVCodecContext *p) {
        if (p) {
            avcodec_free_context(&p);
            p = NULL;
        }
    });
    ctx = NULL;

    if (async) {
        InitProcessor();
    }
    type_ = t;
    firstPts_ = UNINIT_PTS;
    if (t == MediaType::MediaType_Video) {
        SetName("vdecoder");
        duration_ = 30;
    }else if (t == MediaType::MediaType_Audio){
        SetName("adecoder");
        duration_ = 20;
    }
    LLOG_INFO("init decdoer %s success", name_.c_str());
    return ret;
} 

int CommDecoder::DecodePacket(std::shared_ptr &in,
                          std::shared_ptr &frm) 
{
    int64_t pkt_pts = 0;
    int ret = 0;
    AVFrame *p = NULL;
    defer[&] {
        if (ret != 0 && p) {
            av_frame_free(&p);
            p = NULL;
        }
    };

    if ((p = av_frame_alloc()) == NULL) {
        ret = DECODER_ALLOCFRAME_FAILED;
        lastState_ = DECODER_ALLOCFRAME_FAILED;
        LLOG_ERROR("DECODER_ALLOCFRAME_FAILED,");
        return ret;
    }
    GetSeiData(in.get());

    int got = 0;
    if (type_ == MediaType::MediaType_Video) {
        if (avcodec_decode_video2(ctx_.get(), p, &got, in.get()) <= 0) {
            ret = DECODER_DECODE_FAILED;
            lastState_ = DECODER_DECODE_FAILED;
            LLOG_ERROR("avcodec_decode_video2 fail,");
            return ret;
        }
    } else {
        pkt_pts = in->pts;
        if (avcodec_decode_audio4(ctx_.get(), p, &got, in.get()) <= 0) {
            ret = DECODER_DECODE_FAILED;
            lastState_ = DECODER_DECODE_FAILED;
            LLOG_ERROR("avcodec_decode_audio4 fail,");
            return ret; 
        }
    }

    if (got != 1) {
        ret = 2;
        LLOG_ERROR("decode but not got frame name %s,", name_.c_str());
        return ret;
    } else {
        if (ctx_->codec_id == AV_CODEC_ID_MP3) {
            p->pts = pkt_pts;
        }

        if (firstPts_ == UNINIT_PTS) {
            LLOG_INFO("media file first pts  %ld, type:%s,", p->pts, type_ == MediaType::MediaType_Video?"video":"audio");
            firstPts_ = p->pts;
        }
        p->pts -= firstPts_;

       
        if (lastFramePts_ >= p->pts) {
            LLOG_ERROR("video timestamp exception lastFramePts_ %ld p->pts %ld duration %ld", lastFramePts_, p->pts, p->pkt_duration);
            p->pts = lastFramePts_ + (p->pkt_duration?p->pkt_duration:duration_);
            LLOG_ERROR("adjust p->pts:%d",p->pts);
        }

        lastFramePts_ = p->pts;
        duration_ = (p->pkt_duration? p->pkt_duration:duration_);
        LLOG_INFO("decoded frame pts %ld,duration %ld name %s w %d h %d nb_samples %d,endTime_:%d,startTime_:%d", 
			p->pts, p->pkt_duration, name_.c_str(), p->width, p->height, p->nb_samples, endTime_, startTime_);
/*
        if ((ret = dropPackets(p)) == 2) {
            LLOG_ERROR("got frame but time not reach ,ignore it pts %ld, startTime_ %ld", p->pts, startTime_);
	    }
*/
        if( name_ == "vdecoder" ) {
            pFrames_++;
        }
        frm.reset(p, [](AVFrame *pp) {
            if (pp) {
                av_frame_free(&pp);
                pp = NULL;
            }
        });
	p = NULL;


	return ret;
    }
    return ret;
}

int CommDecoder::Close() 
{
    ctx_.reset();
    TranscodeMonitor::Instance()->MonitorAll(this);
    return 0;
}

int CommDecoder::Process(const PNode &n) 
{
    int ret = 0;
    defer[&] {
        if (ret < 0) {
            //PassThrough(MakeEnd());
            // PNode end = MakeEnd();
            // PassThrough(end);
	        LLOG_ERROR("decode process failed, ret %d", ret);
        }
    };

    if (GetStop()) {
        LLOG_ERROR("set it stop,");
        ret = DECODER_SET_STOP;
        return ret;
    }

    if (!ctx_) {
        ret = DECODER_CODECCONTEXT_NULL;
        LLOG_ERROR("vctx is null,yet");
        return ret;
    }

    if (!n) {
        LLOG_ERROR("its null pnode,");
        return 2;		
    }  

    if (n->mtype == MediaType::MediaType_End) {
        if (name_ == "vdecoder")
            FlushDecoder();
        LLOG_INFO("decoder process end, pFrames_ :%d", pFrames_);	
        return PassThrough(n);     	
    }
    if (n->mtype != MediaType::MediaType_Audio && n->mtype != MediaType::MediaType_Video) {
        LLOG_ERROR("unknow media type ,");
        return 0;
    }

    if (!n->p) {
	    LLOG_ERROR("type %d, packet is null,", n->mtype);
        return 2;
    }

    PAVFrame f;
    if ((ret = DecodePacket(n->p, f)) < 0) {
        LLOG_ERROR("decoder error,");
        ret = DECIDER_DECODEPKT_FAILED;
        lastState_ = DECIDER_DECODEPKT_FAILED;
        LLOG_ERROR("DECIDER_DECODEPKT_FAILED,");
        return ret;
    }

    
    n->f = f;
    n->dtype = DataType::DataType_frame;

    if (ret == 2) {
        ret = 0;
        return ret;
    }

    ret = PassThrough(n);
    return ret;
}

int CommDecoder::ProcessLoop() 
{
    int ret = 0;
    if (!ctx_) {
        return 0;
    }

    while (true) {
        if (GetStop()) {
            LLOG_ERROR("state except exit,");
            ret = -1001;
            lastState_ = COMM_SET_STOP;
            break;
        }
        PNode n = in_->GetNode();
        if ((ret = Process(n)) < 0) {
            LLOG_ERROR("decoder process ret %d,", ret);
            return ret;
        }

        if (ret == 1) {
            LLOG_INFO("decoder to end,");
            return ret;
        }
    }
    return ret;
}

int CommDecoder::FlushDecoder() 
{
    int ret = 0;
    AVFrame *p = NULL;
    defer[&] {
        if (ret != 0 && p) {
            av_frame_free(&p);
            p = NULL;
        }
    };

    if (!ctx_) {
        return 0;
    }
    while (true) {
        if (GetStop()) {
            LLOG_ERROR("set it stop,");
            ret = -1001;
            lastState_ = COMM_SET_STOP;
            break;
        }
        p = av_frame_alloc();
        if (p == NULL) {
            ret = DECODER_FLUSH_ALLFRAME_FAILED;
            lastState_ = DECODER_FLUSH_ALLFRAME_FAILED;
            break;
        }

        if ((ret = avcodec_receive_frame(ctx_.get(), p)) < 0) {
            if (ret == AVERROR_EOF) {
                LLOG_INFO("decode frame end,");
                ret = 1;
                lastState_ = DECODER_FLUSH_EOF;
                break;
            } else if (ret == AVERROR(EAGAIN)) {
                if (p) {
                    av_frame_free(&p);
                    p = NULL;
                }
                if (avcodec_send_packet(ctx_.get(), NULL) != 0) {
                    ret = DECODER_FLUSH_FAILED;
                    LLOG_ERROR("flush decoder send null packet failed,");
                    break;
                }
                continue;
            }

            LLOG_ERROR("flush decoder failed, ret %d\n", ret);
            ret = DECODER_FLUSH_FAILED;
            break;
        }

        if (firstPts_ == UNINIT_PTS) {
            LLOG_INFO("media file first pts  %ld, type:%s,", p->pts, type_ == MediaType::MediaType_Video?"video":"audio");
            firstPts_ = p->pts;
        }
        p->pts -= firstPts_;

       
        if (lastFramePts_ >= p->pts) {
            LLOG_ERROR("video timestamp exception lastFramePts_ %ld p->pts %ld duration %ld", lastFramePts_, p->pts, p->pkt_duration);
            p->pts = lastFramePts_ + (p->pkt_duration?p->pkt_duration:duration_);
            LLOG_ERROR("adjust p->pts:%d",p->pts);
        }

        lastFramePts_ = p->pts;
        duration_ = (p->pkt_duration? p->pkt_duration:duration_);
        LLOG_INFO("flush vdecoder,decoded frame pts %ld,duration %ld name %s w %d h %d nb_samples %d,endTime_:%d,startTime_:%d", 
			p->pts, p->pkt_duration, name_.c_str(), p->width, p->height, p->nb_samples, endTime_, startTime_);

        if ((ret = dropPackets(p)) == 2) {
            LLOG_ERROR("got frame but time not reach ,ignore it pts %ld, startTime_ %ld", p->pts, startTime_);
            av_frame_free(&p);
            p = NULL;
            continue;
        }

        std::shared_ptr pfrm(p, [](AVFrame *p) {
            if (p) {
                av_frame_free(&p);
                p = NULL;
            }
        });
        p = NULL;


        pFrames_++;
       
        PNode ef = MakeNodeByFrame(pfrm, MediaType::MediaType_Video);
        if ((PassThrough(ef)) != 0) {
            LLOG_ERROR("flush decoder failed,");
            ret = DECODER_FLUSH_RECV_FAILED;
            lastState_ = DECODER_FLUSH_RECV_FAILED;
            break;
        }
    }
    return 0;
}

int CommDecoder::dropPackets(AVFrame *f)
{
    int ret = 0;
    if (f->pts < startTime_) {
        LLOG_INFO("type:%s, %s ,discarded  frame,frame pts %d", 
			(type_ == MediaType::MediaType_Video?"video":type_ == MediaType::MediaType_Audio?"audio":"unknown"),
			"smaller startTime",
			f->pts);
        ret = 2;
        return ret;
    } else if (f->pts >= startTime_) {
        if (type_ == MediaType::MediaType_Audio) {
            f->pts = f->pts - startTime_;
        }
        if (type_ == MediaType::MediaType_Video) {
            f->pts = f->pts - startTime_;
	}
        LLOG_INFO("type:%s media cut,frame pts %d", (type_ == MediaType::MediaType_Video?"video":type_ == MediaType::MediaType_Audio?"audio":"unknown"),
	                f->pts);
    }

    return ret;
}

void CommDecoder::SetDuration(const int64_t &start, const int64_t &end) 
{
    if (start >= 0) {  
        startTime_ = start;
    }
    if (end > 0) {
        endTime_ = end;
    }
}

int CommDecoder::MonitorAll(Json::Value &rt) 
{
    LLOG_INFO("seiCount_: %d\n", seiCount_);
    rt["srcSeiCount"] = seiCount_;
    int ret = 0;
    return ret;
}

void CommDecoder::GetSeiData(AVPacket *p)
{
    if (memcmp(p->data, nalu_head_, 4) == 0 || memcmp(p->data, nalu_head_+1, 3) == 0) {
        GetAnnexbSeiData(p);
    } else {
        GetMp4SeiData(p);
    }
}

void CommDecoder::GetMp4SeiData(AVPacket *p)
{
    unsigned int parse_off = 0;
    int len = p->size;
    uint8_t *data = p->data;

    while (parse_off + sizeof(int) + 4 < len) {
        unsigned int cur_size = ((data[parse_off + 0] << 24) & 0xff000000) | ((data[parse_off + 1] << 16) & 0xff0000)
               | ((data[parse_off + 2] << 8) & 0xff00) | (data[parse_off + 3] & 0xff);

        parse_off += 4;
        int parse_start = parse_off;
        if (data[parse_off] != 0x6) {
            parse_off = parse_start + cur_size;
            continue;
        }
        if (cur_size + parse_off > len) {
            LLOG_ERROR("sei parse nalu size invalid, cur_size %d, parse_off %d len %d dts %ld,", cur_size, parse_off, len, p->dts);
            return;
        }
        parse_off++;

        int type, pay_size, uuid_off;
        if (ParseSei(data + parse_off, len - parse_off, type, pay_size, uuid_off) < 0) {
            LLOG_ERROR("sei parse err, pts %lld", p->pts);
            return;
        }
        seiCount_++;
        parse_off = parse_start + cur_size;
    }
    return;
}

void CommDecoder::GetAnnexbSeiData(AVPacket *p)
{
    int off = 0;
    int parse_off = 0;
    int len = p->size;
    uint8_t *data = p->data;

    while ((off = KmpSearch((const char*)data + parse_off, len - parse_off, nalu_head_ + 1, 3)) >= 0
               && parse_off + off + 4 < len) {
        parse_off += off +3;
        if (data[parse_off++] != 0x6) {
            continue;
        }
        int type, pay_size, uuid_off;
        if (ParseSei((uint8_t*)data + parse_off, len - parse_off, type, pay_size, uuid_off) < 0) {
            LLOG_ERROR("sei parse err, pts %lld", p->pts);
            return;
        }
        seiCount_++;
        parse_off += uuid_off + pay_size + 1;
    }
    return;
}

 

你可能感兴趣的:(ffmpeg,C++,音视频)