最近公司需要将产品与外界的设备进行流媒体通信,经过一系列的方案研究确立,最终把功能完成,目前能够顺利的播放基于h264的流媒体文件,趁着闲暇时间对相关的东西做一些笔记记录,方便以后追溯。
这里主要分析imx6 的vpu 测试程序,有一部分功能是基于这个来实现的。考虑到 imx6 的 vpu 固件代码不开源,相关的vpu 操作代码根据文档来执行,其接口函数看文档就行,本篇就不过多阐述,驱动也不加以分析。简单来说,就是vpu 的应用以及注意点。
linux内核版本:3.10.17
vpu 测试程序版本:imx-test-3.10.17
main.c main() 函数
int
#ifdef _FSL_VTS_
vputest_main(int argc, char *argv[])
#else
main(int argc, char *argv[])
#endif
{
int err, nargc, i, ret = 0;
char *pargv[32] = {0}, *dbg_env;
pthread_t sigtid;
#ifdef COMMON_INIT
vpu_versioninfo ver;
#endif
int ret_thr;
#ifndef COMMON_INIT
srand((unsigned)time(0)); /* init seed of rand() */
#endif
dbg_env=getenv("VPU_TEST_DBG");
if (dbg_env)
vpu_test_dbg_level = atoi(dbg_env);
else
vpu_test_dbg_level = 0;
/* 解析主要的参数选项 : -D(vpu解码) -E(vpu编码) -L(Loopback模式) -C(从文件中获取参数)
* 重点1:当为-C时,会在该函数中解析出文件中的参数,除此之外的,只标记是哪种任务,后面通过
* parse_args 函数进行解析
* 重点2: 解析出来的参数放在结构体 input_arg 中:它是一个全局变量,为
* static struct input_argument input_arg[MAX_NUM_INSTANCE] 结构体数组类型*/
err = parse_main_args(argc, argv);
if (err) {
goto usage;
}
/* instance: 用来标记任务实例个数的,例如输入的参数中可能包含 -D -E,即解码、编码混合,那么就是两个
* instance, 这个关系到后面的开任务的个数,如果为一个instance的话,后面将只有一个线程来处理,
* 如果有多个的话,会按照任务个数来开启对应的线程进行处理 */
if (!instance) {
goto usage;
}
info_msg("VPU test program built on %s %s\n", __DATE__, __TIME__);
#ifndef _FSL_VTS_
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
pthread_create(&sigtid, NULL, (void *)&signal_thread, NULL);
#endif
#ifdef COMMON_INIT
err = vpu_Init(NULL); /* 有关于vpu的操作,初始化 */
if (err) {
err_msg("VPU Init Failure.\n");
return -1;
}
err = vpu_GetVersionInfo(&ver); /* 有关于vpu的操作,得到版本信息 */
if (err) {
err_msg("Cannot get version info, err:%d\n", err);
vpu_UnInit();
return -1;
}
info_msg("VPU firmware version: %d.%d.%d_r%d\n", ver.fw_major, ver.fw_minor,
ver.fw_release, ver.fw_code);
info_msg("VPU library version: %d.%d.%d\n", ver.lib_major, ver.lib_minor,
ver.lib_release);
#else
// just to enable cpu_is_xx() to be used in command line parsing
err = vpu_Init(NULL);
if (err) {
err_msg("VPU Init Failure.\n");
return -1;
}
vpu_UnInit();
#endif
/* 下面的就是根据instance 的个数来进行相应的操作,当个数大于1的时候会开启对应个数线程来
* 进行相应的任务操作 */
if (instance > 1) {
for (i = 0; i < instance; i++) {
#ifndef COMMON_INIT
/* sleep roughly a frame interval to test multi-thread race
especially vpu_Init/vpu_UnInit */
usleep((int)(rand()%ONE_FRAME_INTERV));
#endif
if (using_config_file == 0) { /* 这里重点就是是否从文件中得到相应的参数,如果不是,
* 还需要进行上面提到过的进一步的细化解析命令行参数
* parse_args() 函数来完成,具体实现见代码 */
get_arg(input_arg[i].line, &nargc, pargv);
err = parse_args(nargc, pargv, i);
if (err) {
vpu_UnInit();
goto usage;
}
}
/* check_params(): 在真正使用得到的各种参数之前对参数进行一遍检查,有些默认的东西会在此添加
* 这里有一点需要注意:在上面的参数赋值中如果参数的来源来自于解析文件,而decode时候的输入方式
* 又空着没有指定的话,在这里面会把输入方式指定为从网络输入 */
if (check_params(&input_arg[i].cmd,
input_arg[i].mode) == 0) {
/* open_files(): 把上面参数指定的输入文件、输出文件一次性打开,返回句柄供下面使用
* 注意点:输入方式为:文件 和 网络(udp),文件:对应格式的文件(h264、MP4等)
* 输出方式为:文件 和 网络,文件:普通文件(存储)、网络文件(发送出去),IPU(imx6x不适用)*/
if (open_files(&input_arg[i].cmd) == 0) {
if (input_arg[i].mode == DECODE) {
/* 重点: 在这里就是上面说的多个instance的时候了,会通过创建对应的线程来进行任务的操作:
* decode解码任务 和 encode 编码任务*/
pthread_create(&input_arg[i].tid,
NULL,
(void *)&decode_test, /* 任务开始 */
(void *)&input_arg[i].cmd);
} else if (input_arg[i].mode ==
ENCODE) {
pthread_create(&input_arg[i].tid,
NULL,
(void *)&encode_test, /* 任务开始 */
(void *)&input_arg[i].cmd);
}
}
}
}
} else {
if (using_config_file == 0) {
get_arg(input_arg[0].line, &nargc, pargv);
err = parse_args(nargc, pargv, 0); /* 解读同上 */
if (err) {
vpu_UnInit();
goto usage;
}
}
if (check_params(&input_arg[0].cmd, input_arg[0].mode) == 0) { /* 解读同上 */
if (open_files(&input_arg[0].cmd) == 0) { /* 解读同上 */
if (input_arg[0].mode == DECODE) {
ret = decode_test(&input_arg[0].cmd); /* 任务开始 */
} else if (input_arg[0].mode == ENCODE) {
ret = encode_test(&input_arg[0].cmd); /* 任务开始 */
} else if (input_arg[0].mode == TRANSCODE) {
ret = transcode_test(&input_arg[0].cmd);
}
close_files(&input_arg[0].cmd);
} else {
ret = -1;
}
} else {
ret = -1;
}
if (input_arg[0].mode == LOOPBACK) {
encdec_test(&input_arg[0].cmd);
}
}
/* 等待对应的线程任务结束 */
if (instance > 1) {
for (i = 0; i < instance; i++) {
if (input_arg[i].tid != 0) {
pthread_join(input_arg[i].tid, (void *)&ret_thr);
if (ret_thr)
ret = -1;
close_files(&input_arg[i].cmd);
}
}
}
#ifdef COMMON_INIT
vpu_UnInit(); /* vpu操作,uninit,资源的注销等 */
#endif
return ret;
usage:
info_msg("\n%s", usage);
return -1;
}
dec.c 中decode_test() 函数
int
decode_test(void *arg)
{
struct cmd_line *cmdl = (struct cmd_line *)arg;
vpu_mem_desc mem_desc = {0};
vpu_mem_desc ps_mem_desc = {0};
vpu_mem_desc slice_mem_desc = {0};
vpu_mem_desc vp8_mbparam_mem_desc = {0};
struct decode *dec;
int ret, eos = 0, fill_end_bs = 0, fillsize = 0;
#ifndef COMMON_INIT
vpu_versioninfo ver;
ret = vpu_Init(NULL);
if (ret) {
err_msg("VPU Init Failure.\n");
return -1;
}
ret = vpu_GetVersionInfo(&ver);
if (ret) {
err_msg("Cannot get version info, err:%d\n", ret);
vpu_UnInit();
return -1;
}
info_msg("VPU firmware version: %d.%d.%d_r%d\n", ver.fw_major, ver.fw_minor,
ver.fw_release, ver.fw_code);
info_msg("VPU library version: %d.%d.%d\n", ver.lib_major, ver.lib_minor,
ver.lib_release);
#endif
vpu_v4l_performance_test = 0;
dec = (struct decode *)calloc(1, sizeof(struct decode));
if (dec == NULL) {
err_msg("Failed to allocate decode structure\n");
ret = -1;
goto err;
}
mem_desc.size = STREAM_BUF_SIZE;
ret = IOGetPhyMem(&mem_desc); /* 得到物理地址 */
if (ret) {
err_msg("Unable to obtain physical mem\n");
goto err;
}
if (IOGetVirtMem(&mem_desc) <= 0) { /* 得到虚拟地址 */
err_msg("Unable to obtain virtual mem\n");
ret = -1;
goto err;
}
/* decode解码任务的设置阶段,注意重要的数据结构 struct decode *dec,
* 数据存储于此 */
dec->phy_bsbuf_addr = mem_desc.phy_addr;
dec->virt_bsbuf_addr = mem_desc.virt_uaddr;
dec->reorderEnable = 1;
dec->tiled2LinearEnable = 0;
dec->userData.enable = 0;
dec->mbInfo.enable = 0;
dec->mvInfo.enable = 0;
dec->frameBufStat.enable = 0;
dec->mjpgLineBufferMode = 0;
dec->mjpegScaleDownRatioWidth = 0; /* 0,1,2,3 */
dec->mjpegScaleDownRatioHeight = 0; /* 0,1,2,3 */
dec->cmdl = cmdl;
if (cpu_is_mx6x() && (dec->cmdl->format == STD_MJPG)
&& dec->mjpgLineBufferMode) {
dec->mjpg_cached_bsbuf = malloc(STREAM_BUF_SIZE);
if (dec->mjpg_cached_bsbuf == NULL) {
err_msg("Failed to allocate mjpg_cached_bsbuf\n");
ret = -1;
goto err;
}
}
if (cmdl->format == STD_RV)
dec->userData.enable = 0; /* RV has no user data */
if (cmdl->format == STD_AVC) {
ps_mem_desc.size = PS_SAVE_SIZE;
ret = IOGetPhyMem(&ps_mem_desc);
if (ret) {
err_msg("Unable to obtain physical ps save mem\n");
goto err;
}
dec->phy_ps_buf = ps_mem_desc.phy_addr;
}
/* open decoder */
ret = decoder_open(dec); /* 里面就是根据上面的设置进行vpu的打开操作 */
if (ret)
goto err;
cmdl->complete = 1;
if (dec->cmdl->src_scheme == PATH_NET)
fillsize = 1024;
if (cpu_is_mx6x() && (dec->cmdl->format == STD_MJPG) && dec->mjpgLineBufferMode) {
ret = mjpg_read_chunk(dec);
if (ret < 0)
goto err1;
else if (ret == 0) {
err_msg("no pic in the clip\n");
ret = -1;
goto err1;
}
} else {
ret = dec_fill_bsbuffer(dec->handle, cmdl,
dec->virt_bsbuf_addr,
(dec->virt_bsbuf_addr + STREAM_BUF_SIZE),
dec->phy_bsbuf_addr, fillsize, &eos, &fill_end_bs);
if (fill_end_bs)
err_msg("Update 0 before seqinit, fill_end_bs=%d\n", fill_end_bs);
if (ret < 0) {
err_msg("dec_fill_bsbuffer failed\n");
goto err1;
}
}
cmdl->complete = 0;
/* parse the bitstream */
ret = decoder_parse(dec); /* 解析得到的第一帧数据,其实就是在解析数据头,
* 然后配置接下来的动态性的参数,例如数据的格式(h264、h263或者mpeg等)
* 一帧数据的大小(540 * 480)*/
if (ret) {
err_msg("decoder parse failed\n");
goto err1;
}
/* allocate slice buf */
if (cmdl->format == STD_AVC) {
slice_mem_desc.size = dec->phy_slicebuf_size;
ret = IOGetPhyMem(&slice_mem_desc);
if (ret) {
err_msg("Unable to obtain physical slice save mem\n");
goto err1;
}
dec->phy_slice_buf = slice_mem_desc.phy_addr;
}
if (cmdl->format == STD_VP8) {
vp8_mbparam_mem_desc.size = 68 * (dec->picwidth * dec->picheight / 256);
ret = IOGetPhyMem(&vp8_mbparam_mem_desc);
if (ret) {
err_msg("Unable to obtain physical vp8 mbparam mem\n");
goto err1;
}
dec->phy_vp8_mbparam_buf = vp8_mbparam_mem_desc.phy_addr;
}
/* allocate frame buffers */
ret = decoder_allocate_framebuffer(dec); /* 缓存空间的准备 */
if (ret)
goto err1;
/* start decoding */
/* 里面就是对vpu的一些实质性的操作,然后将vpu处理后的数据经过解析后进行相应的操作:
* 1. 往ipu里面写?2.通过v4l2往video17 里面写?使之显示
* 或者 3.仅仅作为文件数据直接写到普通文件中,保存下来
* 提示,项目中把数据拿出来,放到gpu里面去渲染就是走的往普通文件写的流程,把本应该
* 往普通文件里面写的数据给导流到gpu 中渲染,最终的呈现在屏幕上
* 另外,在这里还使用了ipu 对数据进行格式的变换:vpu处理得到的是 yuv420,输入到gpu为rgb
* (关于ipu 和 gpu 的东西是另外一个程序中涉及到的,这里是因为在具体的项目中使用到了,
* 留在这里做以后的提醒使用,在这里不展开阐述其使用)*/
ret = decoder_start(dec);
err1:
decoder_close(dec);
/* free the frame buffers */
decoder_free_framebuffer(dec);
err:
if (cmdl->format == STD_AVC) {
IOFreePhyMem(&slice_mem_desc);
IOFreePhyMem(&ps_mem_desc);
}
if (cmdl->format == STD_VP8)
IOFreePhyMem(&vp8_mbparam_mem_desc);
if (dec->mjpg_cached_bsbuf)
free(dec->mjpg_cached_bsbuf);
IOFreeVirtMem(&mem_desc);
IOFreePhyMem(&mem_desc);
if (dec)
free(dec);
#ifndef COMMON_INIT
vpu_UnInit();
#endif
return ret;
}
浏览了一下,通过对关键点的提取,关键代码的注释,拿到源码后,应该可以很好的切入了,或回忆或熟悉会事半功倍。
里面拓展提到的一些东西是项目中实际中遇到的,有些东西没有说太细,但是基本的东西摸清楚了,后面的东西看是否能熟练使用以及变化了。