Поврежденный AVFrame, возвращенный libavcodec

В рамках более крупного проекта я пытаюсь одновременно декодировать несколько видеопотоков HD (1920x1080). Каждый видеопоток хранится в необработанном формате yuv420p в контейнере AVI. У меня есть класс Decoder, из которого я создаю ряд объектов в разных потоках (по одному объекту на поток). Два основных метода в Decoder — это decode() и getNextFrame(), реализацию которых я привожу ниже.

Когда я разделяю логику декодирования и использую ее для декодирования одного потока, все работает нормально. Однако, когда я использую многопоточный код, я получаю ошибку сегментации, и программа аварийно завершает работу в коде обработки в цикле декодирования. После некоторого исследования я понял, что массив данных AVFrame, заполненный getNextFrame(), содержит адреса, которые находятся вне допустимого диапазона (согласно gdb).

Я действительно потерялся здесь! Я не делаю ничего, что могло бы изменить содержимое AVFrame в моем коде. Единственное место, где я пытаюсь получить доступ к AVFrame, это когда я вызываю sws_scale() для преобразования формата цвета, и именно здесь возникает ошибка сегментации во втором случае из-за поврежденного AVFrame. Любое предложение относительно того, почему это происходит, приветствуется. Заранее спасибо.

Метод decode():

void decode() {

    QString filename("video.avi");

    AVFormatContext* container = 0;

    if (avformat_open_input(&container, filename.toStdString().c_str(), NULL, NULL) < 0) {
        fprintf(stderr, "Could not open %s\n", filename.toStdString().c_str());
        exit(1);
    }

    if (avformat_find_stream_info(container, NULL) < 0) {
        fprintf(stderr, "Could not find file info..\n");
    }

    // find a video stream
    int stream_id = -1;
    for (unsigned int i = 0; i < container->nb_streams; i++) {
        if (container->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            stream_id = i;
            break;
        }
    }

    if (stream_id == -1) {
        fprintf(stderr, "Could not find a video stream..\n");
    }

    av_dump_format(container, stream_id, filename.toStdString().c_str(), false);

    // find the appropriate codec and open it
    AVCodecContext* codec_context = container->streams[stream_id]->codec;   // Get a pointer to the codec context for the video stream

    AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);  // Find the decoder for the video stream

    if (codec == NULL) {
        fprintf(stderr, "Could not find a suitable codec..\n");
        return -1; // Codec not found
    }


    // Inform the codec that we can handle truncated bitstreams -- i.e.,
    // bitstreams where frame boundaries can fall in the middle of packets
    if (codec->capabilities & CODEC_CAP_TRUNCATED)
        codec_context->flags |= CODEC_FLAG_TRUNCATED; 

    fprintf(stderr, "Codec: %s\n", codec->name);

    // open the codec
    int ret = avcodec_open2(codec_context, codec, NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open the needed codec.. Error: %d\n", ret);
        return -1;
    }


    // allocate video frame 
    AVFrame *frame = avcodec_alloc_frame();  // deprecated, should use av_frame_alloc() instead

    if (!frame) {
        fprintf(stderr, "Could not allocate video frame..\n");
        return -1;
    }

    int frameNumber = 0;

    // as long as there are remaining frames in the stream
    while  (getNextFrame(container, codec_context, stream_id, frame)) {

        // Processing logic here...
        // AVFrame data array contains three addresses which are out of range

    }

    // freeing resources
    av_free(frame);

    avcodec_close(codec_context);

    avformat_close_input(&container);
}

Метод getNextFrame():

bool getNextFrame(AVFormatContext *pFormatCtx,
                  AVCodecContext *pCodecCtx,
                  int videoStream,
                  AVFrame *pFrame) {

    uint8_t inbuf[INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];

    char buf[1024];

    int len;

    int got_picture;
    AVPacket avpkt;

    av_init_packet(&avpkt);

    memset(inbuf + INBUF_SIZE, 0, FF_INPUT_BUFFER_PADDING_SIZE);

    // read data from bit stream and store it in the AVPacket object
    while(av_read_frame(pFormatCtx, &avpkt) >= 0) {

        // check the stream index of the read packet to make sure it is a video stream
        if(avpkt.stream_index == videoStream) {

            // decode the packet and store the decoded content in the AVFrame object and set the flag if we have a complete decoded picture
            avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &avpkt);

            // if we have completed decoding an entire picture (frame), return true
            if(got_picture) {

                av_free_packet(&avpkt);

                return true;
            }
        }

        // free the AVPacket object that was allocated by av_read_frame
        av_free_packet(&avpkt);
    }

    return false;

}

Функция обратного вызова управления блокировкой:

static int lock_call_back(void ** mutex, enum AVLockOp op) {
    switch (op) {
        case AV_LOCK_CREATE:
            *mutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
            pthread_mutex_init((pthread_mutex_t *)(*mutex), NULL);
            break;
        case AV_LOCK_OBTAIN:
            pthread_mutex_lock((pthread_mutex_t *)(*mutex));
            break;
        case AV_LOCK_RELEASE:
            pthread_mutex_unlock((pthread_mutex_t *)(*mutex));
            break;
        case AV_LOCK_DESTROY:
            pthread_mutex_destroy((pthread_mutex_t *)(*mutex));
            free(*mutex);
            break;
    }

    return 0;
}

person informer2000    schedule 30.12.2014    source источник
comment
Происходит ли повреждение каждый раз в одном и том же декодированном кадре одного и того же входа? Если это так, вам следует загрузить ввод на сайт обмена файлами, чтобы люди могли попытаться воспроизвести. Также дело может сводиться к ошибке (возможно, уже исправленной) в libavcodec/device.   -  person njahnke    schedule 31.12.2014


Ответы (1)


Я выяснил причину проблемы. Это вызов av_free_packet() перед возвратом при получении декодированного кадра. Я закомментировал этот вызов, и программа заработала! Я все еще не совсем уверен, почему это влияет на заполненный AVFrame.

Я также не уверен, что удаление этого вызова вызовет утечку памяти в моем коде.

Надеюсь, эксперт по libavcodec прольет свет на это и объяснит, что я делал не так.

person informer2000    schedule 02.01.2015
comment
Я только сегодня столкнулся с этой проблемой. Что касается того, почему он освобождает кадр, когда вы освобождаете пакет, потому что кадр просто содержит ссылку на все его пакеты, поэтому данные не дублируются, но я могу ошибаться. - person 06needhamt; 22.04.2020