远程共享摄像头
本帖最后由 赵玥玥 于 2023-2-26 12:08 编辑#申请原创# @21小跑堂@21小跑堂@21小跑堂
1.项目介绍现在各种屏幕共享的软件都已经开始收费了,而且局域网下的无线共享设备市面上也很少,为了发挥CH32V307的优秀外设USBHS和千兆以太网,特此设计了一款远程共享摄像头项目。
2.设计首先需要对整体框架进行设计,一共分为3个部分。①、摄像头服务器,该部分的功能主要是获取摄像头数据,并通过USB HS往CH32V307发送摄像头数据,同时接收CH32V307发送上来的温湿度数据。②、CH32V307中继器,该部分的功能是通过总线获取DHT11的温湿度数据,并通过USB HS发送给摄像头服务器。同时接收USB HS的摄像头数据+温湿度数据,并通过以太网以UDP协议的方式发送出去。③、摄像头客户端,该部分的功能就是解析从UDP接收下来的数据,并判断接收的是否为一个完整的帧数据,如果是则显示到界面上。该部分可以拥有多台设备。系统整体框图如下图1所示: 图1 系统整体框图首先是摄像头服务器的设计,该部分采用的是QT框架进行设计,首先需要从摄像头中获取帧数据,然后将帧数据RGB888格式转换成JPG压缩格式,减少USB和以太网传输数据的压力。同时将视频帧数据进行拆包发送。摄像头服务器的工作流程如下图2所示:图2 摄像头服务器工作流程图摄像头服务器主要代码如下所示:void USB_CAMERA::cameraImageCaptured(int, QImage image)
{
QBuffer jpgBuffer;
QPixmap tempPixmap = QPixmap::fromImage(image);
unsigned char buf2;
int res;
// 获取当前时间戳
qDebug()<< "start-----" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
jpgBuffer.open(QBuffer::WriteOnly);
ui->labelCamera->setPixmap(tempPixmap);
image.save(&jpgBuffer, "JPG");
qDebug() << "add after:" << jpgBuffer.size();
if(jpgBuffer.size() % 508 != 0)
{
char data1 = {0};
jpgBuffer.write(data1, 508 - (jpgBuffer.size() % 508));
}
jpgBuffer.close();
jpgBuffer.open(QBuffer::ReadOnly);
buf2 = jpgBuffer.size() / 508;
buf2 = tempValue;
buf2 = humiValue;
for(int i = 0;i < jpgBuffer.size() / 508;i++)
{
buf2 = i + 1;
jpgBuffer.seek(508 * i);
jpgBuffer.read((char *)&buf2, 508);
res = hid_write(handle, buf2, 513); //第一个字节发不出去
}
qDebug()<< "end-----" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
ui->labelTemp->setText("Temp:" + QString::number(tempValue, 10) + "C");
ui->labelHumi->setText("Humi:" + QString::number(humiValue, 10) + "%");
}
之后就是CH32V307中继器的设计,该部分设计主要分为3个部分,第一个就是接收USB HS的数据,该部分需要采用DMA方式接收,这样能够给MCU更多的机会处理以太网的数据。将从USB HS接收到的数据存在存在缓存中,之后使用以太网以UDP的协议把数据发送出去。在每间隔一段时间获取DHT11温湿度传感器的数据,将数据使用USB HS发送给上位机,其实这里会有人会好奇为什么不直接把温湿度数据从UDP发送出去?因为设计将温湿度数据夹杂在视频帧数据中,这样可以让通信协议变得更简单。CH32V307中继器的工作流程如下图3所示:图3 CH32V307中继器工作流程图CH32V307中继器主要代码如下所示:void UART2_Rx_Service( void )
{
if ( USBHS_Endp_Busy[ 2 ] == 0x00)
{
/* Upload packet via USB. */
USBHS_EP2_Tx_Buf = temperature;
USBHS_EP2_Tx_Buf = humidity;
USBHSD->UEP2_TX_DMA= (uint32_t)(uint8_t *)USBHS_EP2_Tx_Buf;
USBHSD->UEP2_TX_LEN= 512;
USBHS_Endp_Busy[ 2 ] |= DEF_UEP_BUSY;
USBHSD->UEP2_TX_CTRL = ((USBHSD->UEP2_TX_CTRL) & ~USBHS_UEP_T_RES_MASK) | USBHS_UEP_T_RES_ACK;
UART2_Rx_RemainLen -= 2;
UART2_Rx_Deal_Ptr += 2;
if (UART2_Rx_Deal_Ptr >= DEF_UART2_BUF_SIZE)
{
UART2_Rx_Deal_Ptr = 0x00;
}
}
}
void UART2_Tx_Service(int *dhtTimer)
{
uint16_t pkg_len = 0;
uint8_t *pbuf;
/* Pass-through USB-HID data to uart2 */
if(RingBuffer_Comm.RemainPack)
{
*dhtTimer += 500;
pbuf = &Data_Buffer[(RingBuffer_Comm.DealPtr) * DEF_USBD_HS_PACK_SIZE];
printf("send uart \n");
camera_udp_send_data((u8 *)&Data_Buffer[(RingBuffer_Comm.DealPtr) * DEF_USBD_HS_PACK_SIZE], 512);
NVIC_DisableIRQ(USBHS_IRQn); // Disable USB interrupts
RingBuffer_Comm.RemainPack--;
RingBuffer_Comm.DealPtr++;
if(RingBuffer_Comm.DealPtr == DEF_Ring_Buffer_Max_Blks)
{
RingBuffer_Comm.DealPtr = 0;
}
NVIC_EnableIRQ(USBHS_IRQn); // Enable USB interrupts
}
/* Monitor whether the remaining space is available for further downloads */
if(RingBuffer_Comm.RemainPack < (DEF_Ring_Buffer_Max_Blks - DEF_RING_BUFFER_RESTART))
{
if(RingBuffer_Comm.StopFlag)
{
RingBuffer_Comm.StopFlag = 0;
USBHSD->UEP1_RX_CTRL = (USBHSD->UEP1_RX_CTRL & ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_ACK;
}
}
}
int main(void)
{
Delay_Init();
USART_Printf_Init(115200);
printf( "SystemClk:%d\r\n",SystemCoreClock) ;
printf( "Compatibility HID Running On USBHS Controller\n" );
/* Variables init */
Var_Init();
/* UART2 init */
UART2_Init();
UART2_DMA_Init();
/* USB20 device init */
USBHS_RCC_Init( );
USBHS_Device_Init( ENABLE );
camera_udp_init();
/* Timer init */
TIM2_Init();
printf("start usbh\n");
if(DHT11_Init())
{
printf("DTH11 error\n");
}
else
{
DHT11_Read_Data(&temperature, &humidity);
printf("Temp:%d,Humi:%d\n", temperature, humidity);
DHT11_flag = 1;
printf("DTH11 success\n");
}
while(1)
{
dhtTimer++;
if(dhtTimer >= 500000)
{
dhtTimer = 0;
if(DHT11_Read_Data(&temperature, &humidity) == 0 && DHT11_flag == 1)
{
printf("Temp:%d,Humi:%d\n", temperature, humidity);
UART2_Rx_Service();
}
}
camera_udp_task();
if (USBHS_DevEnumStatus)
{
UART2_Tx_Service(&dhtTimer);
HID_Set_Report_Deal();
}
}
}
最后就是摄像头客户端的设计,该部分主要是接收UDP的数据,熟悉UDP的应该知道UDP是没有握手机制的,所以UDP丢包的情况很常见,那么我们要如何处理丢包的情况呢?首先在每一包数据中都包含视频帧的总包数量,同时包含当前包是视频帧拆包的第几包,只有接收到总包数量的包数时,说明没有丢包,此时就可以将接收到的视频帧显示在软件上。摄像头客户端的工作流程如下图3所示:图4 摄像头客户端工作流程图摄像头客户端主要代码如下所示://服务器接收
private void SocketReceive()
{
//进入接收循环
while (true)
{
//对data清零
recvData = new byte;
//获取客户端,获取客户端数据,用引用给客户端赋值
recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
string recStr;
recStr = "From Server Len: " + recvLen + "\r\n";
System.Console.WriteLine(recStr);
if (recvData == 1 || recImageCnt > recvData)
{
recImageCnt = 0;
}
Array.ConstrainedCopy(recvData, 4, recvImageData, 508 * recImageCnt, 508);
recImageCnt++;
if (recvData == recvData)
{
if (recImageCnt == recvData)
{
labelTemp.Text = "Temp:" + recvData.ToString() + "C";
labelHumi.Text = "Humi:" + recvData.ToString() + "%";
img = ByteToImage(recvImageData);
pictureBoxCamera.Image = img;
}
recImageCnt = 0;
}
}
}
3.总结一开始设计本来准备采用CH32V307的DVP获取摄像头数据的,但是后面发现帧率太低了,会影响到摄像头展示的效果,之后才采用上面的方案。再次我还测试了USB HS和USB FS的区别,USB HS一包最多可以发送1024个字节,而USB FS一包最多发送64个字节,相差16倍,开发这个项目也算体验了一下高速USB的快乐。在有线网络的环境下UDP丢包的情况其实挺低的,大家可以看演示视频,可以发现另一台电脑界面中的摄像头其实也挺流畅的。沁恒不愧是USB起家的,USB通信做的杠杠的!
演示视频:https://www.bilibili.com/video/BV1s54y1w7mf/源码: 你这个是在局域网里发包的吧 棒楼主 dvp接口帧率能到20fps吗?最近正在打算做类似的东西,硬件都买好了,帧率不够可就麻烦了
页:
[1]