C++/C FFmpeg 伪影跨视频帧构建

C++/C FFmpeg artifact build up across video frames

本文关键字:视频 构建 伪影 FFmpeg C++      更新时间:2023-10-16

上下文:
我正在构建一个记录器,用于在 Ubuntu 16.04 上使用 FFmpeg 2.8.6 在单独的线程(使用 Boost 线程组)中捕获视频和音频。我在这里遵循了demuxing_decoding示例:https://www.ffmpeg.org/doxygen/2.8/demuxing_decoding_8c-example.html

视频捕获细节:
我正在从罗技 C264 网络摄像头读取 H920 并将视频写入原始文件。我注意到视频的问题是,在特定帧重置之前,似乎跨帧会积聚伪影。这是我的帧抓取和解码功能:

// Used for injecting decoding functions for different media types, allowing
// for a generic decode loop
typedef std::function<int(AVPacket*, int*, int)> PacketDecoder;
/**
* Decodes a video packet.
* If the decoding operation is successful, returns the number of bytes decoded,
* else returns the result of the decoding process from ffmpeg
*/
int decode_video_packet(AVPacket *packet,
int *got_frame,
int cached){
int ret = 0;
int decoded = packet->size;
*got_frame = 0;
//Decode video frame
ret = avcodec_decode_video2(video_decode_context,
video_frame, got_frame, packet);
if (ret < 0) {
//FFmpeg users should use av_err2str
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "Error decoding video frame " << errbuf << std::endl;
decoded = ret;
} else {
if (*got_frame) {
video_frame->pts = av_frame_get_best_effort_timestamp(video_frame);
//Write to log file
AVRational *time_base = &video_decode_context->time_base;
log_frame(video_frame, time_base,
video_frame->coded_picture_number, video_log_stream);
#if( DEBUG )
std::cout << "Video frame " << ( cached ? "(cached)" : "" )
<< " coded:" <<  video_frame->coded_picture_number
<< " pts:" << pts << std::endl;
#endif
/*Copy decoded frame to destination buffer:
*This is required since rawvideo expects non aligned data*/
av_image_copy(video_dest_attr.video_destination_data,
video_dest_attr.video_destination_linesize,
(const uint8_t **)(video_frame->data),
video_frame->linesize,
video_decode_context->pix_fmt,
video_decode_context->width,
video_decode_context->height);
//Write to rawvideo file
fwrite(video_dest_attr.video_destination_data[0],
1,
video_dest_attr.video_destination_bufsize,
video_out_file);
//Unref the refcounted frame
av_frame_unref(video_frame);
}
}
return decoded;
}
/**
* Grabs frames in a loop and decodes them using the specified decoding function
*/
int process_frames(AVFormatContext *context,
PacketDecoder packet_decoder) {
int ret = 0;
int got_frame;
AVPacket packet;
//Initialize packet, set data to NULL, let the demuxer fill it
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
// read frames from the file
for (;;) {
ret = av_read_frame(context, &packet);
if (ret < 0) {
if  (ret == AVERROR(EAGAIN)) {
continue;
} else {
break;
}
}
//Convert timing fields to the decoder timebase
unsigned int stream_index = packet.stream_index;
av_packet_rescale_ts(&packet,
context->streams[stream_index]->time_base,
context->streams[stream_index]->codec->time_base);
AVPacket orig_packet = packet;
do {
ret = packet_decoder(&packet, &got_frame, 0);
if (ret < 0) {
break;
}
packet.data += ret;
packet.size -= ret;
} while (packet.size > 0);
av_free_packet(&orig_packet);
if(stop_recording == true) {
break;
}
}
//Flush cached frames
std::cout << "Flushing frames" << std::endl;
packet.data = NULL;
packet.size = 0;
do {
packet_decoder(&packet, &got_frame, 1);
} while (got_frame);
av_log(0, AV_LOG_INFO, "Done processing framesn");
return ret;
}


问题:

  1. 如何调试基础问题?
  2. 是否有
  3. 可能在打开解码上下文的线程以外的线程中运行解码代码导致问题?
  4. 我在解码代码中做错了什么吗?

我尝试/发现的事情:

  1. 我在这里发现了这个线程,该问题大致相同:关键帧之间的 FFMPEG 解码伪影 (由于隐私问题,我无法发布损坏的框架样本,但该问题中链接到的图像描述了我遇到的相同问题) 但是,OP发布了问题的答案,没有有关如何解决问题的具体细节。OP只提到他没有"正确保存数据包",但没有提到出了什么问题或如何修复它。我没有足够的声誉来发表评论寻求澄清。

  2. 我最初是按值将数据包传递到解码函数中,但在数据包释放不正确的情况下切换到通过指针传递。

  3. 我发现了另一个关于调试解码问题的问题,但找不到任何结论:如何调试视频解码损坏?

我将不胜感激任何见解。多谢!

[编辑] 为了回应罗纳德的回答,我添加了更多不适合评论的信息:

  1. 我只从处理视频帧的线程调用 decode_video_packet();另一个处理音频帧的线程调用类似的 decode_audio_packet() 函数。所以只有一个线程调用该函数。我应该提到,我已经将解码上下文中的thread_count设置为 1,否则我会在刷新缓存帧时在 malloc.c 中出现段错误。

  2. 如果process_frames和帧解码器函数在不同的线程上运行,我可以看到这是一个问题,但事实并非如此。是否有特定原因说明释放是在函数内完成还是在函数返回后完成?我相信释放函数会传递原始数据包的副本,因为如果解码器不解码整个音频数据包,音频数据包需要多次解码调用。

  3. 一个普遍的问题是损坏不会一直发生。如果它是确定性的,我可以更好地调试。否则,我什至不能说解决方案是否有效。

需要检查的几件事:

  • 您是否正在运行多个正在调用decode_video_packet()的线程?如果你是:不要那样做!FFmpeg 内置了对多线程解码的支持,您应该让 FFmpeg 在内部透明地进行线程处理。
  • 您在调用帧解码器函数后立即调用av_free_packet(),但此时它可能还没有机会复制内容。您可能应该在调用avcodec_decode_video2()后让decode_video_packet()释放数据包。

一般调试建议:

  • 在没有任何线程的情况下运行它,看看是否有效;
  • 如果是这样,并且线程处理失败,请使用 TSAN 或 Helgrind 等线程调试器来帮助查找指向代码的争用条件。
  • 它还有助于了解您获得的输出是可重现的(这表明代码中存在与线程无关的错误)还是从一次运行到另一次运行的更改(这表明代码中的争用条件)。

是的,定期清理是因为关键帧。