Почему не работает декодирование кадров из контейнера avi и кодирование их в h264/mp4?

Я начал использовать ffmpeg и хочу преобразовать файл avi в файл mp4/h264. Я прочитал много сообщений, включая эту, но не смог не нашел ни одного хорошего примера, как сохранить кадры в файл mp4. Приведенный ниже код является упрощенным, который декодирует кадры из файла avi и кодирует их в файл H264/mp4, но когда я сохраняю кадры, файл mp4 не может быть воспроизведен. Я думаю, что я делаю что-то неправильно в кодировке

Буду признателен, если подскажете, что не так и как это исправить.

const char* aviFileName = "aviFrom.avi";
const char* mp4FileName = "mp4To.mp4";

// Filling pFormatCtx by open video file and Retrieve stream information
// ...
// Retrieving codecCtxDecode and opening codecDecode 
//...


// Get encoder
codecCtxEncode = avcodec_alloc_context();   
codecCtxEncode->qmax = 69; 
codecCtxEncode->max_qdiff = 4;
codecCtxEncode->bit_rate = 400000;
codecCtxEncode->width = codecCtxDecode->width;
codecCtxEncode->height = codecCtxDecode->height;
codecCtxEncode->pix_fmt = AV_PIX_FMT_YUV420P;   
codecEncode = avcodec_find_encoder(CODEC_ID_H264);
if(codecEncode == NULL)
    return -1;  
if(avcodec_open2(codecCtxEncode, codecEncode, NULL))
    return -1;

SwsContext *sws_ctx = sws_getContext(codecCtxDecode->width, codecCtxDecode->height, codecCtxDecode->pix_fmt,
                            codecCtxDecode->width, codecCtxDecode->height, AV_PIX_FMT_YUV420P,
                            SWS_BILINEAR, NULL, NULL, NULL);

// Allocate an AVFrame structure    
frameDecoded = avcodec_alloc_frame();
frameEncoded = avcodec_alloc_frame();    

avpicture_alloc((AVPicture *)frameEncoded, AV_PIX_FMT_YUV420P, codecCtxDecode->width, codecCtxDecode->height);

while(av_read_frame(pFormatCtx, &packet)>=0)
{       
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStreamIndex) {
        avcodec_decode_video2(codecCtxDecode, frameDecoded, &frameFinished, &packet);
        // Did we get a video frame?
        if(frameFinished)
        {           
            fwrite(packet.data, packet.size,
            sws_scale(sws_ctx, frameDecoded->data, frameDecoded->linesize, 0, codecCtxDecode->height, 
                        frameEncoded->data, frameEncoded->linesize);



            int64_t pts = packet.pts;
            av_free_packet(&packet);
            av_init_packet(&packet);
            packet.data = NULL;
            packet.size = 0;    
            frameEncoded->pts = pts;                

            int failed = avcodec_encode_video2(codecCtxEncode, &packet, frameEncoded, &got_output);
            if(failed)
            {
                exit(1);
            }
            fwrite(packet.data,1,packet.size, mp4File);
        }
    }

    av_free_packet(&packet);
}

person theateist    schedule 08.11.2012    source источник


Ответы (1)


Вы должны использовать выходной контекст ffmpeg вместо прямой записи необработанных пакетов.

Основные шаги, которые необходимо выполнить:

// find output format
AVOutputFormat * outputFormat = av_guess_format("mp4", NULL, NULL);
AVFormatContext *outFmtCtx = NULL;

// create output cotext
avformat_alloc_output_context2(&outFmtCtx, outputFormat, NULL, NULL);

// create new stream
AVStream * outStrm = avformat_new_stream(outFmtCtx, codecEncode);
avcodec_get_context_defaults3(outStrm->codec, *codec);

outStrm->codec->codec_id = codec_id;
outStrm->codec->coder_type = AVMEDIA_TYPE_VIDEO;
/// outStrm->codec-> ... 
/// set all fields marked as "MUST be set by user" in avcodec.h
/// ....

// create file
avio_open2(&outFmtCtx->pb, file_name, AVIO_FLAG_WRITE, NULL, NULL);
avformat_write_header(outFmtCtx, NULL);

/// write packets
/// for ( )
av_interleaved_write_frame(outFmtCtx, packet);

/// finish
av_write_trailer(outFmtCtx);
avio_close(outFmtCtx->pb);
avformat_free_context(outFmtCtx);

UPD: Полный код, копирующий видео без перекодирования:

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}

int main(int argc, char* argv[])
{
    const char * kInputFileName = "f:/Projects/Temp/testFFMPEG2/test/test_in.avi";
    const char * kOutputFileName = "f:/Projects/Temp/testFFMPEG2/test/text_out.avi";
    const char * kOutputFileType = "avi";

    av_register_all();

    AVFormatContext * inCtx = NULL;
    int err = avformat_open_input(&inCtx, kInputFileName, NULL, NULL);
    if (err < 0)
        exit(1);

    err = av_find_stream_info(inCtx);
    if (err < 0)
        exit(1);


    int vs = -1;
    for (unsigned int s = 0; s < inCtx->nb_streams; ++s)
    {
        if (inCtx->streams[s] &&
            inCtx->streams[s]->codec &&
            inCtx->streams[s]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            vs = s;
            break;
        }        
    }

    if (vs == -1)
        exit(1);

    AVOutputFormat * outFmt = av_guess_format(kOutputFileType, NULL, NULL);
    if (!outFmt)
        exit(1);

    AVFormatContext *outCtx = NULL;
    err = avformat_alloc_output_context2(&outCtx, outFmt, NULL, NULL);

    if (err < 0 || !outCtx)
        exit(1);

    AVStream * outStrm = av_new_stream(outCtx, 0);
    AVStream const * const inStrm = inCtx->streams[vs];
    AVCodec * codec = NULL;
    avcodec_get_context_defaults3(outStrm->codec, codec);
    outStrm->codec->thread_count = 1;

#if (LIBAVFORMAT_VERSION_MAJOR == 53)
    outStrm->stream_copy = 1;
#endif

    outStrm->codec->coder_type = AVMEDIA_TYPE_VIDEO;
    if(outCtx->oformat->flags & AVFMT_GLOBALHEADER) 
        outStrm->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

    outStrm->codec->sample_aspect_ratio = outStrm->sample_aspect_ratio = inStrm->sample_aspect_ratio; 

#if (LIBAVFORMAT_VERSION_MAJOR == 53)
    outCtx->timestamp = 0;
#endif

    err = avio_open(&outCtx->pb, kOutputFileName, AVIO_FLAG_WRITE);
    if (err < 0)
        exit(1);

#if (LIBAVFORMAT_VERSION_MAJOR == 53)
    AVFormatParameters params = {0};
    err = av_set_parameters(outCtx, &params);
    if (err < 0)
        exit(1);
#endif

    outStrm->disposition = inStrm->disposition;
    outStrm->codec->bits_per_raw_sample = inStrm->codec->bits_per_raw_sample;
    outStrm->codec->chroma_sample_location = inStrm->codec->chroma_sample_location;
    outStrm->codec->codec_id = inStrm->codec->codec_id;
    outStrm->codec->codec_type = inStrm->codec->codec_type;

    if (!outStrm->codec->codec_tag)
    {
        if (! outCtx->oformat->codec_tag
            || av_codec_get_id (outCtx->oformat->codec_tag, inStrm->codec->codec_tag) == outStrm->codec->codec_id
            || av_codec_get_tag(outCtx->oformat->codec_tag, inStrm->codec->codec_id) <= 0)
                    outStrm->codec->codec_tag = inStrm->codec->codec_tag;
    }

    outStrm->codec->bit_rate = inStrm->codec->bit_rate;
    outStrm->codec->rc_max_rate = inStrm->codec->rc_max_rate;
    outStrm->codec->rc_buffer_size = inStrm->codec->rc_buffer_size;

    const size_t extra_size_alloc = (inStrm->codec->extradata_size > 0) ?
                                    (inStrm->codec->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE) :
                                     0;

    if (extra_size_alloc)
    {
        outStrm->codec->extradata = (uint8_t*)av_mallocz(extra_size_alloc);    
        memcpy( outStrm->codec->extradata, inStrm->codec->extradata, inStrm->codec->extradata_size);
    }
    outStrm->codec->extradata_size = inStrm->codec->extradata_size;

    AVRational input_time_base = inStrm->time_base;
    AVRational frameRate = {25, 1};
    if (inStrm->r_frame_rate.num && inStrm->r_frame_rate.den 
        && (1.0 * inStrm->r_frame_rate.num / inStrm->r_frame_rate.den < 1000.0))
    {
        frameRate.num = inStrm->r_frame_rate.num;
        frameRate.den = inStrm->r_frame_rate.den;
    }

    outStrm->r_frame_rate = frameRate;
    outStrm->codec->time_base = inStrm->codec->time_base;

    outStrm->codec->pix_fmt = inStrm->codec->pix_fmt;
    outStrm->codec->width =  inStrm->codec->width;
    outStrm->codec->height =  inStrm->codec->height;
    outStrm->codec->has_b_frames =  inStrm->codec->has_b_frames;
    if (! outStrm->codec->sample_aspect_ratio.num) {
        AVRational r0 = {0, 1};
        outStrm->codec->sample_aspect_ratio =
            outStrm->sample_aspect_ratio =
            inStrm->sample_aspect_ratio.num ? inStrm->sample_aspect_ratio :
            inStrm->codec->sample_aspect_ratio.num ?
            inStrm->codec->sample_aspect_ratio : r0;
    }
#if LIBAVFORMAT_VERSION_MAJOR == 53
    av_write_header(outFmtCtx);
#else
    avformat_write_header(outCtx, NULL);
#endif


    for (;;)
    {
        AVPacket packet = {0};
        av_init_packet(&packet);

        err = AVERROR(EAGAIN);
        while (AVERROR(EAGAIN) == err) 
            err = av_read_frame(inCtx, &packet);

        if (err < 0)
        {
            if (AVERROR_EOF != err && AVERROR(EIO) != err)
            {
                // error
                exit(1);            
            }
            else
            {
                // end of file
                break;
            }            
        }


        if (packet.stream_index == vs)
        {

            err = av_interleaved_write_frame(outCtx, &packet);
            if (err < 0)
                exit(1);
        }            

        av_free_packet(&packet);        

    }

    av_write_trailer(outCtx);
    if (!(outCtx->oformat->flags & AVFMT_NOFILE) && outCtx->pb)
        avio_close(outCtx->pb);

    avformat_free_context(outCtx);
    av_close_input_file(inCtx);
    return 0;
}
person pogorskiy    schedule 08.11.2012
comment
Гипотетически, если я вручную добавлю верхний и нижний колонтитулы в файл, все будет в порядке? - person theateist; 08.11.2012
comment
Для некоторых форматов это может сработать, но в целом это неверный путь. - person pogorskiy; 08.11.2012
comment
Что касается эксперимента с тем, что вы написали, я попытался прочитать из файла avi и записать те же пакеты, которые я прочитал (без декодирования), в новый файл avi. Я ожидал получить тот же файл, но новый файл больше на 3 КБ, и медиаплеер не может его открыть. - person theateist; 08.11.2012
comment
Сделал 2 вещи 1 - вместо mp4 написал avi и имя файла естественно тоже поменял на newavi.avi. 2 - для каждого поля для outStrm->codec-> я взял значение из codecCtxDecode->. И на упаковке написано while(av_read_frame(pFormatCtx, &packet)>=0) { av_interleaved_write_frame(outFmtCtx, &packet); }. В чем проблема на ваш взгляд? - person theateist; 08.11.2012
comment
Я попробовал ваш код, который копирует видео без перекодирования, но результат был таким же - размер выходного файла был больше на несколько КБ, общий битрейт был больше на 1 Кбит/с, размер потока был 99%, а не 100%. У Вас есть какие-то предложения? - person theateist; 10.11.2012
comment
Размер файла может не совпадать точно. Это зависит от того, как ffmpeg пишет заголовки файлов - person pogorskiy; 11.11.2012
comment
хорошо, но после использования вашего кода проигрыватель Windows Media не может воспроизвести файл. Плееру VLC удалось открыть файл, но вместо видео он показывает какие-то мигающие полоски. Другой вопрос, если заголовки разные, как проигрыватели будут воспроизводить файл? - person theateist; 11.11.2012
comment
В конце концов это сработало, но есть некоторые файлы avi, которые все еще не работают, и я не могу понять, почему - person theateist; 13.11.2012
comment
В каждом случае может быть разная причина. В общем, копирование пакетов из одного контейнера в другой не всегда возможно выполнить корректно. - person pogorskiy; 13.11.2012
comment
@pogorskiy, спасибо за ответ. Мне нужно декодировать пакет во фрейм, обработать его с помощью opencv и передать обратно в AVPacket. Я использую avcodec_encode_video2, но он выдает ошибку. Не могли бы вы помочь мне, как декодировать кадр в AVFrame и кодировать его в AVPacket? - person Davood Falahati; 02.07.2017
comment
@DavoodFalahati см. пример decoding_encoding.c из источника ffmpeg - person pogorskiy; 03.07.2017