基于FFmpeg的H.264视频硬件编解码在S3C6410处理器上的实现

http://news.eeworld.com.cn/mcu/article_2018021337779.html

虽然FFmpeg提供了简单的应用程序编程接口(API),可以很方便地实现多种格式的视频软件编解码[3],但是软件编解码在处理复杂视频编解码(如H.264)时无法运用到处理速度不快、内存空间不多的嵌入式环境中。为了在资源有限的嵌入式环境下使用FFmpeg实现复杂视频编解码,下面在分析FFmpeg视频编码流程和S3C6410处理器视频编解码方法的基础上,阐述嵌入式Linux操作系统下基于FFmpeg的H.264硬件编解码在S3C6410处理器上的实现方法。

1 FFmpeg视频编解码流程

FFmpeg主要有encode/decode、muxer/demuxer和内存操作3个模块。encode/decode模块用于音视频的编码和解码,存放在libavcodec子目录中;muxer/demuxer模块用于音频和视频的合并与分离(也称混合器模块),存放在libavformat目录中;内存等常用模块存放于libavutil目录中。下面以解码过程为例分析FFmpeg视频编解码流程。

解码基本流程共分4步:

① 注册所有可能用到的编解码器和混合器。av_register_all(void)函数中通过执行 REGISTER_MUXDEMUX(X,x)和REGISTER_ENCDEC(X,x),把所有FFmpeg支持的混合器和编解码器相关信息以链式的结构存放在内存中。

② 打开视频文件。av_open_input_file(AVFormatContext **ic_ptr,const char *filename,AVInputFormat *fmt,int buf_size,AVFormatParameters *ap)函数中侦测文件的格式,根据文件格式从链式的混合器中找到相对应的混合器(demuxer)并分离出视频信息。

③ 获取视频信息。通过av_find_stream_info(AVFormatContext *ic)函数获取视频格式。根据视频格式,在链式的视频解码器中找到相应的视频解码器,并通过avcodec_open(AVCodecContext *avctx,AVCodec *codec)函数将解码器打开用于下一步视频的解码。

④ 解码一帧视频,通过 avcodec_decode_video(AVCodecContext *avctx,AVFrame *picture,int *got_picture_ptr,const uint8_t *buf,int buf_size)函数解码一帧视频。

FFmpeg的编码过程与解码过程类似,不同的是第3步根据要求编码的格式在链式的视频编码器中找到相应的视频编码器,并执行编码过程。

通过以上对FFmpeg视频编解码流程分析可以知道,为了在FFmpeg中添加自定义的视频编解码器,并在程序运行时使用这个编解码器,关键在于如下两点:

① 根据FFmpeg对编解码器的描述,实现自定义编解码器。

② 通过REGISTER_ENCDEC(X,x)函数将自定义的视频编解码器添加到视频编解码器链中。在获取视频信息时,保证需要编码或解码的视频能找到视频编解码器链中自定义的视频编解码器。

S3C6410视频编解码软件架构[4]如图1所示。底层为操作系统空间,上层为用户空间,视频编解码器通过驱动和操作系统以设备文件的形式使用,使用的方法和普通文件一样,包括文件打开和关闭、文件读写和输入/输出控制(ioctl,input/output control)。

图1 S3C6410视频编解码软件架构

具体操作方法如下:

① 通过open函数打开编解码器设备文件;

② 使用mmap方法在用户空间和驱动空间之间映射输入/输出缓存空间,这样做的好处是可以快速进行数据输入/输出;

③ 通过ioctl设备编解码参数,初始化编解码器;

④ 输入数据,通过ioctl执行编解码过程,输出数据;

⑤ 通过close方法关闭编解码器设备文件。

值得注意的是,无论编码还是解码,处理的数据都是以一帧帧的形式操作的,所以第4步是一个不断循环的过程,直到所有数据处理完成。另外,虽然编解码器以设备文件的形式使用,但是它不能使用标准的文件读写操作,查看编解码的设备驱动可以发现,其文件读写函数是空的,这一点三星公司的开发文档并没有说明。

3 H.264硬件编解码实现

  FFmpeg的H.264硬件编解码[5]实现就是自定义一个视频编解码器,加入到FFmpeg库中。这个视频编解码器使用S3C6410处理视频硬件编解码功能来实现H.264的视频编码和解码过程,这样使用FFmpeg库的多媒体程序可以用访问FFmpeg其他编解码器一样的方法使用这个自定义的编解码器。添加自定义编解码器的关键是根据FFmpeg中对编解码的描述定义编解码器,并实现定义中的相关函数。

在libavcodec/avcodec.h中的AVCodec结构体是定义FFmpeg编解码器的关键结构体,包括编解码器的名字、类型(声音/视频)、编解码器的识别 (CodecID)、支持格式和一些用于初始化、编码、解码和关闭的函数指针。

  typedef struct AVCodec {

  const char *name;

  enum CodecType type;

  enum CodecID id;

  int priv_data_size;

  int (*init)(AVCodecContext *);

  int (*encode)(AVCodecContext *,uint8_t *buf,int buf_size,void *data);

  int (*close)(AVCodecContext *);

  int (*decode)(AVCodecContext *,void *outdata,int *outdata_size,

  uint8_t *buf,int buf_size);

  int capabilities;

  struct AVCodec *next;

  void (*flush)(AVCodecContext *);

  const AVRational *supported_framerates;

  const enum PixelFormat *pix_fmts;

  } AVCodec;

  H.264硬件编解码器定义如下:

  AVCodec s3cx264_encoder = {

  .name=”s3cx264″,

  .type=AVMEDIA_TYPE_VIDEO,

  .id=CODEC_ID_H264,

  .init=X264_init,

  .encode=X264_frame,

  .decode=X264_decode,

  .close=X264_close,

  …

  };

  解码器的名字为s3cx264,类型为视频。CodecID为H264,表示这个解码器用于H.264视频编解码。初始化、编码、解码和关闭函数指针分别指向X264_init、X264_frame、X264_decodec和X264_close函数。

  添加s3cx264编解码器到编解器链中,关键是通过修改libavcodec/allcodecs.c文件实现,修改如下:

  REGISTER_ENCDEC (ASV1,asv1);

  REGISTER_ENCDEC (S3CX264,s3cx264);

  //添加s3cx264编解码器

  REGISTER_ENCDEC (ASV2,asv2);

  这样,在程序运行时调用av_register_all(void)函数后,就可以把自定义的编解码器s3cx264添加到FFmpeg存放在内存中的解编码器链中。值得提出的是,对同一个视频格式FFmpeg有多个编解码器与之相对应。如H.264格式的视频,FFmpeg本身就带有对应的软解码器,现在添加了硬解码器,为了避免不确定是哪一个解码器在执行,可以把自定义的硬件编解码器在注册时放在注册过程的最前面,这样编解码器在添加到解编器链中时就会放在靠前的位置,查找时就可以优于软件解码器找到硬解码器。

  把硬件编解码器s3cx264注册到编解码器链后,还要完成X264_init、X264_frame、X264_decodec和X264_close函数,编解码器才能正常工作。以下结合前面对S3C6410视频编解码过程的分析,以编码为例详细阐述实现过程。

  定义X264Context结构体,保存设备文件描述符、编码参数和输入/输出地址等信息,用于FFmpeg模块间数据的传递:

  typedef struct X264Context {

  int dev_fd;

  uint8_t *addr;

  s3c_mfc_enc_init_arg_t enc_init;

  s3c_mfc_enc_exe_arg_t enc_exe;

  s3c_mfc_get_buf_addr_arg_t get_buf_addr;

  uint8_t *in_buf,*out_buf;

  AVFrame out_pic;

  } X264Context;

  X264_init实现的是编码器初始化过程, 用于编码器设备文件的打开、内存空间的映射、编码参数设置和获取编解码数据输入/输出地址。

  static av_cold int X264_init(AVCodecContext *avctx){

  X264Context *x4 = avctx->priv_data;

  //打开编码器设备文件

  x4->dev_fd = open(MFC_DEV_NAME,O_RDWR|O_NDELAY);

  //内存空间映射

  x4->addr = (uint8_t *) mmap(0,BUF_SIZE,PROT_READ |PROT_WRITE,MAP_SHARED,x4->dev_fd,0);

  //编码参数设置

  ioctl(x4->dev_fd,S3C_MFC_IOCTL_MFC_H264_ENC_INIT,&x4->enc_init);

  //获取输入/输出地址

  x4->get_buf_addr.in_usr_data = (int)x4->addr;

  ioctl(x4->dev_fd,S3C_MFC_IOCTL_MFC_GET_YUV_BUF_ADDR,&x4->get_buf_addr);

  x4->in_buf = (uint8_t *)x4->get_buf_addr.out_buf_addr;

  x4->get_buf_addr.in_usr_data = (int)x4->addr;

  ioctl(x4->dev_fd,S3C_MFC_IOCTL_MFC_GET_LINE_BUF_ADDR,&x4->get_buf_addr);

  x4->out_buf = (uint8_t *)x4->get_buf_addr.out_buf_addr;

  return 0;

  }

  ioctl的参数为S3C_MFC_IOCTL_MFC_H264_ENC_INIT,表示使用H.264编码。

  X264_frame函数执行编码过程。需要注意的是data参数保存了需要编码的数据,是一个四维的数组,要把它转换成一维数组用于S3C6410编码器输入。另外,编码数据存在空的情况,也就是空帧。这是需要处理的,方法是返回“0”,表示没有输出数据,否则程序运行时会出现段错误。

  static int X264_frame(AVCodecContext *ctx,uint8_t *buf,int bUFSize,void *data){

  ……

  //空间转换

  if(frame){

  memcpy(x4->in_buf,frame->data[0],ctx->width*ctx>height);

  memcpy(x4->in_buf+ctx->width*ctx->height,frame->data[1],ctx->width*ctx->height/4);

  memcpy(x4->in_buf+ctx->width*ctx->height+ctx->width*ctx->height/4,frame->data[2],

  ctx->width*ctx->height/4);

  }

  else

  return 0;//空帧,返回

  //执行编码过程

  ioctl(x4->dev_fd,S3C_MFC_IOCTL_MFC_H264_ENC_EXE,&x4->enc_exe);

  //编码数据输出

  bufsize = x4->enc_exe.out_encoded_size;

  memcpy(buf,x4->out_buf,bufsize);

  ……

  return bufsize;

  }

  X264_close关闭函数用于编码结束后的资源释放,包括取消空间映射和关闭设备文件。

  static av_cold int X264_close(AVCodecContext *avctx){

  …

  //取消空间映射

  munmap(x4->addr,BUF_SIZE);

  //关闭设备文件

  close(x4->dev_fd);

  return 0;

  }

  解码函数的实现过程类似于编码函数,包括空间转换、执行解码和解码数据输出。初始化时使用S3C_MFC_IOCTL_MFC_H264_DEC_INIT参数,执行时使用S3C_MFC_IOCTL_MFC_H264_ENC_EXE参数。

 4 运行测试

  s3cx264编解码器添加到FFmpeg后,可以通过以下方式测试:

  ① 用如下命令编译FFmpeg。

  ./configure –enable-cross-compile

  –arch=armv6 –cpu=armv6

  –target-os=linux –cross-prefix

  =/usr/LOCal/arm/4.3.2/bin/

  arm-linux-

  ② 运行 ./ffmpeg -codecs查看可以找到s3cx264编解码器,如图2所示。

  图2 FFmpeg显示s3cx264编解码器信息

  ③ 结合USB摄像头测试s3cx264编码。运行 ./ffmpeg -s 320×240 -r 50 -f video4linux2 -i /dev/video2 -vcodec s3cx264 test.mp4 可以看到FFmpegg正使用s3cx264编码器将USB摄像头采集的数据编码压缩成test.mp4文件。test.mp4能够正常播放显示。

  以上测试说明已经成功地将s3cx264硬件视频编码器添加到了FFmpeg中,能够编码视频数据,可以运用到其他使用FFmpeg库的多媒体程序中。

————————————————

 

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年5月14日
下一篇 2021年5月14日

相关推荐