Hi3559AV100外接UVC/MJPEG相機實時採圖設計(四):VDEC_Send_Stream執行緒分析

流星斬月發表於2021-03-05

  下面隨筆將對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資料通路,將利用其執行緒機制進行移植。

 

 

 

 

 

 


相關文章