Не удается записать кадр YUV из ffmpeg в файл .yuv

Моя цель — записать кадр, который я декодирую, в файл. Я знаю, что записал его хорошо, потому что он отображается при воспроизведении SDL, и я без проблем кодирую его впоследствии. Тем не менее, кажется, я не могу правильно записать кадр в файл. Вот код:

#define PIXFMT AV_PIX_FMT_YUV420P
#define WIDTH 1280
#define HEIGHT 720
// initialize SWS context for software scaling

sws_ctx = sws_getContext(pCodecCtx->width,
    pCodecCtx->height,
    pCodecCtx->pix_fmt,
    WIDTH,
    HEIGHT,
    PIXFMT,
    SWS_LANCZOS,
    NULL,
    NULL,
    NULL
);

FfmpegEncoder enc("rtsp://127.0.0.1:1935/live/myStream", pParser);
//SetPixelArray();

i = 0;
enc.FillYuvImage(pFrameRGB, 0, this->pCodecCtx->width, this->pCodecCtx->height);
FILE *pFile;
while (av_read_frame(pFormatCtx, &packet) >= 0 && !exit) {
    if (packet.stream_index == videoindex) {
        // Decode video frame
        avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

        if (frameFinished) {

            i++;
            sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
            if (i < 500)
            {
                pFile = fopen(std::string("./screenshots/screen.yuv").c_str(), "a+");
                for (int y = 0; y < pCodecCtx->height; y++)
                {
                    fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, pCodecCtx->width * pCodecCtx->height, pFile);
                    fwrite(pFrame->data[1] + y * pFrame->linesize[1], 1, pCodecCtx->width * pCodecCtx->height / 4, pFile);
                    fwrite(pFrame->data[2] + y * pFrame->linesize[2], 1, pCodecCtx->width * pCodecCtx->height / 4, pFile);
                }
                fclose(pFile);
            }
            enc.encodeFrame(pFrameRGB);
        }
    }
    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
}

Программа вылетает при попытке записи кадра.


person tankyx    schedule 28.11.2016    source источник
comment
Проверьте, успешно ли работает fopen.   -  person MayurK    schedule 28.11.2016


Ответы (2)


Как я уже упоминал в комментарии, вам нужно проверить, успешно ли работает fopen.

Но я думаю, проблема в том, что вы пишете больше, чем есть в буфере. Я знаю ограниченные знания о yuv. Я думаю, вам следует сделать следующее. Это основано на моем понимании изображения Single Frame YUV420 в этой страницы.

abcfor (int y = 0; y < pCodecCtx->height; y++) //plane 0: same width and height
{
    fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, pCodecCtx->width, pFile);
}


for (int y = 0; y < pCodecCtx->height/4; y++) //plane1: Same width and quarter height.
{
    fwrite(pFrame->data[1] + y * pFrame->linesize[1], 1, pCodecCtx->width, pFile);
}

for (int y = 0; y < pCodecCtx->height/4; y++) //plane2: Same width and quarter height.
{
    fwrite(pFrame->data[2] + y * pFrame->linesize[2], 1, pCodecCtx->width, pFile);
}

Обратите внимание, порядок написания зависит от цветового формата. Я хочу, чтобы вы сосредоточились на количестве итераций цикла и количестве байтов в fwrite() на каждой итерации.

person MayurK    schedule 28.11.2016
comment
YUV420P имеет height строки шириной width для плоскости 0 и height/2 строки шириной width/2 для плоскостей 1 и 2. Но расширение .yuv обычно означает формат пикселей YUV444P... И даже в YUV420P он, вероятно, будет планарным, а не линейным. чередующаяся цветность. - person Andrey Turkin; 28.11.2016
comment
Вы имеете в виду, что для plane1 и plane2 цикл for от 0 до высоты/2 и fwrite ширина/2 байта? - person MayurK; 28.11.2016
comment
Да, точно. Плоскости 1 и 2 в два раза меньше плоскости 0 в каждом направлении. - person Andrey Turkin; 28.11.2016
comment
Ок внес изменения. Я знаю про YUV420SP. Будет две плоскости, 2-я плоскость будет иметь такую ​​же ширину, но половину высоты. В любом случае, спасибо, что научили меня. :) - person MayurK; 28.11.2016
comment
Этот называется NV21 в ffmpeg. Я думаю, что код все же неправильный, потому что для 420P он должен писать каждую плоскость отдельно, сначала 0, затем 1, затем 2. По крайней мере, файлы yuv, которые я видел с форматом 444P, были в этом макете. - person Andrey Turkin; 28.11.2016
comment
@AndreyTurkin О да. Теперь это должно быть правильно. Если что-то не так, пожалуйста, отредактируйте их. - person MayurK; 28.11.2016
comment
Я пробовал этот код, но он не работает так же. Каждый раз, когда я хочу написать pFrame-›data[1] и pFrame-›data[2], это segfault - person tankyx; 29.11.2016
comment
@tankyx: можешь попробовать? Как для plane1, так и для plane2 makefor (...,y ‹ pCodecCtx->высота/4;..) и fwrite(...,pCodecCtx->width,..). Не меняйте plane0. Сообщите мне результат. Во всяком случае, я обновил его в своем ответе. - person MayurK; 29.11.2016
comment
@AndreyTurkin: Эй, мы ошибались. Пожалуйста, посмотрите изображение в ссылке на Википедию, которую я предоставил в обновленном ответе. Я исправил свой код. - person MayurK; 29.11.2016
comment
@MayurK нет, мы не были. Плоскости 1 и 2 определенно вдвое меньше, чем плоскость 0. Я даже дважды проверил в отладчике, и значения размера строки для видео YUV420P 720x576 были [768, 384, 384]. - person Andrey Turkin; 29.11.2016
comment
@AndreyTurkin: Это? Тогда я не понимаю эту картину. Не могли бы вы кратко объяснить это относительно этой картинки? - person MayurK; 29.11.2016
comment
Эта картина вводит в заблуждение или даже просто неверна (особенно когда шаг не совпадает с шириной). Удивительно, но я не смог найти действительно хорошего введения в изображения YUV (их много, но все они неверны/неполны в том или ином аспекте). Вот картинка получше: i.afterdawn.com/storage/pictures/dvf_color_02.jpg . Это так просто - каждый блок пикселей 2x2 в плоскости Y соответствует одному пикселю в плоскостях U и V; так что на этом рисунке плоскость Y имеет размер 4x4, а плоскости U и V - 2x2. Половина строк, половина столбцов. - person Andrey Turkin; 29.11.2016

Во-первых, спасибо за все ваши комментарии и ответы! Вот фиксированный код:

    while (av_read_frame(pFormatCtx, &packet) >= 0 && !exit && i < 1000) {
    if (packet.stream_index == videoindex) {
        // Decode video frame
        avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

        if (frameFinished) {

            i++;
            sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
            if ((pFile = fopen(std::string("./screenshots/screen.yuv").c_str(), "ab")) == NULL)
                continue;
            fwrite(pFrameRGB->data[0], 1, pCodecCtx->width * pCodecCtx->height, pFile);
            fwrite(pFrameRGB->data[1], 1, pCodecCtx->width * pCodecCtx->height / 4, pFile);
            fwrite(pFrameRGB->data[2], 1, pCodecCtx->width * pCodecCtx->height / 4, pFile);
            fclose(pFile);
            enc.encodeFrame(pFrameRGB);
        }
    }
    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
}

Раньше я использовал не тот кадр, а также неправильные данные и размер.

person tankyx    schedule 29.11.2016
comment
Это может привести к неверным результатам; отдельные строки в плоскостях не обязательно будут непрерывными, поэтому вы не можете записать их как единый блок данных. IIRC libavutil дополнит выделенное изображение, чтобы каждая строка была выровнена по 32 байтам. Это будет работать для 1280x720, но может не работать для других разрешений (попробуйте видео с некоторыми необычными размерами, такими как 258x142). - person Andrey Turkin; 29.11.2016