跨视频帧构建的 C++/C FFmpeg 工件

标签 c++ multithreading video ffmpeg decoding

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

视频捕捉细节:
我正在从 Logitech C920 网络摄像头读取 H264,并将视频写入原始文件。我在视频中注意到的问题是,在特定帧重置之前,似乎存在跨帧的伪像累积。这是我的帧抓取和解码功能:

// 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 frames\n");
    return ret;
}


问题:

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

我尝试/发现的东西:

  1. 我在这里找到了关于相同问题的帖子:FFMPEG decoding artifacts between keyframes (由于隐私问题,我无法发布损坏的帧样本,但该问题中链接的图像描述了我遇到的相同问题) 但是,问题的答案是由 OP 发布的,但没有说明问题是如何解决的具体细节。 OP 只提到他没有“正确地保存数据包”,但没有提到哪里出了问题或如何修复它。我没有足够的声誉来发表评论寻求澄清。

  2. 我最初是按值将数据包传递给解码函数,但如果数据包释放操作不正确,我转而使用指针传递。

  3. 我发现了另一个关于调试解码问题的问题,但找不到任何结论:How is video decoding corruption debugged?

如果有任何见解,我将不胜感激。非常感谢!

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

  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)来帮助找到指向您的代码的竞争条件。
  • 它还有助于了解您获得的输出是否可重现(这表明您的代码中存在与线程无关的错误)或从一次运行到另一次运行的变化(这表明您的代码中存在竞争条件) .

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

关于跨视频帧构建的 C++/C FFmpeg 工件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42633115/

相关文章:

c# - 转码 mxf 视频文件时复制音频编解码器抛出异常

c++ - GCC 是否优化调用约定

java - 多线程应用程序连续失败后如何将主机名添加到阻止列表?

Android - 更改 Activity 时的多线程问题

ios - 使用PHCachingImageManager无法获取iPhone的所有视频

java - 使用 ffmpeg 降低比特率

c++ - 从字符串中删除特定的子字符串

c++ - C++ 中奇怪的类型不匹配

c++ - 为什么空字符串可以在 C++ 中输出索引 0 元素

c++ - 如何多线程队列处理