2 S3C6410处理器视频编解码方法
S3C6410视频编解码软件架构[4]如图1所示。底层为操作系统空间,上层为用户空间,视频编***系统以设备文件的形式使用,使用的方法和普通文件一样,包括文件打开和关闭、文件读写和输入/输出控制(ioctl,input/output control)。
图1 S3C6410视频编解码软件架构
具体操作方法如下:
① 通过open函数打开编解码器设备文件;
② 使用mmap方法在用户空间和驱动空间之间映射输入/输出缓存空间,这样做的好处是可以快速进行数据输入/输出;
③ 通过ioctl设备编解码参数,初始化编解码器;
④ 输入数据,通过ioctl执行编解码过程,输出数据;
⑤ 通过close方法关闭编解码器设备文件。
值得注意的是,无论编码还是解码,处理的数据都是以一帧帧的形式操作的,所以第4步是一个不断循环的过程,直到所有数据处理完成。另外,虽然编解码器以设备文件的形式使用,但是它不能使用标准的文件读写操作,查看编解码的设备驱动可以发现,其文件读写函数是空的,这一点三星公司的开发文档并没有说明。
3 H.264硬件编解码实现
FFmpeg的H.264硬件编解码[5]实现就是自定义一个视频编***库中。这个视频编解码器使用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 320x240 ?r 50 ?f video4linux2 ?i /dev/video2 ?vcodec s3cx264 test.mp4 可以看到FFmpegg正使用s3cx264编码器将USB摄像头采集的数据编码压缩成test.mp4文件。test.mp4能够正常播放显示。
以上测试说明已经成功地将s3cx264硬件视频编码器添加到了FFmpeg中,能够编码视频数据,可以运用到其他使用FFmpeg库的多媒体程序中。
|