返回列表 发新帖我要提问本帖赏金: 100.00元(功能说明)

[RISC-V MCU 应用开发] 远程共享摄像头

[复制链接]
4012|6
 楼主| 赵玥玥 发表于 2023-2-18 15:33 | 显示全部楼层 |阅读模式
本帖最后由 赵玥玥 于 2023-2-26 12:08 编辑

#申请原创#      @21小跑堂  @21小跑堂  @21小跑堂
1.项目介绍
现在各种屏幕共享的软件都已经开始收费了,而且局域网下的无线共享设备市面上也很少,为了发挥CH32V307的优秀外设USBHS和千兆以太网,特此设计了一款远程共享摄像头项目。

2.设计
首先需要对整体框架进行设计,一共分为3个部分。
①、摄像头服务器,该部分的功能主要是获取摄像头数据,并通过USB HSCH32V307发送摄像头数据,同时接收CH32V307发送上来的温湿度数据。
②、CH32V307中继器,该部分的功能是通过总线获取DHT11的温湿度数据,并通过USB HS发送给摄像头服务器。同时接收USB HS的摄像头数据+温湿度数据,并通过以太网以UDP协议的方式发送出去。
③、摄像头客户端,该部分的功能就是解析从UDP接收下来的数据,并判断接收的是否为一个完整的帧数据,如果是则显示到界面上。该部分可以拥有多台设备。
系统整体框图如下图1所示:
01.png
1 系统整体框图
首先是摄像头服务器的设计,该部分采用的是QT框架进行设计,首先需要从摄像头中获取帧数据,然后将帧数据RGB888格式转换成JPG压缩格式,减少USB和以太网传输数据的压力。同时将视频帧数据进行拆包发送。
摄像头服务器的工作流程如下图2所示:
02.png
2 摄像头服务器工作流程图
摄像头服务器主要代码如下所示:
  1. void USB_CAMERA::cameraImageCaptured(int, QImage image)
  2. {
  3.     QBuffer jpgBuffer;
  4.     QPixmap tempPixmap = QPixmap::fromImage(image);
  5.     unsigned char buf2[512 + 1];
  6.     int res;
  7.     // 获取当前时间戳
  8.     qDebug()<< "start-----" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");

  9.     jpgBuffer.open(QBuffer::WriteOnly);
  10.     ui->labelCamera->setPixmap(tempPixmap);
  11.     image.save(&jpgBuffer, "JPG");
  12.     qDebug() << "add after:" << jpgBuffer.size();
  13.     if(jpgBuffer.size() % 508 != 0)
  14.     {
  15.         char data1[512] = {0};

  16.         jpgBuffer.write(data1, 508 - (jpgBuffer.size() % 508));
  17.     }
  18.     jpgBuffer.close();

  19.     jpgBuffer.open(QBuffer::ReadOnly);
  20.     buf2[1] = jpgBuffer.size() / 508;
  21.     buf2[3] = tempValue;
  22.     buf2[4] = humiValue;

  23.     for(int i = 0;i < jpgBuffer.size() / 508;i++)
  24.     {
  25.         buf2[2] = i + 1;
  26.         jpgBuffer.seek(508 * i);
  27.         jpgBuffer.read((char *)&buf2[5], 508);
  28.         res = hid_write(handle, buf2, 513); //第一个字节发不出去
  29.     }

  30.     qDebug()<< "end-----" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");

  31.     ui->labelTemp->setText("Temp:" + QString::number(tempValue, 10) + "C");
  32.     ui->labelHumi->setText("Humi:" + QString::number(humiValue, 10) + "%");
  33. }

之后就是CH32V307中继器的设计,该部分设计主要分为3个部分,第一个就是接收USB HS的数据,该部分需要采用DMA方式接收,这样能够给MCU更多的机会处理以太网的数据。将从USB HS接收到的数据存在存在缓存中,之后使用以太网以UDP的协议把数据发送出去。在每间隔一段时间获取DHT11温湿度传感器的数据,将数据使用USB HS发送给上位机,其实这里会有人会好奇为什么不直接把温湿度数据从UDP发送出去?因为设计将温湿度数据夹杂在视频帧数据中,这样可以让通信协议变得更简单。
CH32V307中继器的工作流程如下图3所示:
03.png
3 CH32V307中继器工作流程图
CH32V307中继器主要代码如下所示:
  1. void UART2_Rx_Service( void )
  2. {
  3.         if ( USBHS_Endp_Busy[ 2 ] == 0x00)
  4.         {
  5.                 /* Upload packet via USB. */

  6.                 USBHS_EP2_Tx_Buf[0] = temperature;
  7.                 USBHS_EP2_Tx_Buf[1] = humidity;

  8.                 USBHSD->UEP2_TX_DMA  = (uint32_t)(uint8_t *)USBHS_EP2_Tx_Buf;
  9.                 USBHSD->UEP2_TX_LEN  = 512;
  10.                 USBHS_Endp_Busy[ 2 ] |= DEF_UEP_BUSY;
  11.                 USBHSD->UEP2_TX_CTRL = ((USBHSD->UEP2_TX_CTRL) & ~USBHS_UEP_T_RES_MASK) | USBHS_UEP_T_RES_ACK;
  12.                 UART2_Rx_RemainLen -= 2;
  13.                 UART2_Rx_Deal_Ptr += 2;
  14.                 if (UART2_Rx_Deal_Ptr >= DEF_UART2_BUF_SIZE)
  15.                 {
  16.                         UART2_Rx_Deal_Ptr = 0x00;
  17.                 }
  18.         }
  19. }

  20. void UART2_Tx_Service(int *dhtTimer)
  21. {
  22.     uint16_t pkg_len = 0;
  23.     uint8_t *pbuf;
  24.         /* Pass-through USB-HID data to uart2 */
  25.         if(RingBuffer_Comm.RemainPack)
  26.         {
  27.                 *dhtTimer += 500;
  28.                
  29.                 pbuf = &Data_Buffer[(RingBuffer_Comm.DealPtr) * DEF_USBD_HS_PACK_SIZE];
  30.                 printf("send uart \n");

  31.                 camera_udp_send_data((u8 *)&Data_Buffer[(RingBuffer_Comm.DealPtr) * DEF_USBD_HS_PACK_SIZE], 512);

  32.                 NVIC_DisableIRQ(USBHS_IRQn);                                                  // Disable USB interrupts
  33.                 RingBuffer_Comm.RemainPack--;
  34.                 RingBuffer_Comm.DealPtr++;
  35.                 if(RingBuffer_Comm.DealPtr == DEF_Ring_Buffer_Max_Blks)
  36.                 {
  37.                         RingBuffer_Comm.DealPtr = 0;
  38.                 }
  39.                 NVIC_EnableIRQ(USBHS_IRQn);                                                   // Enable USB interrupts
  40.         }

  41.     /* Monitor whether the remaining space is available for further downloads */
  42.     if(RingBuffer_Comm.RemainPack < (DEF_Ring_Buffer_Max_Blks - DEF_RING_BUFFER_RESTART))
  43.     {
  44.         if(RingBuffer_Comm.StopFlag)
  45.         {
  46.             RingBuffer_Comm.StopFlag = 0;
  47.             USBHSD->UEP1_RX_CTRL = (USBHSD->UEP1_RX_CTRL & ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_ACK;
  48.         }
  49.     }
  50. }

  51. int main(void)
  52. {

  53.         Delay_Init();
  54.         USART_Printf_Init(115200);
  55.         printf( "SystemClk:%d\r\n",SystemCoreClock) ;
  56.         printf( "Compatibility HID Running On USBHS Controller\n" );

  57.          /* Variables init */
  58.     Var_Init();

  59.     /* UART2 init */
  60.     UART2_Init();
  61.     UART2_DMA_Init();

  62.     /* USB20 device init */
  63.     USBHS_RCC_Init( );
  64.     USBHS_Device_Init( ENABLE );

  65.     camera_udp_init();

  66.     /* Timer init */
  67.     TIM2_Init();
  68.     printf("start usbh\n");

  69.     if(DHT11_Init())
  70.     {
  71.             printf("DTH11 error\n");
  72.     }
  73.     else
  74.     {
  75.             DHT11_Read_Data(&temperature, &humidity);
  76.             printf("Temp:%d,Humi:%d\n", temperature, humidity);
  77.             DHT11_flag = 1;
  78.             printf("DTH11 success\n");
  79.     }

  80.     while(1)
  81.     {
  82.             dhtTimer++;
  83.             if(dhtTimer >= 500000)
  84.             {
  85.                     dhtTimer = 0;
  86.                     if(DHT11_Read_Data(&temperature, &humidity) == 0 && DHT11_flag == 1)
  87.                     {
  88.                             printf("Temp:%d,Humi:%d\n", temperature, humidity);
  89.                             UART2_Rx_Service();
  90.                     }
  91.             }
  92.             camera_udp_task();
  93.         if (USBHS_DevEnumStatus)
  94.         {
  95.             UART2_Tx_Service(&dhtTimer);
  96.             HID_Set_Report_Deal();
  97.         }
  98.     }
  99. }

最后就是摄像头客户端的设计,该部分主要是接收UDP的数据,熟悉UDP的应该知道UDP是没有握手机制的,所以UDP丢包的情况很常见,那么我们要如何处理丢包的情况呢?首先在每一包数据中都包含视频帧的总包数量,同时包含当前包是视频帧拆包的第几包,只有接收到总包数量的包数时,说明没有丢包,此时就可以将接收到的视频帧显示在软件上。
摄像头客户端的工作流程如下图3所示:
04.png
4 摄像头客户端工作流程图
摄像头客户端主要代码如下所示:
  1. //服务器接收
  2. private void SocketReceive()
  3. {
  4.         //进入接收循环
  5.         while (true)
  6.         {
  7.                 //对data清零
  8.                 recvData = new byte[512];
  9.                 //获取客户端,获取客户端数据,用引用给客户端赋值
  10.                 recvLen = socket.ReceiveFrom(recvData, ref clientEnd);

  11.                 string recStr;
  12.                 recStr = "From Server Len: " + recvLen + "\r\n";

  13.                 System.Console.WriteLine(recStr);
  14.                 if (recvData[1] == 1 || recImageCnt > recvData[0])
  15.                 {
  16.                         recImageCnt = 0;
  17.                 }
  18.                 Array.ConstrainedCopy(recvData, 4, recvImageData, 508 * recImageCnt, 508);
  19.                 recImageCnt++;
  20.                 if (recvData[1] == recvData[0])
  21.                 {
  22.                         if (recImageCnt == recvData[0])
  23.                         {
  24.                                 labelTemp.Text = "Temp:" + recvData[2].ToString() + "C";
  25.                                 labelHumi.Text = "Humi:" + recvData[3].ToString() + "%";
  26.                                 img = ByteToImage(recvImageData);
  27.                                 pictureBoxCamera.Image = img;
  28.                         }
  29.                         recImageCnt = 0;
  30.                 }
  31.         }
  32. }

3.总结
一开始设计本来准备采用CH32V307的DVP获取摄像头数据的,但是后面发现帧率太低了,会影响到摄像头展示的效果,之后才采用上面的方案。再次我还测试了USB HS和USB FS的区别,USB HS一包最多可以发送1024个字节,而USB FS一包最多发送64个字节,相差16倍,开发这个项目也算体验了一下高速USB的快乐。在有线网络的环境下UDP丢包的情况其实挺低的,大家可以看演示视频,可以发现另一台电脑界面中的摄像头其实也挺流畅的。沁恒不愧是USB起家的,USB通信做的杠杠的!

演示视频:https://www.bilibili.com/video/BV1s54y1w7mf/
源码: ch32v307evt-usb-hs-master.zip (199.09 KB, 下载次数: 10)

打赏榜单

21小跑堂 打赏了 100.00 元 2023-02-27
理由:恭喜通过原创审核!期待您更多的原创作品~

评论

赞  发表于 2024-10-31 13:55
文章选题方向较好,文章结构合理,流程图清晰详细,实现效果也不错,整体实现较好。  发表于 2023-2-27 13:38
micky_xie 发表于 2023-2-22 16:55 | 显示全部楼层
你这个是在局域网里发包的吧
gangong 发表于 2024-10-26 20:35 | 显示全部楼层
棒楼主
mark301600 发表于 2024-10-31 10:28 | 显示全部楼层
dvp接口帧率能到20fps吗?最近正在打算做类似的东西,硬件都买好了,帧率不够可就麻烦了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

63

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部