Неверные данные при создании контейнера mkv с потоком h264, так как дополнительные данные являются нулевыми - PullRequest
3 голосов
/ 18 февраля 2020

Мне нужно кодировать необработанные кадры в поток 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
  • { ссылка } говорит оно не должно быть нулевым.

1 Ответ

1 голос
/ 18 февраля 2020

Установите AV_CODEC_FLAG_GLOBAL_HEADER для кодера-> флаги перед открытием. Это скажет x264 добавить экстраданные в контекст кодера вместо отправки их как внутриполосного в закодированном битовом потоке. Муксер Matroska просматривает только параметры кода AVStream c для этих экстраданных и копируется туда из контекста кодера, а не из пакетов в вашем коде.

...