关于ffmpeg(libav)解码视频最后丢帧的问题
其实最初不是为了解决这个问题而来的,是Peter兄给我的提示解决另一个问题却让我误打误撞解决了另外一个问题之后也把这个隐藏了很久的bug找到(之前总是有一些特别短的视频产生不知所措还以为是视频素材本身有问题呢),今天真是收获颇丰啊,对libav的理解更深。
一直以来我的程序架构是让读一帧av_read_frame,然后去尝试根据packet的type类型分别去decode video或者audio,然而这样总是在视频的结尾会有许多帧丢失的问题,我找过avplay代码中似乎没找到我想象中的那种读packet完毕后特殊处理的代码。
一下log是我设置将其avcodec_decode_video2函数调用前后输出的pts、dts值(注:Frame:为decode之后,测试视频为纯视频无音频的文件),从图中可以看出来,开头为了顺序正确,libav暂存了9个packet,读packet完毕后,我当然不能就直接不管了,还是要将libav暂存的那些帧取出来才行。
综上几点我认为是在调用avcodec_decode_video2函数的时候,函数内会去发现这个packet尚不足以解码下一帧的时候就会暂存packet在内部队列中(我自己给他们取的名字,望能理解),这样问题就来了,等到整个视频文件都读取完了,剩下libav内部存的packet还有一大堆没处理,我的架构又是要求函数直接向视频索取一帧,因此就需要在读新packet完毕后还 单独调用decode video函数或者decode audio函数来取出剩余的AVFrame。
为了这些bug我破例无耻的使用了goto语句:
首先定义了类成员变量,初始化都为false:
bool _no_packet; //代表是否还有packet bool _end_video_frame; //代表是否video中的队列取出完毕 bool _end_audio_frame; //代表是否audio中的队列取出完毕
AVPacket packet = {0}; while(true) { auto auto_releaser = std::shared_ptr<AVPacket>(&packet, [](AVPacket* p) { av_free_packet(p); }); int ret = av_read_frame(_context.get(), &packet); if ( ret != 0) { _no_packet = true; if(!_end_video_frame) goto loop_end_video; if(!_end_audio_frame) goto loop_end_audio; if(_end_video_frame && _end_audio_frame) return false; } if(packet.stream_index == vstream_index) { loop_end_video: int frame_finished = true; if(avcodec_decode_video2(_vcodec, _decoded_frame, &frame_finished, &packet) < 0) { // 。。。。do something。。。 } if(frame_finished) { //。。。。do something。。。 } else { if(_no_packet) _end_video_frame = true; } } else if (packet.stream_index == astream_index && (type & MEDIA_AUDIO) != 0) { loop_end_audio: int frame_finished; if (avcodec_decode_audio4(_acodec, _decoded_frame, &frame_finished, &packet) < 0) { // 。。。。do something。。。 } if(frame_finished) { //。。。。do something。。。 } else { if(_no_packet) _end_audio_frame = true; } } }
有了这些跳转,就能完美的在read_frame完之后还继续decode为我所用。希望能帮助到与我遇到了相同问题的人。
再次感谢Peter~