imx6 vpu程序分析

imx6 vpu程序分析

背景

最近公司需要将产品与外界的设备进行流媒体通信,经过一系列的方案研究确立,最终把功能完成,目前能够顺利的播放基于h264的流媒体文件,趁着闲暇时间对相关的东西做一些笔记记录,方便以后追溯。
这里主要分析imx6 的vpu 测试程序,有一部分功能是基于这个来实现的。考虑到 imx6 的 vpu 固件代码不开源,相关的vpu 操作代码根据文档来执行,其接口函数看文档就行,本篇就不过多阐述,驱动也不加以分析。简单来说,就是vpu 的应用以及注意点。

版本

linux内核版本:3.10.17
vpu 测试程序版本:imx-test-3.10.17

程序功能说明

  • 处理的任务:-D 解码 -E 编码 -L Loopback模式 -C 解析文件,从文件中获取参数的设置
  • 如果有多个任务,会为每个任务开启一个线程来进行处理(解码任务、编码任务)
  • 输入方式为:文件 和 网络(udp),文件:对应格式的文件(h264、MP4等)
    输出方式为:文件 和 网络,文件:普通文件(存储)、网络文件(发送出去),IPU(imx6x不适用),video17(调用v4l2架构直接显示)

关键源码注释

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;
}

后记

浏览了一下,通过对关键点的提取,关键代码的注释,拿到源码后,应该可以很好的切入了,或回忆或熟悉会事半功倍。
里面拓展提到的一些东西是项目中实际中遇到的,有些东西没有说太细,但是基本的东西摸清楚了,后面的东西看是否能熟练使用以及变化了。

你可能感兴趣的:(linux内核,Graphic)