发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
打印
[RISC-V MCU 应用开发]

远程共享摄像头

[复制链接]
2488|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 赵玥玥 于 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所示:
1 系统整体框图
首先是摄像头服务器的设计,该部分采用的是QT框架进行设计,首先需要从摄像头中获取帧数据,然后将帧数据RGB888格式转换成JPG压缩格式,减少USB和以太网传输数据的压力。同时将视频帧数据进行拆包发送。
摄像头服务器的工作流程如下图2所示:
2 摄像头服务器工作流程图
摄像头服务器主要代码如下所示:
void USB_CAMERA::cameraImageCaptured(int, QImage image)
{
    QBuffer jpgBuffer;
    QPixmap tempPixmap = QPixmap::fromImage(image);
    unsigned char buf2[512 + 1];
    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[512] = {0};

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

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

    for(int i = 0;i < jpgBuffer.size() / 508;i++)
    {
        buf2[2] = i + 1;
        jpgBuffer.seek(508 * i);
        jpgBuffer.read((char *)&buf2[5], 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[0] = temperature;
                USBHS_EP2_Tx_Buf[1] = 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[512];
                //获取客户端,获取客户端数据,用引用给客户端赋值
                recvLen = socket.ReceiveFrom(recvData, ref clientEnd);

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

                System.Console.WriteLine(recStr);
                if (recvData[1] == 1 || recImageCnt > recvData[0])
                {
                        recImageCnt = 0;
                }
                Array.ConstrainedCopy(recvData, 4, recvImageData, 508 * recImageCnt, 508);
                recImageCnt++;
                if (recvData[1] == recvData[0])
                {
                        if (recImageCnt == recvData[0])
                        {
                                labelTemp.Text = "Temp:" + recvData[2].ToString() + "C";
                                labelHumi.Text = "Humi:" + recvData[3].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/
源码: ch32v307evt-usb-hs-master.zip (199.09 KB)

使用特权

评论回复

打赏榜单

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

评论
21小跑堂 2023-2-27 13:38 回复TA
文章选题方向较好,文章结构合理,流程图清晰详细,实现效果也不错,整体实现较好。 

相关帖子

沙发
micky_xie| | 2023-2-22 16:55 | 只看该作者
你这个是在局域网里发包的吧

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

56

帖子

0

粉丝