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

时间:2017-03-06 18:38:17

标签: 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. 我在解码代码中做错了吗?
  4. 我尝试/发现的事情:

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

    2. 我最初是通过值将数据包传递给解码函数,但是切换到通过指针传递数据包释放错误的可能性。

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

    4. 我很欣赏任何见解。非常感谢!

      [编辑]在回应罗纳德的回答时,我补充了一些不适合评论的信息:

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

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

      3. 一般问题是腐败不会一直发生。如果它是确定性的,我可以更好地调试。否则,我甚至不能说解决方案是否有效。

1 个答案:

答案 0 :(得分:2)

要检查的一些事项:

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

一般调试建议:

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

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