近几个星期在做360度视频编码的过程中遇到一个很棘手的问题,就是调用gstreamer1.0的imxvideoconvert_ipu vpuenc_h264 元件进行转码 + 编码时遇到 wait_for_comp_timeout 错误提示,导致无法完成编码,只能进行硬件重启才能恢复。重启后再次发生这个问题时间随机。下面详细讲述解决这个问题的过程,否则就对不起近日的辛苦。

产生问题环境

MCU:imx6qdl,4核
kernel:fsl-yocto-L4.1.15 编译器版本:fsl-imx-fb
gstreamer1.0 pipeline:appsrc、 imxvideoconvert_ipu、vpuenc_h264、appsink
应用需求:360度图像经过MIPI接口,然后经IPU转换后合成为一副全景图像,实时显示到framebuffer的fb0节点;格式为BGR32,然后需要将BGR格式数据的全景图像转换为YUV格式,再通过VPU编码为h264帧,最后通过4G 推送h264码流到服务器。

问题描述

使用gstreamer1.0库以上4个元件构成pipeline,由appsrc实时获取fb0节点的数据,经过imxvideoconvert_ipu转码为YUV格式,再经过vpuenc_h264元件编码,最后通过appsink获取编码后的h264帧进行推流;要求达到每秒25帧。
实际测试时,出现imx-ipuv3 2400000.ipu: ERR:[0xd8fc2000]-no:0x8f771 “wait_for_comp_timeout” ret:0,line:2962,转码超时误提示,一旦出现此情况,就变成每秒只能完成一帧转换;出现此错误的时间可能在系统运行到1分钟、10分钟、半小时、1小时、2小时、4小时以上不等,随机出现,没有规律。

问题解决过程

1、首先百度此问题

直接贴上串口打印的错误提示,经过一堆搜索阅读,最后发现一个遇到类似问题的人,网页:https://community.nxp.com/message/980742?commentID=980742#comment-980742
不过最开始只是简单阅读,并没有深入理解其中每一句话的意思,只是看看人家有没有解决此问题,发现也没有。
经过大量的百度与Google搜索,即便发现与此问题有关的页面,也没有提到如何解决此问题。感觉这个问题,无法掌控,没有解决的思路。

2、怀疑自己代码写的有问题

毕竟写gstreamer1.0库的人,应该比我要厉害,还是先从自己身上找问题吧。是不是我用生产者,消费者模型,在转码、编码、推流的过程中有问题,导致数据没给到IPU,仔细检查代码,但是用gstreamer1.0的库,其实我需要写的代码很少,只需要完成appsrc的start_feed、stop_feed与appsink的new_h264_sample_on_appsink;这3个回调函数就可以了。
start_feed函数:用于appsrc元件数据传送给imxvideoconvert_ipu后,需要下一次的数据
stop_feed函数:用于appsrc元件数据feed满之后,停止start_feed函数传送数据
new_h264_sample_on_appsink:表示得到一帧新的H264帧
这些函数加起来也不超过150行。行吧,没办法只能慢慢尝试。
2.1 先把推流的部分屏蔽
先把推流部分代码屏蔽,关闭socket连接,将获得的H264帧直接保存为文件,没有socket连接与传输这部分后,CPU的占用率小幅下降,经过8个多小时的测试,还是会出现此问题,心塞啊。期间还怕超过每秒25帧,故意使用延时减低IPU转码的速度,发现减低转码速度后,的确可以延长出现问题的时间,但是还是会出现。
2.2 删除程序中的多线程、生产者与消费者模型
没用,还是会出现问题
2.1 在以上屏蔽基础上,将获得H264帧直接丢弃
这下转码帧率更快了,出现问题的速度也更快了。
经过以上测试与代码屏蔽,实际我写的代码已经不超过100行了。经过仔细的分析可以得出以下结论:
1、转码帧率越快,问题出现速度越快
2、CPU负荷越重,出现问题的速度越快
自己的代码也没几行了,没法在怀疑自己的代码了,那就只能转向怀疑gstreamer1.0库的代码有问题了,但是库的代码我也不能修改,没办法,只能自己在另写一个IPU转换的代码看看

3、怀疑imxvideoconvert_ipu库有问题,自己写一个

自己写一个谈何容易,我还不清楚IPU该怎么用能!经过百度搜索、kernel中关于IPU的代码阅读,主要是ipu_device.c文件经过高人指点最终写出如下代码:

static int fb_init(void)
{printf("=========== imx60 360 get fb0 info start ==========\n");memset (&FbInfo, 0, sizeof (FbInfoStruct));fd_ipu = open("/dev/mxc_ipu", O_RDWR);fd_fb = open("/dev/fb0", O_RDWR);ioctl(fd_fb, FBIOGET_VSCREENINFO, &FbInfo.vinfo);ioctl(fd_fb, FBIOGET_FSCREENINFO, &FbInfo.finfo);FbInfo.width = FbInfo.vinfo.xres;FbInfo.height = FbInfo.vinfo.yres;FbInfo.bpp = FbInfo.vinfo.bits_per_pixel;FbInfo.rowsize = FbInfo.width * (FbInfo.bpp >> 3);FbInfo.offset = FbInfo.rowsize * FbInfo.vinfo.yoffset;FbInfo.total_len = FbInfo.vinfo.xres_virtual * FbInfo.vinfo.yres_virtual * (FbInfo.bpp >> 3);FbInfo.real_len = FbInfo.width * FbInfo.height * (FbInfo.bpp >> 3);printf("================= var screen info =================\n");printf("  sz  [%d x %d] %d\n", FbInfo.width, FbInfo.height, FbInfo.bpp);printf("  vsz [%d x %d]\n", FbInfo.vinfo.xres_virtual, FbInfo.vinfo.yres_virtual);printf("  pan     : (%d, %d)\n", FbInfo.vinfo.xoffset, FbInfo.vinfo.yoffset);printf("  off     : %d\n", FbInfo.offset);printf("============ imx60 360 get fb0 info end ===========\n");return 0;
}
static void *ipu_thread(void *arg)
{int res;unsigned int yuv_size = 0;unsigned int ipu_index = 0;//pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask);sleep(2);yuv_size = FbInfo.vinfo.xres * FbInfo.vinfo.yres * 3 / 2;   // NV12 sizeprintf("yuv_size:%d\n", yuv_size);ipu_pmem.paddr = yuv_size;ipu_pmem.size = yuv_size;printf("before IPU_ALLOC pmem.paddr:%d\n", ipu_pmem.paddr);res = ioctl(fd_ipu, IPU_ALLOC, &ipu_pmem.paddr);printf("after IPU_ALLOC pmem.paddr:%d\n", ipu_pmem.paddr);ipu_pmem.vaddr = mmap(NULL, yuv_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_ipu, ipu_pmem.paddr);printf("pmem.vaddr:%d\n", (int)ipu_pmem.vaddr);bzero(&gtask, sizeof(gtask));gtask.input.width  = FbInfo.vinfo.xres;gtask.input.height = FbInfo.vinfo.yres;gtask.input.crop.w = FbInfo.vinfo.xres;gtask.input.crop.h = FbInfo.vinfo.yres;if(FbInfo.vinfo.bits_per_pixel == 16)gtask.input.format = IPU_PIX_FMT_RGB565;elsegtask.input.format = IPU_PIX_FMT_BGR32;FbInfo.offset = FbInfo.vinfo.xres * (FbInfo.vinfo.bits_per_pixel >> 3) * FbInfo.vinfo.yoffset;gtask.input.paddr = FbInfo.finfo.smem_start + FbInfo.offset;gtask.output.width = FbInfo.vinfo.xres;gtask.output.height = FbInfo.vinfo.yres;gtask.output.crop.w = FbInfo.vinfo.xres;gtask.output.crop.h = FbInfo.vinfo.yres;gtask.output.format = IPU_PIX_FMT_NV12;gtask.output.paddr = ipu_pmem.paddr;gtask.priority = IPU_TASK_PRIORITY_HIGH;gtask.task_id = IPU_TASK_ID_PP;while(res != IPU_CHECK_OK){printf("IPU check task!\n");res = ioctl(fd_ipu, IPU_CHECK_TASK, &gtask);}while(1){ipu_index++;//printf("ipu_index:%d\n",ipu_index);res = ioctl(fd_ipu, IPU_QUEUE_TASK, &gtask);if(res < 0){printf("IPU queue task failed\n");}else{sem_post(&sem_frame_put);sem_wait(&sem_frame_get);ioctl(fd_fb, FBIOGET_VSCREENINFO, &FbInfo.vinfo);ioctl(fd_fb, FBIOGET_FSCREENINFO, &FbInfo.finfo);FbInfo.offset = FbInfo.vinfo.xres * (FbInfo.vinfo.bits_per_pixel >> 3) * FbInfo.vinfo.yoffset;gtask.input.paddr = FbInfo.finfo.smem_start + FbInfo.offset;}}munmap(ipu_pmem.vaddr, ipu_pmem.size);ioctl(fd_ipu, IPU_FREE, &ipu_pmem.paddr);return 0;
}

自己写的代码转码就是快,可以达到每秒40帧,可是快又有毛用,还不是死的更快。
继续尝试去掉多线程、存文件、丢弃帧,都会在不定时间内出现问题。真是心都碎了,这还是我可以解决的问题吗,开始怀疑自己的人生。硬着头皮也要上吧,咱就怀疑kernel关于IPU的驱动写的不好,能不能看看有什么问题?

4、怀疑kernel的IPU驱动有问题,找问题

这可问题大了,kernel关于IPU的驱动,那么多,从哪里找问题呢?
4.1 阅读手册与驱动源代码
咱还是从源头开始,先阅读 IMX6DQRM.pdf手册关于IPU的部分吧。(可别小瞧这一点,仔细阅读手册,让我明白知道了IPU的工作过程与原理,要是没有这一步,问题还真找不出来)
阅读手册,首先从IPU的框架图开始,贴上一张图

经过仔细阅读手册与分析我明白了360度图像合成与我的360 视频编码分别需要IPU的那几个部分
1、360度视频合成需要使用如图所示IPU的CSI、VDI、IDMAC、IC、DMFC、IRT、DP、DI这几个部分
2、360度视频编码需要使用如图所示IPU的IDMAC、IC两个部分
阅读手册后知道,IDMAC在IPU中至关重要,数据都是通过它里面的52个channel来传输
手册阅读结束后,就要开始分析IPU驱动的框架,这个网址的博客给了我很多有用的信息:https://blog.csdn.net/yanbixing123/article/category/6380610
现在我知道,我的错误提示是在ipu_device.c的函数do_task的2962行。在第3不解决问题的过程中,我知道了如何去操作IPU,但还不知道IPU是如何实现我要求的转换操作的。
经过分析,kernel的IPU驱动框架,将imx6q的2个IPU生成4个内核线程,为什么是4个呢,看下面的struct ipu_channel_tabel定义就知道了。下面贴出有用的宏定义,小伙伴们可根据提示去查找。

#define MXC_IPU_MAX_NUM     2
#define MXC_DI_NUM_PER_IPU  2
#define IPU_PP_CH_VF    (IPU_TASK_ID_VF - 1)
#define IPU_PP_CH_PP    (IPU_TASK_ID_PP - 1)
#define MAX_PP_CH   (IPU_TASK_ID_MAX - 1)
struct ipu_channel_tabel {struct mutex    lock;u8      used[MXC_IPU_MAX_NUM][MAX_PP_CH];u8      vdoa_used;
};

ipu_channel_tabel数据结构中包含一个used二维数组,imx6q内含2个ipu,每个ipu包含MAX_PP_CH(2)个通道,内核驱动据此实现4个内核线程。
4.2 分析IPU驱动框架
在解决问题的第三步中,自己写了一个操作IPU进行RGB数据到YUV数据的转换过程,那时还不清楚,驱动是怎样一个实现过程,经过阅读源代码,知道了在第三步实现的IPU转码主要是
static long mxc_ipu_ioctl(struct file *file,unsigned int cmd, unsigned long arg) 此函数完成,
IPU_ALLOC:负责分配内核空间
IPU_CHECK_TASK:检查ipu_task转换数据的合法性
IPU_QUEUE_TASK:再次检查ipu_task转换数据的合法性,并确定转换类型,int ipu_queue_task(struct ipu_task *task)函数完成转换操作
代码最终执行static void do_task(struct ipu_task_entry *t) 完成转换操作,并返回结果。
分析我的转换操作应该是t->set.mode = IC_MODE、t->set.task = IC_PP,那就开始验证吧
通过在do_task函数内部加上内核打印,结果发现和我预想的不一样,在进行转换操作时,%99的情况走的是t->set.task = IC_VF逻辑,偶尔走一下IC_PP逻辑,但是这两者都能完成转码操作,这是为什么呢?
搞不清楚为什么,就先接受这个事实吧,继续分析走这两个不同的逻辑,有什么区别:
if (t->set.task & IC_PP) {
t->set.ic_chan = MEM_PP_MEM;
dev_dbg(ipu->dev, “[0x%p]ic channel MEM_PP_MEM\n”, (void *)t);
//printk(“——–t->set.mode: %d !\n”, t->set.mode);
//printk(“——–t->set.task & IC_PP true!\n”);
//printk(“——–t->task_id: %d !\n”, t->task_id);
} else if (t->set.task & IC_VF) {
t->set.ic_chan = MEM_PRP_VF_MEM;
//printk(“——–t->set.mode: %d !\n”, t->set.mode);
//printk(“——–t->set.task & IC_VF true!\n”);
//printk(“——–t->task_id: %d !\n”, t->task_id);
dev_dbg(ipu->dev, “[0x%p]ic channel MEM_PRP_VF_MEM\n”, (void *)t);
区别很明显,使用的IDMAC的channel不一样。
IC_PP channel:MEM_PP_MEM = _MAKE_CHAN(6, 11, 15, 18, 22),
IC_VF channel :MEM_PRP_VF_MEM = _MAKE_CHAN(5, 12, 14, 17, 21),
还记得我在一步搜索到的网页吗,仔细阅读一下,发现一个重要线索:
this error happens usually as result of data starvation when data that should be sent to display is not available in IPUs internal FIFO. Most probable cause for this issue is big load on the DDR memory bus.
Try for example reduce fps@resolution.
翻译过来就是:IPU从内部FIFO取不到数据,饿死了,结果转换操作就超时了,造成此问题的原因是 DDR的负担过重
那为什么FIFO内没数据呢 ,DDR内存的数据是通过IDMAC的channel传输到内部FIFO,然后再由IPU获取,FIFO没数据,很大的可能就是IDMAC没有把DDR内存的数据传输到FIFO,因为DDR很忙,那么DDR在忙什么?
还记得我在第二步的总结吗:
1、转码帧率越快,问题出现速度越快
2、CPU负荷越重,出现问题的速度越快
嗯,这个确实会造成DDR很忙,但是手册上不是说IPU支持60fps帧率转换吗,现在不到25fps就挂了,为什么呢?
小伙伴们,咱们别忘了一个重要的事情的,360度视频的合成,它又是怎么使用IPU的呢?
经过添加内核打印,发现360度视频合成需要t->set.mode= IC_MODE | ROT_MODE
使用的IDMAC的channel包含:
CSI_PRP_VF_MEM = _MAKE_CHAN(20, NO_DMA, NO_DMA, NO_DMA, 21),
MEM_VDI_PRP_VF_MEM_P = _MAKE_CHAN(21, 8, 14, 17, 21),
MEM_VDI_PRP_VF_MEM = _MAKE_CHAN(22, 9, 14, 17, 21),
MEM_VDI_PRP_VF_MEM_N = _MAKE_CHAN(23, 10, 14, 17, 21),
小伙伴们,仔细看看与 MEM_PRP_VF_MEM = _MAKE_CHAN(5, 12, 14, 17, 21),有什么不一样,_MAKE_CHAN宏定义如下:
_MAKE_CHAN(num, v_in, g_in, a_in, out) ((num << 24) | (v_in << 18) | (g_in << 12) | (a_in << 6) | out)
也就是说,360度的视频合成,大量使用了IC_VF 操作的IDMAC的channel,且主要是输出channel:21,所以不是DDR忙,而是IDMAC忙于360度视频合成数据的输入输出,而我的视频转码只能在合成操作的空闲时间执行,鬼知道它什么时候空闲啊?
在阅读手册过程中我注意到IDMAC的实时与非实时操作用AXI ID来区分,经过分析内核与此有关的代码是

static struct ipu_platform_type ipu_type_imx6q = {。。。省略无关部分//.ch0123_axi = 0,//.ch23_axi = 0,//.ch27_axi = 0,//.ch28_axi = 0,//.normal_axi = 1,//.idmac_used_bufs_en_r = false,//.idmac_used_bufs_en_w = false,.ch0123_axi = 0,.ch23_axi = 0,.ch27_axi = 2,.ch28_axi = 3,.normal_axi = 1,.idmac_used_bufs_en_r = true,.idmac_used_bufs_en_w = true,.idmac_used_bufs_max_r = 0x3,.idmac_used_bufs_max_w = 0x3,.smfc_idmac_12bit_3planar_bs_fixup = false,
};

是不是与此有关,经过此修改验证,在达到30fps帧率的情况下,一定程度延迟了问题出现的时间,但是不能根除此问题,因为一旦出现问题,必须硬件重启才能恢复,因此我们的要求是,永远不出现问题。
既然修改这里不行,我也不知道360度视频合成啥时候会空闲,那我转码操作不跟你使用同一个通道总可以了吧,前面我们不是说有1%的情况使用IC_PP通道也可以完成转换操作吗,那咱就修改IC_VF channel :MEM_PRP_VF_MEM = _MAKE_CHAN(5, 12, 14, 17, 22)。
完了,坏大事了,结果发先360度视频合成也需要此操作,这一改,视频合成都不能正常工作了,这岂不是搞大了,赶紧改回来吧,这条路是走不通了。
走到这一步,我基本确定就是因为我的360度视频转码操作与合成操作使用了相同的IDMAC的channel,造成竞争,会在随机的时间发生IDMAC没有在有效时间内把数据传输到转码操作使用内部FIFO中,造成IPU数据饿死,超时。
现在还剩下什么路呢,这就回到之前内核打印发现%99的情况走的是t->set.task = IC_VF逻辑,偶尔走一下IC_PP逻辑,为什么会这样呢,要是大量的走IC_PP逻辑,它们的IDMAC输入输出channel与IC_VF完全不一样,是不是就不用竞争,可以解决问题了。
为什么内核给它分配的是IC_VF操作,我可以改吗?在哪里改?
再次仔细阅读驱动代码,发现内核是在static int _get_vdoa_ipu_res(struct ipu_task_entry *t)函数中分配的转码操作类型,来看下面一段代码:

for (i = 0; i < max_ipu_no; i++) {ipu = ipu_get_soc(i);if (IS_ERR(ipu))dev_err(t->dev, "no:0x%x,found_vdoa:%d, ipu:%d\n",t->task_no, found_vdoa, i);used = &tbl->used[i][IPU_PP_CH_VF];if (t->set.mode & VDI_MODE) {if (0 == *used) {*used = 1;found_ipu = 1;break;}} else if ((t->set.mode & IC_MODE) || only_rot(t->set.mode)) {if (0 == *used) {t->task_id = IPU_TASK_ID_VF;if (t->set.mode & IC_MODE)t->set.task |= IC_VF;if (t->set.mode & ROT_MODE)t->set.task |= ROT_VF;*used = 1;found_ipu = 1;break;}} elsedev_err(t->dev, "no:0x%x,found_vdoa:%d, mode:0x%x\n",t->task_no, found_vdoa, t->set.mode);
}
if (found_ipu)goto next;
for (i = 0; i < max_ipu_no; i++) {ipu = ipu_get_soc(i);if (IS_ERR(ipu))dev_err(t->dev, "no:0x%x,found_vdoa:%d, ipu:%d\n",t->task_no, found_vdoa, i);if ((t->set.mode & IC_MODE) || only_rot(t->set.mode)) {used = &tbl->used[i][IPU_PP_CH_PP];if (0 == *used) {t->task_id = IPU_TASK_ID_PP;if (t->set.mode & IC_MODE)t->set.task |= IC_PP;if (t->set.mode & ROT_MODE)t->set.task |= ROT_PP;*used = 1;found_ipu = 1;break;}}
}

仔细看看,我的360度转码操作根据IC_MODE被分配为IC_VF操作,条件是used = &tbl->used[i][IPU_PP_CH_VF]为空,即这个内核线程是空闲的,就会被分配为IC_VF操作;看到没有,IC_PP的分配操作在IC_VF的后面,难怪大部分情况都是分配为IC_VF操作。
既然IC_PP也可以完成转码操作,而且与360视频合成使用的IDMAC 的channel完全不同,不会产生竞争;那我就让你把我的转码操作都分配为IC_PP操作不就行了,修改其实很简单,就是把分配为IC_PP操作的代码放到分配IC_VF的前面就行了,代码如下:

static int _get_vdoa_ipu_res(struct ipu_task_entry *t)
{int     i;struct ipu_soc  *ipu;u8      *used;uint32_t    found_ipu = 0;uint32_t    found_vdoa = 0;struct ipu_channel_tabel    *tbl = &ipu_ch_tbl;mutex_lock(&tbl->lock);if (t->set.mode & VDOA_MODE) {if (NULL != t->vdoa_handle)found_vdoa = 1;else {found_vdoa = tbl->vdoa_used ? 0 : 1;if (found_vdoa) {tbl->vdoa_used = 1;vdoa_get_handle(&t->vdoa_handle);} else/* first get vdoa->ipu resource sequence */goto out;if (t->set.task & VDOA_ONLY)goto out;}}for (i = 0; i < max_ipu_no; i++) {ipu = ipu_get_soc(i);if (IS_ERR(ipu))dev_err(t->dev, "no:0x%x,found_vdoa:%d, ipu:%d\n",t->task_no, found_vdoa, i);if ((t->set.mode & IC_MODE) || only_rot(t->set.mode)) {used = &tbl->used[i][IPU_PP_CH_PP];if (0 == *used) {t->task_id = IPU_TASK_ID_PP;if (t->set.mode & IC_MODE)t->set.task |= IC_PP;if (t->set.mode & ROT_MODE)t->set.task |= ROT_PP;*used = 1;found_ipu = 1;break;}}}if (found_ipu)goto next;for (i = 0; i < max_ipu_no; i++) {ipu = ipu_get_soc(i);if (IS_ERR(ipu))dev_err(t->dev, "no:0x%x,found_vdoa:%d, ipu:%d\n",t->task_no, found_vdoa, i);used = &tbl->used[i][IPU_PP_CH_VF];if (t->set.mode & VDI_MODE) {if (0 == *used) {*used = 1;found_ipu = 1;break;}} else if ((t->set.mode & IC_MODE) || only_rot(t->set.mode)) {if (0 == *used) {t->task_id = IPU_TASK_ID_VF;if (t->set.mode & IC_MODE)t->set.task |= IC_VF;if (t->set.mode & ROT_MODE)t->set.task |= ROT_VF;*used = 1;found_ipu = 1;break;}} elsedev_err(t->dev, "no:0x%x,found_vdoa:%d, mode:0x%x\n",t->task_no, found_vdoa, t->set.mode);}next:if (found_ipu) {t->ipu = ipu;t->ipu_id = i;t->dev = ipu->dev;if (atomic_inc_return(&t->res_get) == 2)dev_err(t->dev,"ERR no:0x%x,found_vdoa:%d,get ipu twice\n",t->task_no, found_vdoa);}
out:dev_dbg(t->dev,"%s:no:0x%x,found_vdoa:%d, found_ipu:%d\n",__func__, t->task_no, found_vdoa, found_ipu);mutex_unlock(&tbl->lock);if (t->set.task & VDOA_ONLY)return found_vdoa;else if (t->set.mode & VDOA_MODE)return found_vdoa && found_ipu;elsereturn found_ipu;
}

改完之后,提心吊胆的开始进行测试。
1分钟、10分钟、半小时、1小时、4小时;时间就这样慢慢过去了没有出现,不行,还得加压测试,写一个占用100%CPU的程序(注意:imx6q为4核)一起跑,又1个小时过去,没有出现。那就所有该运行的程序全部运行起来吧
直到当天下班,还没出现,到现在为止有一点欣慰,但还不敢保证,接着运行一晚上吧,第二天一早过来看,已经100多万帧了,还没挂,我是不是可以认为,这个问题已经解决了呢?
经过后来的一个多星期测试,不管是低帧率、高帧率、CPU占用率高还是低,确实再也没有出现:
imx-ipuv3 2400000.ipu: ERR:[0xd8fc2000]-no:0x8f771 “wait_for_comp_timeout” ret:0,line:2962,这个错误提示了。
这就是这个问题解决过程,耗时2个多星期,比较有用的资源是:
手册:IMX6DQRM.pdf
网页:https://community.nxp.com/message/980742?commentID=980742#comment-980742
博客:https://blog.csdn.net/yanbixing123/article/category/6380610
最后总结:解决问题不能放过任何一个小细节,不能想着走捷径,最笨的也许是最有用的方法。

Gstreamer1.0与imx6q IPU转码 wait_for_comp_timeout 问题解决心路历程相关推荐

  1. 读《台湾码农的心路历程》

    (以下引自原文,希望以后静下来的时候慢慢看) 我不会去问当初我留下来的话,现在我会怎样又怎样.但我知道一件事,人要看下一个十年,然后再下一个十年.有一天,你我都会老,都会 35,都会 45,都会 55 ...

  2. 一位台湾码农的心路历程

    我是个半路出家的资讯人员(类似于大陆的 IT 人,编辑注),说起来很见笑,我没有深厚背景,也不是正规本科或大学,只是个专科毕业的,补个二技/科大文凭,大学毕业后连一个像样的国立研究所也考不上.台清交成 ...

  3. gstreamer-1.0学习笔记

    gstreamer-1.0安装 apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-pl ...

  4. [导入]SunriseUpload.0.9.1的源码分析(七)

    接着分析了几个小时的SunriseUpload.0.9.1的源码. 终于明白了作者的整体思路.在此就做一个总结. 首先,要想能上传很大的文件,我们就必须编写一个HttpModule来自己处理用户上传的 ...

  5. Anaconda Python3.6 OpenCV4.1.0 Ubuntu 16.04源码编译

    Anaconda Python3.6 OpenCV4.1.0 Ubuntu 16.04源码编译 转载于:https://blog.csdn.net/phdsky/article/details/782 ...

  6. android 5.0播放swf flash源码Demo

    android 5.0播放swf flash源码Demo 安卓5.0flash播放源码 android flash 播放器 swf 由于之前webview方法播放flash在新的系统中不可用.所以so ...

  7. 基于thinkphp5.0和支付宝面对面扫码支付DEMO制作的扫码支付

    ​ 基于thinkphp5.0和支付宝面对面扫码支付DEMO制作的扫码支付 今天接口申请下来,下载了官方demo对着调试了一天,终于实现想要的功能,先看图 选择支付宝支付 跳出二维码弹窗 手机支付宝进 ...

  8. 点云配准2:icp算法在PCL1.10.0上的实现+源码解析

    目录 本文最后实现的配准实例 点云配准系列 准备 程序结构 主程序 1.为什么要降采样 2.体素降采样原理 3.点云更新 icp 配准前的参数设置 icp配准算法内部 对应点对确定(determine ...

  9. 最新超唯美Like_Girl V5.0.0恋爱主题博客源码

    正文: 最新超唯美Like_Girl V5.0.0恋爱主题博客源码,后端使用了Ajax异步请求提交数据 配合插件提醒弹窗. 程序: wwxsrd.lanzoub.com/iXDks0jbkr5i 图片 ...

最新文章

  1. 图像轮廓的提取和绘制
  2. 美多商城之订单(结算订单)
  3. 一种生成不重复数的算法
  4. 用 namspace 隔离 DHCP 服务 - 每天5分钟玩转 OpenStack(90)
  5. 分布式事务 TCC-Transaction 源码分析 —— 项目实战
  6. 【机器学习】知识框图总结
  7. 【JVM】javap命令行分析(a++ + ++a)的虚拟机指令
  8. HOUR 4 Expressions, Statements, and Operators
  9. ios 图片裁剪框架_iOS 图片裁剪与修改
  10. android自定义屏幕,Android自定义屏保
  11. font-family常见中文字体对应的英文名称
  12. angularjs+uib-pagination实现同一页面多个分页功能
  13. rar password recover(rar密码恢复工具) v2.0.0.0
  14. 2021年全国安全生产月 安全知识网络竞赛 链工宝“测测你的安全力”题库 三百多道真题含答案
  15. python3爬虫抓取链家上海租房信息
  16. PCB板的绘制原来是这样完成的——布线
  17. 批量识别PDF/OFD/PNG/JPG电子发票到EXCEL
  18. linux服务器,docker部署es6.8.7,开启密码认证
  19. 关于python全局性解释锁(GIL)
  20. Java常用类——Java教案(六)

热门文章

  1. 英特尔迅驰二代风尚盛典亲身体验
  2. 电路原理图中的各种地(数字地、模拟地、信号地、直流地、交流地、屏蔽地)的区别
  3. 对偶方法(Dual Methods)
  4. 计算机组成原理学习笔记——校验码
  5. 【转】深入理解JavaScript系列(8):S.O.L.I.D五大原则之里氏替换原则LSP
  6. centos7磁盘挂载及目录扩容
  7. 一文看懂电容的种类及其在电路中的作用
  8. openwrt fota
  9. 如何在 Excel 中实现区间查找式的 VLOOKUP
  10. Qt Qml 查看所有字体、添加字体库、使用字体库的方法