下面随笔将对Hi3559AV100外接UVC/MJPEG相机实现实时采图设计的关键点-VDEC_Send_Stream线程进行分析,一两个星期前我写了有三篇系列随笔,已经实现了项目功能,大家可以参考下面随笔:

Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析

https://www.cnblogs.com/iFrank/p/14399421.html

Hi3559AV100外接UVC/MJPEG相机实时采图设计(二):V4L2接口的实现(以YUV422为例) :

https://www.cnblogs.com/iFrank/p/14403397.html

Hi3559AV100外接UVC/MJPEG相机实时采图设计(三):V4L2接口通过MPP平台输出 :

https://www.cnblogs.com/iFrank/p/14403620.html

  虽然已经实现了实时采图的功能,但是我还是对VDEC_Send_Stream线程进行细节分析,为后面Hi3559AV100外接UVC/MJPEG相机实现目标检测做准备,同时也让大家能够更好的了解实现的机制与原理,方便大家进行移植。

1、整体代码实现

  首先给出涉及的数据与函数,方便后面分析:

 1     /************************************************
2 step8: send stream to VDEC
3 *************************************************/
4 for(i=0; i<u32VdecChnNum; i++)
5 {
6 snprintf(stVdecSend[i].cFileName, sizeof(stVdecSend[i].cFileName), "test_jpeg.jpeg");
7 snprintf(stVdecSend[i].cFilePath, sizeof(stVdecSend[i].cFilePath), "%s", "/nfsroot");
8 stVdecSend[i].enType = astSampleVdec[i].enType;
9 stVdecSend[i].s32StreamMode = astSampleVdec[i].enMode;
10 stVdecSend[i].s32ChnId = i;
11 stVdecSend[i].s32IntervalTime = 1000;
12 stVdecSend[i].u64PtsInit = 0;
13 stVdecSend[i].u64PtsIncrease = 0;
14 stVdecSend[i].eThreadCtrl = THREAD_CTRL_START;
15 stVdecSend[i].bCircleSend = HI_TRUE;
16 stVdecSend[i].s32MilliSec = 0;
17 stVdecSend[i].s32MinBufSize = (astSampleVdec[i].u32Width * astSampleVdec[i].u32Height * 3)>>1;
18 }
19 SAMPLE_COMM_VDEC_StartSendStream(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);
20
21 SAMPLE_COMM_VDEC_CmdCtrl(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);
22
23 SAMPLE_COMM_VDEC_StopSendStream(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);

  程序中u32VdecChnNum表示你所开的VDEC通道,这个根据项目的实际需求来定,随后给出MJPEG输入的文件名cFileName和路径名cFilePath,随后的参数设置根据文档说明进行修改,也可直接默认使用。在完成参数设置后,进入关键的三个函数,分别为:

1 SAMPLE_COMM_VDEC_StartSendStream(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);
2
3 SAMPLE_COMM_VDEC_CmdCtrl(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);
4
5 SAMPLE_COMM_VDEC_StopSendStream(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);

  三者传入的参数一致,处理对象相同,现在我将一一分析各个函数的实现功能:

1.1、SAMPLE_COMM_VDEC_StartSendStream实现分析

  对于SAMPLE_COMM_VDEC_StartSendStream(),其最关键的是pthread_create:

1         pthread_create(&pVdecThread[i],  //对应于VdecThread[i]
2 0,
3 SAMPLE_COMM_VDEC_SendStream,
4 (HI_VOID *)&pstVdecSend[i]); //对应于stVdecSend[0]

  参考man pthread_create,pthread_create()函数在调用中启动一个新线程处理,给出pthread_create的特征:

1 #include <pthread.h>
2
3 int pthread_create(pthread_t *thread,
4 const pthread_attr_t *attr,
5 void *(*start_routine) (void *),
6 void *arg);

  新的线程通过调用start_routine()来开始执行,在此处,start_routine()为SAMPLE_COMM_VDEC_SendStream,arg<------->pstVdecSend[i]作为start_routine()的唯一参数传递;而thread这个标识符是用来引用在后续调用其他pthread功能,成功创建线程后,返回0。

  线程分两类,一类是joinable,一类是detached,对于joinable线程,需要用pthread_join()来等待线程结束并获取状态;而对于detached线程终止,其所用的资源系统会自动回收的,不需要进行操作,对于线程创建来说,默认是joinable线程,如果大家需要detached线程,则需要修改attr参数。

  当start_routine()设置为SAMPLE_COMM_VDEC_SendStream,函数的关键又转移到了start_routine下,所以分析重点又转了SAMPLE_COMM_VDEC_SendSteam,此函数下,首先是设置了线程名字,fopen打开MJPEG文件,并开辟了内存空间,为后面接收数据做准备,并清空了缓存区:

 1     //设置线程名字
2 prctl(PR_SET_NAME, "VideoSendStream", 0,0,0);
3
4 //cStreamFile = "/nfsroot/test_jpeg.jpeg"
5 snprintf(cStreamFile, sizeof(cStreamFile), "%s/%s", pstVdecThreadParam->cFilePath,pstVdecThreadParam->cFileName);
6
7 pu8Buf = malloc(pstVdecThreadParam->s32MinBufSize);
8
9 /*
10 清空标准输出缓冲区,
11 刷新输出缓冲区,即将缓冲区的东西输出到屏幕上
12 */
13 fflush(stdout);

  在完成准备工作后,进入了while循环,while循环主要完成下面几件事:

  (1)、用pstVdecThreadParam->eThreadCtrl来实现while循环继续与否:

 1         //另外线程控制
2 if (pstVdecThreadParam->eThreadCtrl == THREAD_CTRL_STOP)
3 {
4 break;
5 }
6 else if (pstVdecThreadParam->eThreadCtrl == THREAD_CTRL_PAUSE)
7 {
8 sleep(1);
9 continue;
10 }

  (2)、第二就是把MJPEG文件导入,读取长度等等,并进行MJPEG数据帧头帧尾判断, 接收数据。

  因为JPEG 文件的格式是分为一个一个的段来存储的,段的多少和长度并不是一定的。只要包含了足够的信息,该JPEG文件就能够被打开,呈现给人们。JPEG文件的每个段都一定包含两部分一个是段的标识,它由两个字节构成:第一个字节是十六进制0xFF,第二个字节对于不同的段,这个值是不同的。紧接着的两个字节存放的是这个段的长度(除了前面的两个字节0xFF和0xXX,X表示不确定。他们是不算到段的长度中的)。

  段的一般结构:

1 -----------------------------------------------------------------
2 名称 字节数 数据 说明
3 -----------------------------------------------------------------
4 段标识 1 FF 每个新段的开始标识
5 段类型 1 类型编码(称作“标记码”)
6 段长度 2 包括段内容和段长度本身,不包括段标识和段类型
7 段内容 ≤65533字节

  段类型:

 1 ---------------------------------------
2 名称 标记码 说明
3 ---------------------------------------
4 SOI D8 文件头
5 EOI D9 文件尾
6 SOF0 C0 帧开始(标准 JPEG)
7 SOF1 C1 同上
8 DHT C4 定义 Huffman 表(霍夫曼表)
9 SOS DA 扫描行开始
10 DQT DB 定义量化表
11 DRI DD 定义重新开始间隔
12 APP0 E0 定义交换格式和图像识别信息
13 COM FE 注释
14 -----------------------------------------------------------

  (3)、VDEC通道数据的发送,通过使用HI_MPI_VDEC_SendStream向视频解码通道发送码流数据,具体实现如下:

 1 HI_MPI_VDEC_SendStream(pstVdecThreadParam->s32ChnId, &stStream, pstVdecThreadParam->s32MilliSec); 

1.2、SAMPLE_COMM_VDEC_CmdCtrl实现分析

  因为前面设置了VDEC_THREAD_PARAM_S参数类型pstVdecSend[i[.bCircleSend == HI_TRUE,函数直接利用goto语句跳转到WHILE标签,在WHILE标签下,首先就会终端打印

1 printf("\nSAMPLE_TEST:press 'e' to exit; 'p' to pause; 'r' to resume; 'q' to query!\n");

  通过在终端输入字符'e'、'p'、'r'、'q'执行对应的操作,具体功能如上所示,实现如下:

 1         if (c == 'e')
2 break;
3 else if (c == 'r')
4 {
5 if (bVoPause == HI_TRUE)
6 {
7 for (i=0; i<s32ChnNum; i++)
8 {
9 pstVdecSend[i].eThreadCtrl = THREAD_CTRL_START;
10 }
11
12 for (VoLayer=0; VoLayer<VO_MAX_LAYER_NUM; VoLayer++)
13 {
14 for (VoChn=0; VoChn<VO_MAX_CHN_NUM; VoChn++)
15 {
16 s32Ret = HI_MPI_VO_ResumeChn(VoLayer, VoChn);
17 if(HI_SUCCESS != s32Ret)
18 {
19 printf("HI_MPI_VO_ResumeChn(%d, %d) fail for 0x%x!\n", VoLayer, VoChn, s32Ret);
20 }
21 }
22 }
23 printf("VO Resume!!!");
24 }
25 bVoPause = HI_FALSE;
26 }
27 else if (c == 'p')
28 {
29 if(bVoPause == HI_FALSE)
30 {
31 for (i=0; i<s32ChnNum; i++)
32 {
33 pstVdecSend[i].eThreadCtrl = THREAD_CTRL_PAUSE;
34 }
35
36 for (VoLayer=0; VoLayer<VO_MAX_LAYER_NUM; VoLayer++)
37 {
38 for (VoChn=0; VoChn<VO_MAX_CHN_NUM; VoChn++)
39 {
40 s32Ret = HI_MPI_VO_PauseChn(VoLayer, VoChn);
41 if(HI_SUCCESS != s32Ret)
42 {
43 printf("HI_MPI_VO_PauseChn(%d, %d) fail for 0x%x!\n", VoLayer, VoChn, s32Ret);
44 }
45 }
46 }
47 printf("VO Pause...");
48 }
49 bVoPause = HI_TRUE;
50 }
51 else if (c == 'q')
52 {
53 for (i=0; i<s32ChnNum; i++)
54 {
55 HI_MPI_VDEC_QueryStatus(pstVdecSend[i].s32ChnId, &stStatus);
56 PRINTF_VDEC_CHN_STATUS(pstVdecSend[i].s32ChnId, stStatus);
57 }
58 }

  SAMPLE_COMM_VDEC_CmdCtrl函数正常情况下只可以通过输入字符'c'退出,而字符'r'、'p'、'q'的具体实现这里就不介绍了,总的来说就是循环往VDEC通道发送数据。

1.3、SAMPLE_COMM_VDEC_StopSendStream实现分析

  SAMPLE_COMM_VDEC_StopSendStream实现功能比较简单,主要完成:

  (1)、解码器停止接收用户发送的码流-HI_MPI_VDEC_StopRecvStream(i);

  (2)、发出THREAD_CTRL_STOP指令,退出SAMPLE_COMM_VDEC_SendStream函数(即break),随后用pthread_join()来等待线程结束并获取状态,回收系统资源;

  具体实现如下所示:

 1     for(i=0; i<s32ChnNum; i++)
2 {
3 pstVdecSend[i].eThreadCtrl = THREAD_CTRL_STOP;
4 HI_MPI_VDEC_StopRecvStream(i);
5 if(0 != pVdecThread[i])
6 {
7 pthread_join(pVdecThread[i], HI_NULL);
8 pVdecThread[i] = 0;
9 }
10 }

2、总结

  通过对VDEC_Send_Stream线程进行分析,比较清楚的了解了其内在的实现机理,而在之后的项目,将实现MJPEG->VDEC->VPSS->NNIE->VGS->VO数据通路,将利用其线程机制进行移植。

												

Hi3559AV100外接UVC/MJPEG相机实时采图设计(四):VDEC_Send_Stream线程分析的更多相关文章

  1. Hi3559AV100外接UVC/MJPEG相机实时采图设计(三):V4L2接口通过MPP平台输出

    可以首先参考前面两篇文章: Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析: https://www.cnblogs.com/iFrank/p/1 ...

  2. Hi3559AV100外接UVC/MJPEG相机实时采图设计(二):V4L2接口的实现(以YUV422为例)

    下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...

  3. Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析

    下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...

  4. Dalsa 8K彩色相机Camera link C#采图

    一个采图工具,所以界面做的很简单. private SapAcquisition m_Acquisition; private SapBuffer m_Buffers; private SapAcqT ...

  5. 相机拍的图,电脑上画的图,word里的文字,电脑屏幕,手机屏幕,相机屏幕显示大小一切的一切都搞明白了!

    相机拍的图,电脑上画的图,word里的文字,电脑屏幕,手机屏幕,相机屏幕显示大小一切的一切都搞明白了! 先说图片X×dpi=点数dotX是图片实际尺寸,简单点,我们只算图片的高吧,比如说拍了张图片14 ...

  6. FusionCharts制作实时刷新图

    转自:http://yklovejava-163-com.iteye.com/blog/1889949 下面介绍的是用FusionCharts制作实时刷新图的过程(FusionCharts确实太好用了 ...

  7. Android相机实时自动对焦的完美实现

    https://zhidao.baidu.com/question/873328177698804372.html Android相机实时自动对焦的完美实现 http://blog.csdn.net/ ...

  8. 关于nagios系统下使用shell脚本自定义监控插件的编写以及没有实时监控图的问题

    关于nagios系统下shell自定义监控插件的编写.脚本规范以及没有实时监控图的问题的解决办法 在自已编写监控插件之前我们首先需要对nagios监控原理有一定的了解 Nagios的功能是监控服务和主 ...

  9. 团队项目之UML图设计---WeEdit

    团队信息: 学号: 姓名: 本次博客链接: 041602209 黄毓明(临时队长)  https://www.cnblogs.com/mingsonic/p/9820702.html 06160023 ...

随机推荐

  1. 牛客编程巅峰赛S1第6场 - 黄金&钻石&王者 B.牛牛摆放花 (贪心)

    题意;将一组数重新排序,使得相邻两个数之间的最大差值最小. 题解:贪心,现将所有数sort一下,然后正向遍历,将数分配到新数组的两端,然后再遍历一次维护一个最大值即可. 代码: class Solut ...

  2. streamlink 安装使用

    CentOS 安装: pip install streamlink 使用: #查看视频信息 streamlink $URL #下载视频 streamlink $URL best streamlink ...

  3. CF1459-C. Row GCD

    CF1459-C. Row GCD 题意: 给出两个整数序列\(a.b\),他们的长度分别为\(n,m\).对于数组\(b\)中的每个数字,让你求出\(gcd(a_1+b_j,a_2+b_j,..., ...

  4. Leetcode(5)-最长回文子串(包含动态规划以及Manacher算法)

    给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为1000. 示例 1: 输入: "babad" 输出: "bab" 注意: &quo ...

  5. ++i和i++的区别

    它们两个的数值变化的区别,我这里就不多说了 这里主要说明两者在效率上的区别 (1)首先如果是自带的数据类型,比如int型,++i和i++,编译器的实现方式是相同的,两者并没有效率上的区别,虽然也有副本 ...

  6. Leetcode(104)-二叉树的最大深度

    给定一个二叉树,找出其最大深度. 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数. 说明: 叶子节点是指没有子节点的节点. 示例:给定二叉树 [3,9,20,null,null,15,7], ...

  7. 杭电多校HDU 6656 Kejin Player(概率DP)题解

    题意: 最低等级\(level\ 1\),已知在\(level\ i\)操作一次需花费\(a_i\),有概率\(p_i\)升级到\(level\ i+1\),有\(1 - p_i\)掉级到\(x_i( ...

  8. JxBrowser: 6.6.1 Crack

    JxBrowser: 6.6.1. 1. RELEASE NOTES Download:HomePage JxBrowser is a cross-platform library that prov ...

  9. range()函数的使用、while循环、for-in循环等

    一.range()函数 用于直接生成一个整数序列 创建range对象的三种方式: (1)range(stop)    创建一个(0,stop)之间的整数序列,步长为1 (2)range(start,s ...

  10. Hadoop 3.0 EC技术

    Hadoop 3.0 EC技术 EC的设计目标 Hadoop默认的3副本方案需要额外的200%的存储空间.和网络IO开销 而一些较低I/O的warn和cold数据,副本数据的访问是比较少的(hot数据 ...