赞
踩
最近公司需要将产品与外界的设备进行流媒体通信,经过一系列的方案研究确立,最终把功能完成,目前能够顺利的播放基于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; }
浏览了一下,通过对关键点的提取,关键代码的注释,拿到源码后,应该可以很好的切入了,或回忆或熟悉会事半功倍。
里面拓展提到的一些东西是项目中实际中遇到的,有些东西没有说太细,但是基本的东西摸清楚了,后面的东西看是否能熟练使用以及变化了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。