Как кодировать входные изображения с камеры в поток H.264?

Я пытаюсь кодировать входные изображения со встроенной HD-камеры FaceTime MacBook Pro в видеопоток H.264 в реальном времени, используя libx264 в Mac OS X 10.9.5.

Ниже приведены шаги, которые я предпринял:

  1. Получите изображения 1280x720 32BGRA с камеры со скоростью 15 кадров в секунду, используя API AVFoundation (класс AVCaptureDevice и т. д.)
  2. Преобразуйте изображения в формат 320x180 YUV420P, используя libswscale.
  3. Кодируйте изображения в видеопоток H.264 (базовый профиль) с помощью libx264.

Я применяю вышеуказанные шаги каждый раз, когда изображение получается с камеры, полагая, что кодировщик отслеживает состояние кодирования и генерирует блок NAL, когда он доступен.

Поскольку я хотел получить закодированные кадры при предоставлении входных изображений кодировщику, я решил очищать кодировщик (вызывая x264_encoder_delayed_frames()) каждые 30 кадров (2 секунды).

Однако, когда я перезапускаю кодирование, кодировщик через некоторое время останавливается (x264_encoder_encode() никогда не возвращается.) Я пытался изменить количество кадров перед сбросом, но ситуация не изменилась.

Ниже приведен соответствующий код (я опустил код захвата изображения, потому что он не выглядит проблемным).

Можете ли вы указать что-нибудь, что я могу делать неправильно?

x264_t *encoder;
x264_param_t param;

// Will be called only first time.
int initEncoder() {
  int ret;

  if ((ret = x264_param_default_preset(&param, "medium", NULL)) < 0) {
    return ret;
  }

  param.i_csp = X264_CSP_I420;
  param.i_width  = 320;
  param.i_height = 180;
  param.b_vfr_input = 0;
  param.b_repeat_headers = 1;
  param.b_annexb = 1;

  if ((ret = x264_param_apply_profile(&param, "baseline")) < 0) {
    return ret;
  }

  encoder = x264_encoder_open(&param);
  if (!encoder) {
    return AVERROR_UNKNOWN;
  }

  return 0;
}

// Will be called from encodeFrame() defined below.
int convertImage(const enum AVPixelFormat srcFmt, const int srcW, const int srcH, const uint8_t *srcData, const enum AVPixelFormat dstFmt, const int dstW, const int dstH, x264_image_t *dstData) {
  struct SwsContext *sws_ctx;
  int ret;
  int src_linesize[4];
  uint8_t *src_data[4];

  sws_ctx = sws_getContext(srcW, srcH, srcFmt,
                       dstW, dstH, dstFmt,
                       SWS_BILINEAR, NULL, NULL, NULL);

  if (!sws_ctx) {
    return AVERROR_UNKNOWN;
  }

  if ((ret = av_image_fill_linesizes(src_linesize, srcFmt, srcW)) < 0) {
    sws_freeContext(sws_ctx);
    return ret;
  }

  if ((ret = av_image_fill_pointers(src_data, srcFmt, srcH, (uint8_t *) srcData, src_linesize)) < 0) {
    sws_freeContext(sws_ctx);
    return ret;
  }

  sws_scale(sws_ctx, (const uint8_t * const*)src_data, src_linesize, 0, srcH, dstData->plane, dstData->i_stride);
  sws_freeContext(sws_ctx);
  return 0;
}

// Will be called for each frame.
int encodeFrame(const uint8_t *data, const int width, const int height) {
  int ret;
  x264_picture_t pic;
  x264_picture_t pic_out;
  x264_nal_t *nal;
  int i_nal;

  if ((ret = x264_picture_alloc(&pic, param.i_csp, param.i_width, param.i_height)) < 0) {
    return ret;
  }

  if ((ret = convertImage(AV_PIX_FMT_RGB32, width, height, data, AV_PIX_FMT_YUV420P, 320, 180, &pic.img)) < 0) {
    x264_picture_clean(&pic);
    return ret;
  }

  if ((ret = x264_encoder_encode(encoder, &nal, &i_nal, &pic, &pic_out)) < 0) {
    x264_picture_clean(&pic);
    return ret;
  }

  if(ret) {
    for (int i = 0; i < i_nal; i++) {
      printNAL(nal + i);
    }
  }

  x264_picture_clean(&pic);
  return 0;
}

// Will be called every 30 frames.
int flushEncoder() {
  int ret;
  x264_nal_t *nal;
  int i_nal;
  x264_picture_t pic_out;

  /* Flush delayed frames */
  while (x264_encoder_delayed_frames(encoder)) {
    if ((ret = x264_encoder_encode(encoder, &nal, &i_nal, NULL, &pic_out)) < 0) {
      return ret;
    }

    if (ret) {
      for (int j = 0; j < i_nal; j++) {
        printNAL(nal + j);
      }
    }
  }
}

person kuu    schedule 21.04.2015    source источник
comment
Вышеприведенный код заработал после того, как я вставил две строки в конце flushEncoder(). Я добавил x264_encoder_close(кодировщик); и initEncoder();   -  person kuu    schedule 22.04.2015


Ответы (1)


Вы не должны сбрасывать задержанные кадры после каждого кадра, а только один раз, когда больше нет входных кадров, т.е. в конце кодирования.

person nobody555    schedule 21.04.2015
comment
Спасибо за указание на неправильное использование API. Я изменил код и переместил часть очистки отложенных кадров в отдельную функцию (flushEncoder). И я заставил функцию вызываться каждые 25 кадров. Теперь я могу получить 28 единиц NAL для первых 25 кадров, состоящих из SPS, PPS, SEI и 25 слайсов (1 IDR + 24 других слайса). Однако, как только я начинаю кодировать следующие 25 кадров, кодировщик останавливается и x264_encoder_encode () никогда не возвращается. Можете еще раз взглянуть на обновленный код? - person kuu; 22.04.2015
comment
Ах, проблема исчезла после того, как я изменил код для сброса кодировщика (вызывая x264_encoder_close() и x264_encoder_open()) каждый раз, когда я очищаю кодировщик. Я узнал, что кодировщик становится бесполезным, как только я вызываю x264_encoder_delayed_frames(). Спасибо. - person kuu; 22.04.2015
comment
Он становится бесполезным не после вызова x264_encoder_delayed_frames(), а после вызова x264_encoder_delayed_frames(), а после вызова x264_encoder_encode() с кадром NULL для сброса кадров, потому что он останавливает поток принятия решений упреждающего/срезового типа (потому что это сигнал о том, что больше нет входных кадров), и после этого он становится менее значимым отправлять реальные кадры в encoder_encode(), потому что они никогда не будут закодированы и будут только увеличивать число delayed_frames (поэтому для вас бесконечный цикл). Как было сказано ранее, вы должны сбрасывать кадры только один раз в конце перед x264_encoder_close(). - person nobody555; 22.04.2015