一 很多同学刚开始接触ffmpeg,会犯一些错误,比如如何创建解码上下文。
1.1 要记住一点,解码参数不要自己随便填,ffmpeg已经帮你解析了参数了,存放在AVStream里面的codecpar里面,你需要拿来初始化解码器,这样才能正常解码,不要自己alloc AVCodecContext之后,自己随便填参数。
二 这里我分享一个我之前实现的通用的解码类实现,任何数据帧类型都支持。
大概的方法是
2.1 先读取视频文件,拿到AVFormatContext。
第一步先demux视频文件,demux详细实现就不写了
用AVCodecContext上下文初始化通用解码器
2.2 拿到AVFormatContext后用AVFormatContext中的AVStream去初始化解码器。
解码参数要从传进来的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;
}