关于ffmpeg分离mp4视频帧硬解码遇到的问题

本文介绍如何使用ffmpeg将MP4格式的视频转换为适用于硬件解码的标准ES流。通过解析AVCodecContext结构中的extradata,针对不同编码类型采取相应措施,并提供了一个具体的函数h264_mp4toannexb_filter实现。

    前段时间在WINCE6搞好一阵时间利用ffmpeg分离mp4出来的视频帧送到硬件解码去,但发现不少问题,我用的是三星的S5PC100处理器,想利用三星提供的MFC的API来进行硬解码,但是根本无法解码,根据调试输出的信息,少了信息头数据,上网查找了好久后,发现mp4/mkv/mov/flv封装的h.264,因为为了减少存储,减少了一些头信息,不是标准的ES流,而从av_read_frame出来的码流一般都是完整的一帧数据的

 

      经过研究的ffmpeg的原代码发现,一般视频帧的信息头数据都会保存在AVCodecContext结构中的extradata ,extradata_size的变量中,  如果是MPEG4编码的话,直接把extradata加在经过av_read_frame分离的视频数据帧前面就可以硬解码了,若是H264的话,我是运用如下函数得到标准ES流的.

 #define EINVAL          22
#define ENOMEM          12
#define AV_RB16(x)  ((((const uint8_t*)(x))[0] << 8) | ((const uint8_t*)(x))[1])
#define AV_RB32(x)  ((((const uint8_t*)(x))[0] << 24) | /
 (((const uint8_t*)(x))[1] << 16) | /
 (((const uint8_t*)(x))[2] <<  8) | /
 ((const uint8_t*)(x))[3])

#define AV_RB32(x)  ((((const uint8_t*)(x))[0] << 24) | /
 (((const uint8_t*)(x))[1] << 16) | /
 (((const uint8_t*)(x))[2] <<  8) | /
 ((const uint8_t*)(x))[3])

#define AV_WB32(p, d) do { /
 ((uint8_t*)(p))[3] = (d); /
 ((uint8_t*)(p))[2] = (d)>>8; /
 ((uint8_t*)(p))[1] = (d)>>16; /
 ((uint8_t*)(p))[0] = (d)>>24; } while(0)

 

typedef struct H264BSFContext {
 uint8_t  length_size;
 uint8_t  first_idr;
 uint8_t *sps_pps_data;
 uint32_t size;
} H264BSFContext;

 

static void alloc_and_copy(uint8_t **poutbuf,          int *poutbuf_size,
         const uint8_t *sps_pps, uint32_t sps_pps_size,
         const uint8_t *in,      uint32_t in_size)
{
 uint32_t offset = *poutbuf_size;
 uint8_t nal_header_size =  4;

 *poutbuf_size += sps_pps_size+in_size+nal_header_size;
 *poutbuf = (unsigned char *)av_realloc(*poutbuf, *poutbuf_size);
    if (*poutbuf==NULL)
        return ;
   
 if (sps_pps)
  memcpy(*poutbuf+offset, sps_pps, sps_pps_size);
 memcpy(*poutbuf+sps_pps_size+nal_header_size+offset, in, in_size);
 //if (!offset)
  AV_WB32(*poutbuf+offset+sps_pps_size, 1);
 //else
 //{
 // (*poutbuf+offset+sps_pps_size)[0] = (*poutbuf+offset+sps_pps_size)[1] = 0;
 // (*poutbuf+offset+sps_pps_size)[2] = 1;
 //}
}

static int h264_mp4toannexb_filter(H264BSFContext *ctx,  AVCodecContext *avctx, const char *args,
           uint8_t  **poutbuf, int *poutbuf_size,
           const uint8_t *buf, int      buf_size,
           int keyframe)
{

 uint8_t unit_type;
 uint32_t nal_size, cumul_size = 0;

 /* nothing to filter */
 if (!avctx->extradata || avctx->extradata_size < 6)
 {
  *poutbuf = (uint8_t*) buf;
  *poutbuf_size = buf_size;
  return 0;
 }

 /* retrieve sps and pps NAL units from extradata */
 if (!ctx->sps_pps_data)
 {
  uint16_t unit_size;
  uint32_t total_size = 0;
  uint8_t *out = NULL, unit_nb, sps_done = 0;
  const uint8_t *extradata = avctx->extradata+4;
  static const uint8_t nalu_header[4] = {0, 0, 0, 1};

  /* retrieve length coded size */
  ctx->length_size = (*extradata++ & 0x3) + 1;
  if (ctx->length_size == 3)
   return AVERROR(EINVAL);

  /* retrieve sps and pps unit(s) */
  unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
  if (!unit_nb)
  {
   unit_nb = *extradata++; /* number of pps unit(s) */
   sps_done++;
  }
  while (unit_nb--)
  {
   unit_size = AV_RB16(extradata);
   total_size += unit_size+4;
   if (extradata+2+unit_size > avctx->extradata+avctx->extradata_size)
   {
    av_free(out);
    return AVERROR(EINVAL);
   }
   out = (unsigned char *)av_realloc(out, total_size);
   if (!out)
    return AVERROR(ENOMEM);
   memcpy(out+total_size-unit_size-4, nalu_header, 4);
   memcpy(out+total_size-unit_size,   extradata+2, unit_size);
   extradata += 2+unit_size;

   if (!unit_nb && !sps_done++)
    unit_nb = *extradata++; /* number of pps unit(s) */
  }

  ctx->sps_pps_data = out;
  ctx->size = total_size;
  ctx->first_idr = 1;
  //char str[512]={0};
  //                           memcpy(str, out, total_size);
 }
 *poutbuf_size = 0;
 *poutbuf = NULL;
 do
 {
  if (ctx->length_size == 1)
   nal_size = buf[0];
  else if (ctx->length_size == 2)
   nal_size = AV_RB16(buf);
  else
  {
   nal_size = AV_RB32(buf);

   
  }

  buf += ctx->length_size;
  unit_type = *buf & 0x1f;

  /* prepend only to the first type 5 NAL unit of an IDR picture */
  if ( unit_type != 6 )
  {
   alloc_and_copy(poutbuf, poutbuf_size, ctx->sps_pps_data, ctx->size, buf, nal_size);
   ctx->first_idr = 0;
  }
  //else
  //{
  // alloc_and_copy(poutbuf, poutbuf_size, NULL, 0,  buf, nal_size);
  // if (!ctx->first_idr && unit_type == 1)
  //  ctx->first_idr = 1;
  //}

  buf += nal_size;
  cumul_size += nal_size + ctx->length_size;
 } while (cumul_size < buf_size);

 return 1;
}

 

通过调用 h264_mp4toannexb_filter函数后就可以得到标准的ES流了,先定义一个H264BSFContext变量,然后传给h264_mp4toannexb_filter函数,

如: 

 H264BSFContext *ctx=(H264BSFContext *) malloc(sizeof(H264BSFContext));
  memset(ctx, 0, sizeof(H264BSFContext));

  while(av_read_frame(pFormatCtx, &packet) >= 0)
  {    

       if(packet.stream_index==videoStream)
      { 
 
          unsigned char *lp=NULL;   //输入的指针
           int len;
           h264_mp4toannexb_filter(ctx, pCodecCtx, NULL, &lp, &len, packet.data, packet.size, 1);
           memcpy((LPBYTE)virInBuf, pCodecCtx->extradata, pCodecCtx->extradata_size);

       }

  }

硬件就可能解码了,这个我是在S5PC100测试成功的,如果是S3C6410应该是类似的,如果是avi文件很容易了,通过av_read_frame直接把它送硬件解码器进行解码就行了

在 RK3588 平台上实现 FFmpeg 对 MJPG 格式的码,需要结合 Rockchip 提供的多媒体处理平台(MPP)来实现高效码。以下是实现该功能的步骤和关键点: ### 码环境准备 1. **确认件支持** RK3588 支持 MJPG 格式的码功能,通过 Rockchip 的 MPP(Media Process Platform)模块进行控制和调度。在开始之前,需确认内核中已加载了 `rkmpp` 模块,并且系统中已安装 Rockchip 提供的 GPU 驱动和 V4L2 驱动。 2. **FFmpeg 编译配置** FFmpeg 必须启用 `rkmpp` 码器,并且在编译时加入 Rockchip 的 SDK 支持。配置编译时需要指定以下参数: ```bash ./configure --enable-shared --disable-static --enable-rkmpp --enable-libdrm --enable-vaapi --enable-v4l2-m2m ``` 确保 `rkmpp` 已被正确识别并启用,可通过 `ffmpeg -decoders | grep rkmpp` 验证是否成功启用码器。 ### 实现 MJPG 码 1. **使用 FFmpeg 命令行进行测试** 在 RK3588 平台上,可以通过 FFmpeg 命令行测试 MJPG 格式的码能力。例如,从 USB 摄像头拉取 MJPG 流并进行码: ```bash ffmpeg -f v4l2 -video_size 1920x1080 -input_format mjpeg -i /dev/video0 -c:v copy -f null - ``` 如果需要进一步处理码后的数据,可以使用 `rkmpp` 码器将 MJPG 流码为 YUV 格式: ```bash ffmpeg -f v4l2 -video_size 1920x1080 -input_format mjpeg -i /dev/video0 -c:v mjpeg_rkmpp -pix_fmt yuv420p output.yuv ``` 以上命令将 MJPG 流通过 `rkmpp` 码器码为 YUV 格式输出[^3]。 2. **编程实现 MJPG 码** 在 C/C++ 层面,可以使用 FFmpeg API 结合 `rkmpp` 码器实现 MJPG 流的码。关键步骤如下: - 初始化 FFmpeg 上下文并打开输入流。 - 查找视频流并获取对应的码器。 - 设置码器为 `mjpeg_rkmpp`。 - 分配帧缓冲区并开始码循环。 示例代码如下: ```c #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> int main(int argc, char *argv[]) { AVFormatContext *format_ctx = NULL; AVCodecContext *codec_ctx = NULL; const AVCodec *codec = NULL; int video_stream_index = -1; avformat_network_init(); if (avformat_open_input(&amp;format_ctx, argv[1], NULL, NULL) != 0) { fprintf(stderr, "Could not open input\n"); return -1; } if (avformat_find_stream_info(format_ctx, NULL) < 0) { fprintf(stderr, "Failed to get input stream information\n"); return -1; } for (int i = 0; i < format_ctx->nb_streams; i++) { AVStream *stream = format_ctx->streams[i]; codec = avcodec_find_decoder_by_name("mjpeg_rkmpp"); if (codec) { codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, stream->codecpar); if (avcodec_open2(codec_ctx, codec, NULL) < 0) { fprintf(stderr, "Failed to open decoder\n"); return -1; } video_stream_index = i; break; } } if (video_stream_index == -1) { fprintf(stderr, "No video stream found\n"); return -1; } AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); while (av_read_frame(format_ctx, pkt) >= 0) { if (pkt->stream_index == video_stream_index) { int ret = avcodec_send_packet(codec_ctx, pkt); while (ret >= 0) { ret = avcodec_receive_frame(codec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; // 处理码后的帧数据 printf("Received frame %d\n", frame->pts); } } av_packet_unref(pkt); } avformat_close_input(&amp;format_ctx); avcodec_free_context(&amp;codec_ctx); av_frame_free(&amp;frame); return 0; } ``` ### 调试与优化 1. **检查码器状态** 如果码失败,需检查 `rkmpp` 初始化参数是否正确,尤其是码类型是否与实际码流匹配。例如,如果码流是 MJPG 格式,则 `mpp_init` 必须指定 `MPP_VIDEO_CodingMJPEG` 作为码类型[^2]。 2. **确保输入数据完整性** MJPG 码流可能分散在多个 `AVPacket` 中,需确保每次送入码器的数据是一帧完整的图像数据。可以通过 FFmpeg 的 `av_read_frame` 和 `avcodec_send_packet` 进行数据完整性检查。 3. **性能优化** 为了提高码效率,建议使用多线程技术,将码、后处理和显示分离到不同的线程中。同时,合理设置缓冲区大小和帧率控制参数,以减少 CPU 占用率和内存开销。 ###
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值