Выход MP4 от h264 имеет недопустимую частоту кадров - PullRequest
0 голосов
/ 27 октября 2019

Я использую ffmpeg в качестве библиотеки (libavcodec, libavformat) в своем приложении C ++ для помещения необработанного потока h264 (с камеры) в контейнер mp4. По сути, я пытаюсь имитировать поведение следующего вызова исполняемого файла ffmpeg: ffmpeg -framerate 30 -i camera.h264 -c copy -f mp4 -movflags frag_keyframe+empty_moov -

Приведенный ниже код работает более или менее, и нет никаких предупреждений или ошибок из libavformat / libavcodec, напечатанных в stderr. Кажется, он создает приемлемый файл mp4, однако все проигрыватели не могут его показать. VLC показывает первый или около того немного искаженный кадр, а затем останавливается, другие игроки жалуются, что файл имеет неверный формат. Размер файла из ffmpeg и мой код немного отличаются.

Я создаю поток ввода следующим образом (каждый вызов проверяется в реальном коде, но для простоты я удалил обработку ошибок):

        _io_buffer_size = getpagesize();

        _io_buffer = std::shared_ptr<unsigned char>((unsigned char *) av_malloc(_io_buffer_size), [](unsigned char *ptr) { av_free(ptr); });

        _io_context = std::shared_ptr<AVIOContext>(
                avio_alloc_context(_io_buffer.get(), _io_buffer_size, 0, this, H264Stream::on_read_buffer, nullptr, nullptr),
                [](AVIOContext *ctx) { av_free(ctx); }
        );

        _format_context = std::shared_ptr<AVFormatContext>(
                avformat_alloc_context(),
                [](AVFormatContext *ctx) { avformat_free_context(ctx); }
        );

        const auto h264_input_format = av_find_input_format("h264");
        _format_context->pb = _io_context.get();

        std::stringstream fps_stream;
        std::stringstream size_stream;
        fps_stream << fps;
        size_stream << width << "x" << height;

        AVDictionary *input_options = nullptr;
        av_dict_set(&input_options, "framerate", fps_stream.str().c_str(), 0);
        av_dict_set(&input_options, "r", fps_stream.str().c_str(), 0);
        av_dict_set(&input_options, "s", size_stream.str().c_str(), 0);

        auto formatPtr = _format_context.get();
        auto res = avformat_open_input(&formatPtr, "(memory file)", h264_input_format, &input_options);

        AVCodec* decoder = nullptr;
        res = av_find_best_stream(formatPtr, AVMEDIA_TYPE_VIDEO, 0, -1, &decoder, 0);

И контекст выходного формата создается следующим образом:

        _io_buffer_size = getpagesize();

        _io_buffer = std::shared_ptr<unsigned char>((unsigned char *) av_malloc(_io_buffer_size), [](unsigned char *ptr) { av_free(ptr); });

        _io_context = std::shared_ptr<AVIOContext>(
                avio_alloc_context(_io_buffer.get(), _io_buffer_size, 1, this, nullptr, H264Conversion::on_write_data, nullptr),
                [](AVIOContext *ctx) { av_free(ctx); }
        );

        auto output_format = av_guess_format(output_extension.c_str(), nullptr, nullptr);

        AVFormatContext* format_context = nullptr;
        const auto rc = avformat_alloc_output_context2(&format_context, output_format, nullptr, nullptr);

        const auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
        log->info("Output format: {}", codec->name);
        auto video_stream = avformat_new_stream(format_context, codec);
        video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
        video_stream->codecpar->width = _stream->width();
        video_stream->codecpar->height = _stream->height();
        video_stream->codecpar->codec_id = AV_CODEC_ID_H264;

        AVStream** streams = new AVStream*[1];
        streams[0] = video_stream;
        format_context->nb_streams = 1;
        format_context->streams = streams;

        _format_context = std::shared_ptr<AVFormatContext>(format_context, [](AVFormatContext* ctx) { avformat_free_context(ctx); });

        _format_context->pb = _io_context.get();

        _converter_thread = std::thread{[=]() { process_conversion(); }};

Метод process_conversion по существу выполняет цикл чтения пакета -> цикл записи пакета, который выглядит следующим образом:

AVDictionary* dict = nullptr;
        av_dict_set(&dict, "movflags", "frag_keyframe+empty_moov", 0);
        av_dict_set(&dict, "r", "30", 0);
        av_dict_set(&dict, "framerate", "30", 0);

        auto rc = avformat_write_header(_format_context.get(), &dict);

        av_dict_free(&dict);

        auto did_complete_regularly = false;

        std::shared_ptr<AVPacket> packet = std::shared_ptr<AVPacket>(av_packet_alloc(), [](AVPacket *packet) { av_packet_free(&packet); });
        while(_is_running) {
            if (!_stream->read_next_packet(*packet.get())) {
                did_complete_regularly = true;
                break;
            }

            av_write_frame(_format_context.get(), packet.get());
            av_packet_unref(packet.get());
        }

        if(did_complete_regularly) {
            av_write_trailer(_format_context.get());
        }
...