Мне нужно кодировать необработанные кадры в поток h264 внутри контейнера mkv с помощью API-интерфейса ffmpeg c. Все примеры, которые я мог найти, копируют параметры существующего кода c из декодера, поэтому мне пришлось немного его модифицировать.
#include <cstdio>
#include <cstring>
#include <iostream>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
static char error_msg[4096] = "";
int main(int argc, char **argv) {
int err;
char *filename = (char *)av_malloc(4096);
AVCodec *codec = nullptr;
SwsContext *color_converter = nullptr;
AVCodecContext *encoder = nullptr;
AVFormatContext *container = nullptr;
AVStream *stream = nullptr;
std::snprintf(filename, sizeof(filename), "%s", "out.mkv");
// Create encoder
codec = avcodec_find_encoder_by_name("libx264");
if (codec == NULL) {
std::cerr << "codec not found" << std::endl;
goto cleanup;
}
encoder = avcodec_alloc_context3(codec);
if (encoder == nullptr) {
std::cerr << "failed to allocate encoder" << std::endl;
goto cleanup;
}
// Configure encoder
encoder->color_range = AVCOL_RANGE_JPEG;
encoder->height = 1024;
encoder->width = 1280;
encoder->sample_aspect_ratio = {1, 1};
encoder->pix_fmt = AV_PIX_FMT_YUVJ420P;
encoder->time_base = {1, 10};
encoder->framerate = {10, 1};
err = av_opt_set_double(encoder->priv_data, "crf", 0.0, 0);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to initialize encoder: " << error_msg << std::endl;
goto cleanup;
}
err = avcodec_open2(encoder, codec, nullptr);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to initialize encoder: " << error_msg << std::endl;
goto cleanup;
}
// Create container
avformat_alloc_output_context2(&container, NULL, NULL, filename);
if (container == NULL) {
std::cerr << "failed to allocate container" << std::endl;
goto cleanup;
}
// Add stream
stream = avformat_new_stream(container, nullptr);
if (stream == NULL) {
std::cerr << "failed to create new stream" << std::endl;
goto cleanup;
}
stream->time_base = encoder->time_base;
err = avcodec_parameters_from_context(stream->codecpar, encoder);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to configure stream: " << error_msg << std::endl;
goto cleanup;
}
// encoder->extradata_size == 0 at this point!
// Open file and write file header
err = avio_open(&container->pb, filename, AVIO_FLAG_WRITE);
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to open output file: " << error_msg << std::endl;
goto cleanup;
}
err = avformat_write_header(container, nullptr);
// fails in ff_isom_write_avcc because stream->codecpar->extradata is null.
if (err < 0) {
av_make_error_string(error_msg, sizeof(error_msg), err);
std::cerr << "failed to set-up container: " << error_msg << std::endl;
goto cleanup;
}
cleanup:
avcodec_close(encoder);
if (container != nullptr) {
avio_closep(&container->pb);
}
avcodec_free_context(&encoder);
sws_freeContext(color_converter);
avformat_free_context(container);
av_free(filename);
return 0;
}
Это происходит с ошибкой EINVAL (неверные данные обнаружены при обработке ввода) в строке avformat_write_header (точнее в ff_isom_write_av cc внутри библиотеки ffmpeg), потому что stream-> codecpar-> extradata имеет значение null. Тот же код работает для контейнера mp4, и я понятия не имею, как инициализировать stream-> codecpar-> extradata (или encoder-> extradata) вручную.
Я проверил похожие вопросы об этой проблеме, но не смог найти окончательный ответ:
- { ссылка } говорит, что дополнительные данные могут быть нулевыми, когда h264 находится в формате приложения b
- { ссылка } говорит оно не должно быть нулевым.