1
votes

Context:
I am building a recorder for capturing video and audio in separate threads (using Boost thread groups) using FFmpeg 2.8.6 on Ubuntu 16.04. I followed the demuxing_decoding example here: https://www.ffmpeg.org/doxygen/2.8/demuxing_decoding_8c-example.html

Video capture specifics:
I am reading H264 off a Logitech C920 webcam and writing the video to a raw file. The issue I notice with the video is that there seems to be a build-up of artifacts across frames until a particular frame resets. Here is my frame grabbing, and decoding functions:

// 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;
}


Questions:

  1. How do I go about debugging the underlying issue?
  2. Is it possible that running the decoding code in a thread other than the one in which the decoding context was opened is causing the problem?
  3. Am I doing something wrong in the decoding code?

Things I have tried/found:

  1. I found this thread that is about the same problem here: FFMPEG decoding artifacts between keyframes (I cannot post samples of my corrupted frames due to privacy issues, but the image linked to in that question depicts the same issue I have) However, the answer to the question is posted by the OP without specific details about how the issue was fixed. The OP only mentions that he wasn't 'preserving the packets correctly', but nothing about what was wrong or how to fix it. I do not have enough reputation to post a comment seeking clarification.

  2. I was initially passing the packet into the decoding function by value, but switched to passing by pointer on the off chance that the packet freeing was being done incorrectly.

  3. I found another question about debugging decoding issues, but couldn't find anything conclusive: How is video decoding corruption debugged?

I'd appreciate any insight. Thanks a lot!

[EDIT] In response to Ronald's answer, I am adding a little more information that wouldn't fit in a comment:

  1. I am only calling decode_video_packet() from the thread processing video frames; the other thread processing audio frames calls a similar decode_audio_packet() function. So only one thread calls the function. I should mention that I have set the thread_count in the decoding context to 1, failing which I would get a segfault in malloc.c while flushing the cached frames.

  2. I can see this being a problem if the process_frames and the frame decoder function were run on separate threads, which is not the case. Is there a specific reason why it would matter if the freeing is done within the function, or after it returns? I believe the freeing function is passed a copy of the original packet because multiple decode calls would be required for audio packet in case the decoder doesnt decode the entire audio packet.

  3. A general problem is that the corruption does not occur all the time. I can debug better if it is deterministic. Otherwise, I can't even say if a solution works or not.

1

1 Answers

2
votes

A few things to check:

  • are you running multiple threads that are calling decode_video_packet()? If you are: don't do that! FFmpeg has built-in support for multi-threaded decoding, and you should let FFmpeg do threading internally and transparently.
  • you are calling av_free_packet() right after calling the frame decoder function, but at that point it may not yet have had a chance to copy the contents. You should probably let decode_video_packet() free the packet instead, after calling avcodec_decode_video2().

General debugging advice:

  • run it without any threading and see if that works;
  • if it does, and with threading it fails, use thread debuggers such as tsan or helgrind to help in finding race conditions that point to your code.
  • it can also help to know whether the output you're getting is reproduceable (this suggests a non-threading-related bug in your code) or changes from one run to the other (this suggests a race condition in your code).

And yes, the periodic clean-ups are because of keyframes.