基于Sipeed M1s Dock制作网络相机
使用M1s Dock制作网络相机,带取景框屏幕预览,将拍摄画面通过wifi传输至电脑合成视频进行存储。
标签
嵌入式系统
2023寒假在家练
网络相机
无书之录
更新2023-03-28
安徽工程大学
360

一.项目需求

  1. 完成相机驱动,定时拍摄图片,并将图片通过网络传到电脑或服务器,实现长时间拍摄。
  2. 通过电脑端编程将图片合成为一个视频。

二.环境配置

   1.M1s 需要在 Linux 环境下进行编译,使用git获取例程仓库与SDK仓库。


例程仓库:

git clone https://gitee.com/Sipeed/M1s_BL808_example.git

SDK仓库:

git clone https://gitee.com/sipeed/M1s_BL808_SDK.git

   2.获取编译工具链并配置编译工具链路径。

      在SDK文件夹下创建编译链文件夹,之后使用git获取编译链。将编译工具重命名为Linux_x86_64。

mkdir -p M1s_BL808_SDK/toolchain
cd M1s_BL808_SDK/toolchain
git clone https://gitee.com/wonderfullook/m1s_toolchain.git
mv m1s_toolchain Linux_x86_64

      获取SDK当前路径。

cd M1s_BL808_SDK
pwd

      将SDK路径导入系统环境,添加语句export BL_SDK_PATH=pwd所得路径。

cd ~
vi ./bashrc
export BL_SDK_PATH=/home/wushu/workspace/m1s_dock/M1s_bl808_SDK

      编译环境结构树如图所示。

Ft1Kf2kwcxCm1TV8UIU5BO2RMgHW


三.程序实现

   官方例程里已给出关于wifi流传摄像头画面的例程,为满足个人需求对部分程序进行修改与功能增加。

   1.E907核心固件修改

      1.1图像流传

         因官方SDK在e907wifi流传程序ip与端口固定死了,存在一些问题(官方已修复),固进行更改。

         文件路径为:M1s_BL808_SDK/components/sipeed/e907/m1s_e907_xram/src/m1s_e907_xram_wifi.c

其中存在   static void upload_stream_task(void *param) 函数,需修改部分如下。

client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(8888);
client_addr.sin_addr.s_addr = inet_addr("10.42.0.1");
memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));

      IP与端口信息已存储至private结构体中,固将程序修改如下。

client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(private.port);
client_addr.sin_addr.s_addr = inet_addr(private.ip);
memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));

      重新编译e907 固件,将编译结构存在于M1s_BL808_example/e907_app/build_out中,将其烧录至板卡即刻。

cd ./M1s_BL808_example/e907_app
./build.sh firmware

      1.2摄像头画面尺寸

         为实现在LCD屏幕中预览摄像头所拍摄画面,对摄像头初始化函数进行修改。被注释部分为未修改前函数。

int bl_cam_mipi_rgb565_init(void)
{
    int ret = 0;
    CAM_CFG_Type camcfg =
    {
        .swMode = CAM_SW_MODE_MANUAL,
        .swIntCnt = 0,
        .pixWidth = CAM_PIX_DATA_BIT_24TO16,
        .dropMode = CAM_DROP_NONE,
        .linePol = CAM_LINE_ACTIVE_POLARITY_HIGH,
        .framePol = CAM_FRAME_ACTIVE_POLARITY_HIGH,
        .camSensorMode = CAM_SENSOR_MODE_V_AND_H,
        .burstType = CAM_BURST_TYPE_INCR64,
        .waitCount = 0x40,
        .memStart = NULL, //need init 
        .memSize = 0,
        // .memSize = SCALER_WIDTH*8*2*2,
        // .frameSize = RGB565_FRAME_SIZE,
        .frameSize = SCALER_WIDTH*SCALER_HEIGHT*2,
    };

    if (rgb565_pic_buf == NULL) {
        // rgb565_pic_buf = pvPortMalloc(RGB565_FRAME_SIZE * FRAME_COUNT);
        rgb565_pic_buf = pvPortMalloc(SCALER_WIDTH*SCALER_HEIGHT*2 * FRAME_COUNT);
        if (NULL == rgb565_pic_buf) {
            printf("malloc rgb565 pic buf fail\r\n");
            ret = -1;
            goto exit;
        }
    }

    DSP2_MISC_Scaler_Cfg_Type scaler_cfg = 
    {
        .inputWidth = MIPI_WIDTH,
        .inputHeight = MIPI_HEIGHT,
        // .outputWidth = RGB565_SCALER_WIDTH,
        // .outputHeight = RGB565_SCALER_HEIGHT,
        .outputWidth = SCALER_WIDTH,
        .outputHeight = SCALER_HEIGHT,
    };

    bl_cam_mipi_csi_init();

    DSP2_MISC_Scaler_Input_Select(DSP2_MISC_SCALER_2_ID, DSP2_MISC_SCALER_DSP2_INPUT);
    DSP2_MISC_Scaler_Init(DSP2_MISC_SCALER_2_ID, &scaler_cfg);
    DSP2_MISC_Scaler_Enable(DSP2_MISC_SCALER_2_ID);

    DSP2_Scaler_Set_Input(DSP2_MISC_SCALER_C, DSP2_MISC_SCALER_DSP2_INPUT);
    DSP2_YUV2RGB_Init(DSP2_YUV2RGB_PARAM_8BIT_BT601);
    DSP2_YUV2RGB_Set_Input(DSP2_YUV2RGB_A, DSP2_YUV2RGB_INPUT_SCALER_C);

    DSP2_MISC_CAM_Input_Select(DSP2_MISC_CAM_5_ID, DSP2_MISC_CAM_YUV2RGB_OUTPUT);
    camcfg.memStart = (uint32_t)(uintptr_t)rgb565_pic_buf;
    camcfg.memSize = SCALER_WIDTH*SCALER_HEIGHT*2 * FRAME_COUNT;
    // camcfg.memSize = RGB565_FRAME_SIZE * FRAME_COUNT;
    CAM_Init(RGB565_CAM_USE_ID, &camcfg);
    CAM_16_Bit_RGB_order(RGB565_CAM_USE_ID, CAM_16_BIT_BGR);
    CAM_Enable(RGB565_CAM_USE_ID);
exit:
    return ret;
}

      此方式实则不妥,初始化摄像头两次,分别使用了

bl_cam_mipi_mjpeg_init();
bl_cam_mipi_rgb565_init();

      初始化mjpeg是为了流传画面,初始化rgb565是为了在lcd中显示。利用使用摄像头后pop的间隔时间,切换使用。mjpeg获取摄像头画面尺寸是rgb565画面尺寸的四倍,故进行修改。


      实际上,最初想在上位机pc端进行画面预览,但在实际使用上预览画面是一张又一张图片显示后关掉,刷新率较低,故放弃使用。

      后想将获取的mjpeg进行解码为rgb,再放到lcd中显示。然后发现获取mjpeg并流传都是e907内核中一线程完成。想要在c906中获取mjpeg数据也受挫,尝试使用一中间变量传递数据,但发现最后解码出来的都有错误。后想多核间通信,查阅发现使用AXI4总线进行多核间通信交换数据,但并未找到详细使用方法。

      最后无奈,因能力不行,使用此笨办法。


   2.c906程序部分

      2.1夜间LED

      最初试想在夜间或黑暗场所点亮板载led进行补光与照亮,初始化LED与按键的GPIO,按下按键后改变LED电平。

/* Flash_led */
#define PIN_LED (8)
#define LED_ON false
#define LED_OFF true
#define PIN_BTN1 (22)

bool led_flag=LED_OFF;

static void Flash_led_init()
{
    GLB_GPIO_Cfg_Type cfg;
    cfg.drive = 0;
    cfg.smtCtrl = 1;
    cfg.gpioFun = GPIO_FUN_GPIO;
    cfg.outputMode = 0;
    cfg.pullType = GPIO_PULL_NONE;

    cfg.gpioPin = PIN_LED;
    cfg.gpioMode = GPIO_MODE_OUTPUT;
    GLB_GPIO_Init(&cfg);

    cfg.gpioPin = PIN_BTN1;
    cfg.gpioMode = GPIO_MODE_INPUT;
    GLB_GPIO_Init(&cfg);

    GLB_GPIO_Write(PIN_LED, led_flag);
}

static void Flash_led()
{
    if(!GLB_GPIO_Read(PIN_BTN1))
    {
        led_flag ^= LED_OFF;
        GLB_GPIO_Write(PIN_LED, led_flag);
        vTaskDelay(500);
        
    }

}

      2.2LCD显示

static void gettolcd_task(void *param)
{
    while(1)
    {
        // socket_sw();
        while (0 != bl_cam_mipi_rgb565_frame_get((uint8_t **)&picture, &len)) 
        {
            vTaskDelay(1);
        }

        BilinearInterpolation_RGB565(picture, 800 ,600, draw_buf, 280, 240);
        for (int i = 0; i < 280 * 240; i++) 
        {
            draw_buf[i] = __builtin_bswap16(draw_buf[i]);
        }
        st7789v_spi_draw_picture_nonblocking(0, 0, 279, 239, draw_buf);
  
        bl_cam_mipi_rgb565_frame_pop();
        vTaskDelay(100);
    }
}

         2.3TCP流传部分

         此为官方例程程序,在c906中只是起到传递wifi ssid、password与TCP客户端IP与端口的功能,实际工作在e907中。

m1s_xram_wifi_init();
m1s_xram_wifi_connect("404Not Found", "404404404");
m1s_xram_wifi_upload_stream("192.168.3.10", 8888);

   3.上位机程序

      上位机使用python语言,构建一个TCP服务端与M1s dock进行tcp连接,在连接成功后,上位机打印客户端ip地址与端口,之后便可接收mpeg数据。

      初次M1s dock会发送接下来图片长度,将其设置为上位机下次接收数据长度并回复任意数据给M1s以应答,让其继续发送。之后每次将发送一帧jpeg数据流与下一帧数据流长度,我们将下一帧数据流长度分离出来并利用。循环往复。

同时,使用opencv将没帧数据流写入形成视频文件,存储至指定路径。

import socket
import cv2
import io
import numpy as np

if __name__ == '__main__':
    fps =10 # 视频帧率
    size = (800, 600)   # 视频分辨率
    # 视频参数
    video = cv2.VideoWriter("G:/Workspace/output.avi", cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size)
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口复用,使程序退出后端口马上释放
    tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    #心跳
    tcp_server.ioctl(socket.SIO_KEEPALIVE_VALS, (1, 60 * 1000, 30 * 1000))
    # 绑定端口
    tcp_server.bind(("", 8888))
    # 设置监听
    tcp_server.listen(128)
    #客户端的ip,端口号
    tcp_client, tcp_client_address = tcp_server.accept()
    print("客户端的ip地址和端口号:", tcp_client_address)
    #超时设置
    socket.setdefaulttimeout(2)
    #初次获取长度帧
    recv_data = tcp_client.recv(4)
    frame_len = int.from_bytes(recv_data, "little")
    send_data = "n".encode()
while True:
    if recv_data:
        tcp_client.send(send_data)
        # 接收帧数据
        recv_data = tcp_client.recv(frame_len+4)

        if (recv_data[0] == 255 and recv_data[1] == 216) and len(recv_data)==(frame_len+4):
            byte_stream = io.BytesIO(recv_data)
            file_bytes = np.asarray(bytearray(byte_stream.read()), dtype=np.uint8)
            img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
            video.write(img)
            print("ok")

        else :
            print("err,repairing..")
            m_len=0
            while len(recv_data)<(frame_len+4):
                m_len=frame_len+4-len(recv_data)
                m_recv_data = tcp_client.recv(m_len)
                recv_data += m_recv_data

        a=recv_data.split(b'\xff\xd9')[1]
        frame_len = int.from_bytes(a, "little")

    else:
        tcp_client.close()
        exit(0)

四.总结

      此任务官方给出了例程,在很大程度上减少了制作难度。摄像头驱动无需自己完成,TCP+socket也是写好的,直接使用就行。在写上位机与板子建立通信时弄了好久,后发现是sdk里程序写死了,有点坑。解决后主要的问题就是jpeg数据流的识别与处理还有将其合成为视频的问题,在众多资料的帮助下逐渐学习与理解,最后解决了相关问题,学习了知识。

      本次项目算是勉强完成任务的要求,实现基本功能,但做的并不好。有很多想要实现的附加功能,但由于个人能力限制与资料较少未能完成。希望之后官方能放出更多资料,可以让我们进一步学习,做出更好的东西。

附件下载
附件.zip
板卡程序与上位机程序
团队介绍
安徽工程大学电子信息工程
团队成员
无书之录
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号