Я использую 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());
}