【记录一个问题】使用ffmpeg api 读取一个已经完全载入内存的mp4文件失败

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


我一开始写了一段代码,从磁盘上加载一个mp4文件,并且输出每个视频帧的 size:

int show_frame_detail(const char *input_file) {
  int ret = 0;
  AVFormatContext *fmt_ctx = NULL;
  AVCodecContext *codec_ctx = NULL;
  AVPacket *pkt = av_packet_alloc();
  //
  ret = avformat_open_input(&fmt_ctx, input_file, NULL, NULL);
  if (ret < 0) {
    P("Could not open input file %s, ret=%d", input_file, ret);
    return ret;
  }
  ret = avformat_find_stream_info(fmt_ctx, NULL);
  if (ret < 0) {
    P("Could not find stream information, ret=%d", ret);
    goto save_first_frame_on_error;
  }
  int video_stream_index = -1;
  for (int i = 0; i < fmt_ctx->nb_streams; i++) {
    if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
      video_stream_index = i;
      break;
    }
  }
  if (video_stream_index == -1) {
    P("Could not find video stream");
    goto save_first_frame_on_error;
  }
  AVCodecParameters *codec_params =
      fmt_ctx->streams[video_stream_index]->codecpar;
  const AVCodec *decoder = avcodec_find_decoder(codec_params->codec_id);
  if (NULL == decoder) {
    P("Could not find decoder");
    goto save_first_frame_on_error;
  }
  codec_ctx = avcodec_alloc_context3(decoder);
  if (NULL == codec_ctx) {
    P("Could not allocate codec context");
    goto save_first_frame_on_error;
  }
  ret = avcodec_parameters_to_context(codec_ctx, codec_params);
  if (ret < 0) {
    P("Could not copy codec parameters to context,ret=%d", ret);
    goto save_first_frame_on_error;
  }
  ret = avcodec_open2(codec_ctx, decoder, NULL);
  if (ret < 0) {
    P("Could not open codec, ret=%d", ret);
    goto save_first_frame_on_error;
  }
  int frame_index = 0;
  printf("frame_index\tpkt_type\tsize\tpos\tpts\tdts\tduration\n");
  for (;;) {
    ret = av_read_frame(fmt_ctx, pkt);
    if (ret < 0) {
      P("av_image_fill_arrays error, ret=%d", ret);
      goto save_first_frame_on_error;
    }
    if (pkt->stream_index != video_stream_index) {
      continue;
    }
    // 开始输出信息
    frame_index++;
    printf("%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\n", frame_index, pkt->flags,
           pkt->size, pkt->pos, pkt->pts, pkt->dts, pkt->duration);
  }
  P("success");
save_first_frame_on_error:
  av_packet_free(&pkt);
  avcodec_free_context(&codec_ctx);
  avformat_close_input(&fmt_ctx);
  return ret;
}

基于上面的程序,我修改为:当整个mp4已经在内存中时,能不能从内存中解析每个视频帧:


struct buffer_data {
  uint8_t *ptr;
  size_t size;
};

static int read_packet_1(void *opaque, uint8_t *buf, int buf_size) {
  struct buffer_data *bd = (struct buffer_data *)opaque;
  buf_size = FFMIN(buf_size, bd->size);

  if (!buf_size)
    return AVERROR_EOF;
  P("ptr:%p size:%zu, buf size=%d", bd->ptr, bd->size, buf_size);

  /* copy internal buffer data to buf */
  memcpy(buf, bd->ptr, buf_size);
  bd->ptr += buf_size;
  bd->size -= buf_size;

  return buf_size;
}

enum {
  SuccessOfShow,
  ErrOfAlloc,
  ErrOfAllocContext,
};

int show_frame_detail_from_mem(void *data, uint64_t len) {
  av_log_set_level(AV_LOG_DEBUG);
  int ret = 0;
  AVFormatContext *fmt_ctx = NULL;
  AVIOContext *avio_ctx = NULL;
  uint8_t *avio_ctx_buffer = NULL;
  const size_t avio_ctx_buffer_size = 1024 * 4; // 缓冲区大小
  struct buffer_data bd = {
      .ptr = (uint8_t *)data, .size = (size_t)len};
  avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
  if (!avio_ctx_buffer) {
    P("av_malloc fail");
    return ErrOfAlloc;
  }
  P("");
  // 创建自定义 AVIOContext
  avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd,
                                read_packet_1, NULL, NULL);
  if (!avio_ctx) {
    P("Could not allocate AVIOContext");
    ret = ErrOfAllocContext;
    goto on_error;
  }
  P("");
  //
  fmt_ctx = avformat_alloc_context();
  if (!fmt_ctx) {
    P("Could not allocate format context");
    ret = ErrOfAllocContext;
    goto on_error;
  }
  fmt_ctx->pb = avio_ctx;
  fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
  // 打开输入文件
  P("");
  // AVDictionary *options = NULL;
  // av_dict_set(&options, "analyzeduration", "10000000", 0); // 10秒
  // av_dict_set(&options, "probesize", "5000000", 0);        // 5MB
  ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
  if (ret < 0) {
    P("Could not open input");
    goto on_error;
  }
  P("");
  AVCodecContext *codec_ctx = NULL;
  int video_stream_index = -1;
  for (int i = 0; i < fmt_ctx->nb_streams; i++) {
    if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
      video_stream_index = i;
      break;
    }
  }
  P("");
  if (video_stream_index == -1) {
    P("Could not find video stream");
    ret = -1;
    goto on_error;
  }
  //
  AVCodecParameters *codec_params =
      fmt_ctx->streams[video_stream_index]->codecpar;
  const AVCodec *decoder = avcodec_find_decoder(codec_params->codec_id);
  if (NULL == decoder) {
    P("Could not find decoder");
    ret = -1;
    goto on_error;
  }
  P("");
  codec_ctx = avcodec_alloc_context3(decoder);
  if (NULL == codec_ctx) {
    P("Could not allocate codec context");
    ret = -1;
    goto on_error;
  }
  ret = avcodec_parameters_to_context(codec_ctx, codec_params);
  if (ret < 0) {
    P("Could not copy codec parameters to context,ret=%d", ret);
    ret = -1;
    goto on_error;
  }
  P("");
  ret = avcodec_open2(codec_ctx, decoder, NULL);
  if (ret < 0) {
    P("Could not open codec, ret=%d", ret);
    ret = -1;
    goto on_error;
  }
  //
  AVPacket *pkt = av_packet_alloc();
  ret = av_new_packet(pkt, codec_ctx->width * codec_ctx->height);
  if (ret < 0) {
    P("av_new_packet error, ret=%d", ret);
    ret = -1;
    goto on_error;
  }
  int frame_index = 0;
  //bd.ptr = data;
  //bd.size = len;
  printf("frame_index\tpkt_type\tsize\tpos\tpts\tdts\tduration\n");
  for (;;) {
    P("");
    ret = av_read_frame(fmt_ctx, pkt);
    if (ret < 0) {
      P("av_read_frame error, ret=%d", ret);
      goto on_error;
    }
    if (pkt->stream_index != video_stream_index) {
      av_packet_unref(pkt);
      continue;
    }
    // 开始输出信息
    frame_index++;
    printf("%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\n", frame_index, pkt->flags,
           pkt->size, pkt->pos, pkt->pts, pkt->dts, pkt->duration);
    av_packet_unref(pkt);
  }
  P("success");
on_error:
  if (pkt != NULL) {
    av_packet_free(&pkt);
  }
  if (codec_ctx != NULL) {
    avcodec_free_context(&codec_ctx);
  }
  if (avio_ctx_buffer != NULL) {
    av_free(avio_ctx_buffer);
  }
  if (fmt_ctx != NULL) {
    avformat_close_input(&fmt_ctx);
    // avformat_free_context(fmt_ctx);
  }
  if (avio_ctx != NULL) {
    avio_context_free(&avio_ctx);
  }
  return ret;
}

上述的代码出现两个问题:

  1. 当一个视频不是 faststart 格式时(mdat在前,moov在后)
  • avformat_open_input(&fmt_ctx, NULL, NULL, NULL); 这行代码读完了 buffer 中的所有数据。(其实也合理,毕竟 moov 在尾部)
  • 使用 av_read_frame() 的时候返回错误码:
  1. 当视频是 faststart 格式时:
  • 可以正常输出每一帧的字节数,但是出现大量错误提醒:“[NULL @ 0x14a904080] Invalid NAL unit size (-161925121 > 57227).”

原本以为 ffmpeg api 已经无比成熟,各种例子也应该是完美无瑕,看来有的坑必须还是要踩一踩。

posted on 2025-01-15 16:41  ahfuzhang  阅读(129)  评论(0)    收藏  举报