4G5G工作原理详解解释图解_5g通信原理-CSDN博客整体视频流转播链路 各模块作用详解结合你硬件环境、软件版本、实际操作流程完整梳理链路、组件功能、配置逻辑全程对应你实操场景硬件拓扑HI3516 摄像机 → IMX6ULL 开发板 → 阿里云 Ubuntu 云服务器 (MediaMTX) → 本地 PC (VLC 播放器)核心协议RTSP RTP UDP Docker GStreamerimx路由表1. 访问摄像机局域网网段路由Destination改为192.168.1.0这个网段的流量都走eth0.route add -net 192.168.1.0 netmask 255.255.255.0 dev eth02. 访问公网/阿里云默认网段10.104.25.1是4G网卡ip是用来上外网的ip。此时Destination为0.0.0.0就是说默认流量都要走eth9.route add default gw 10.104.25.1 dev eth9一、完整数据流走向从采集到播放HI3516(IPC) RTSP流(192.168.1.100:554/test)↓IMX6ULL开发板(GStreamer 拉流转封包UDP推流)↓ UDP 5004端口 RTP-H264裸流阿里云服务器(DockerMediaMTX 接收RTP/转RTSP服务)↓ RTSP 8554端口本地PC(VLC 网络串流播放 rtsp://8.160.122.24:8554/cam)二、逐环节拆解设备、软件、命令、作用、问题复盘环节 1源头设备 —— HI3516 高清摄像头地址与服务内置 RTSP 服务固定流地址rtsp://192.168.1.100:554/test作用负责视频采集、H.264 编码、RTSP 对外输出是整个链路的视频源。对应链路提供原始 RTSP 视频流供下游 IMX6ULL 拉取。环节 2中转设备 —— IMX6ULL 嵌入式开发板核心转发节点板子为百问网嵌入式 Linux无 apt 包管理器、无法安装 gstreamer1.0-rtsp/rtspclientsink因此放弃 RTSP 转发方案改用 GStreamer UDPRTP 转发。执行的最终可用 GStreamer 管道命令gst-launch-1.0 \ rtspsrc locationrtsp://192.168.1.100:554/test latency0 protocolstcp ! \ rtph264depay ! h264parse ! \ rtph264pay config-interval1 pt96 ! \ udpsink host8.160.122.24 port5004 syncfalse详细的GStreamer开发教程_gstreamer教程-CSDN博客逐个元件作用按管道顺序rtspsrc功能GStreamer RTSP 拉流源插件参数说明location指定 HI3516 摄像头 RTSP 地址protocolstcp强制使用 TCP 传输 RTSP避免嵌入式网络丢包latency0关闭缓冲降低播放延迟。作用从 HI3516 拉取完整 RTSP 流剥离 RTSP 信令输出内部 RTP 视频包。rtph264depay功能RTP 解封装作用把 RTSP 自带的 RTP 包头去掉提取出原始 H.264 码流。h264parse功能H.264 码流格式化、分片整理作用修复码流格式、补充帧头保证后续重新封装 RTP 时格式合法。rtph264pay功能RTP 重新封装参数说明pt96RTP 负载类型 96对应 H.264 视频与后端 MediaMTX 的 SDP 配置严格匹配config-interval1每隔 1 帧输出 SPS/PPS 头部保证播放器能正常解码。作用将裸 H.264 码流重新打包为 标准 RTP 数据包为 UDP 传输做准备。udpsink功能UDP 网络发送插件参数说明host8.160.122.24阿里云服务器公网 IPport5004指定 UDP 目标端口syncfalse关闭音视频同步纯视频流优化防止卡顿。作用把封装好的 RTP 包通过 UDP 协议 推送到云服务器 5004 端口。本环节踩坑复盘最初想用 rtspclientsink 直接推 RTSP → 报错 no element rtspclientsink原因嵌入式系统无包管理器无法安装 RTSP 相关插件方案作废。改用 UDPRTP 中转规避插件依赖适配嵌入式板子环境。环节 3云端服务 —— 阿里云 Ubuntu 服务器 Docker MediaMTX服务器角色接收远端 UDP-RTP 流 → 转换为标准 RTSP 服务 → 对外提供播放流。3.1 前置环境Docker 部署Docker 一个超级轻量的 “独立小虚拟机 / 软件盒子”你可以把它理解成一个密封的软件集装箱里面自带运行环境、配置、依赖和你的服务器系统完全隔离点开就能跑不会污染系统不会冲突复制到任何电脑都能一模一样运行用一条命令拿到一个装好MediaMTX的盒子。先修复系统异常dpkg --configure -a解决 apt 被中断问题安装 docker.io、配置国内镜像加速器解决 Docker Hub 拉取超时核心作用使用 Docker 容器隔离 MediaMTX 服务简化部署、端口映射、配置挂载。3.2 MediaMTX 流媒体服务核心转协议组件MediaMTX 是轻量 RTSP 流媒体服务器本次作用监听 UDP 5004 端口接收 RTP-H264 流 → 转成 RTSP 流在 8554 端口对外服务。1配置文件 mediamtx.yml 详解yaml logLevel: info logDestinations: [stdout] rtspAddress: :8554 paths: cam: source: udprtp://0.0.0.0:5004 rtpSDP: | v0 o- 0 0 IN IP4 0.0.0.0 sH264 cIN IP4 0.0.0.0 t0 0 mvideo 5004 RTP/AVP 96 artpmap:96 H264/90000 afmtp:96 packetization-mode1rtspAddress: :8554新版 MediaMTX 顶层配置指定 RTSP 服务监听 8554 端口旧版 rtsp:{port} 写法会解析报错。paths.cam定义流路径 cam对应播放地址后缀 /cam。source: udprtp://0.0.0.0:5004监听本机所有网卡的 UDP 5004 端口接收 RTP 流。rtpSDPSDP 会话描述告诉 MediaMTX 流格式H.264、RTP 负载 96、采样率 90000和开发板 rtph264pay 参数一一对应是解码成功的关键。2容器启动命令 端口映射docker run -d \ --name mediamtx \ --restartalways \ -p 8554:8554/tcp \ -p 5004:5004/udp \ -v /root/mediamtx.yml:/mediamtx.yml \ bluenviron/mediamtx:latest-p 8554:8554/tcp容器 RTSP 端口映射到宿主机 TCP 8554供 VLC 拉流-p 5004:5004/udp容器 UDP 端口映射到宿主机 UDP 5004接收开发板推流-v挂载本地配置文件到容器内永久生效。本环节踩坑复盘初始写 rtp://0.0.0.0:5004 → 报错 invalid source原因MediaMTX 不支持纯 rtp://必须使用 udprtp://。旧版写法 rtsp:{port:8554} → 报错 cannot unmarshal object into Go value of type bool原因v1.12 新版配置语法变更必须使用顶层 rtspAddress。安全组 / 防火墙手动放行 TCP 8554、UDP 5004保证公网 内网端口互通。环节 4播放端 —— 本地 PC VLC 播放器播放地址rtsp://8.160.122.24:8554/cam作用作为 RTSP 客户端主动连接云服务器 MediaMTX 的 8554 端口接收 RTSP 流、解析 RTP 包、解码 H.264 视频完成画面播放。链路终点整套采集 - 转发 - 转协议 - 播放流程闭环。三、核心协议流转总表四、整套方案设计思路适配你硬件限制受限于 IMX6ULL 嵌入式系统无包管理器放弃端到端 RTSP 直推采用 RTSP 拉流 → RTP 重封装 → UDP 透传 绕开插件缺失问题云端用 MediaMTX 做 UDP-RTP → RTSP 协议转换对外提供标准公网 RTSP 服务Docker 简化服务部署与端口管理配合阿里云安全组实现跨网远程视频监控。Wifi应用层APP 数据网络层IP跨网段寻址公网 / 路由数据链路层MAC局域网寻址、信道管控、组帧、重传、加密物理层PHYQAM/OFDM/ 射频 / 电波把比特变成无线电信号WiFi基本知识总结 --- 通信框架及基础概念说明-CSDN博客WiFi工作模式详解从station到Mesh全面解析无线网络架构-CSDN博客Linux系统将WiFi配置为AP模式 --- hostapd 和 udhcpd的使用说明-CSDN博客80211 wifi帧格式--管理帧、数据帧、控制帧_wifi帧长度-CSDN博客PC与IMX开发板同连一个热点私域透传VLC进度条读秒但是黑屏。WIFI传到云服务器再用PC端的VLC拉流就正常。检查了路由表硬件连接正常无线网卡正常向外传输。可能是以下原因。gst-launch-1.0 \ rtspsrc locationrtsp://192.168.1.100:554/test latency0 protocolstcp ! \ rtph264depay ! h264parse ! \ rtph264pay config-interval1 pt96 ! \ udpsink host8.160.122.24 port5004 syncfalse与4G传输时同样的命令。MPU6050添加mpu6050调试设备树添加mpu6050节点为适配6050修改clock-frequency从 400000 改成 100000 。1.在入 Hi3516CV610_SDK_V1.0.1.0的环境中修改设备树、内核图形工具后出现下面找不到dtb文件的问题。初步解决办法修改 /home/ipc/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/Makefile 添加下面强制编译 hi3516cv610-demb-flash.dtb 。$(MAKE) -C $(BUILD_DIR)/$(KERNEL_VER) ARCH$(ARCH_TYPE) LLVM$(LLVM) LLVM_IAS$(LLVM) $(CROSS) hi3516cv610-demb-flash.dtb;5.10 内核必须先载入 hi3516cv610_defconfig 生成.config才有 dtbs 编译目标裸源码直接 make dtbs 必然报错cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/linux-5.10.y # 配置工具链 export PATH$PATH:/opt/linux/x86-arm/arm-v01c02-linux-musleabi-gcc/bin export CROSSarm-v01c02-linux-musleabi- # 1、清理旧配置 make ARCHarm CROSS_COMPILE$CROSS mrproper # 2、载入海思原厂配置关键没有这步无dtbs目标 make ARCHarm CROSS_COMPILE$CROSS hi3516cv610_defconfig # 3、再编译dtbs make ARCHarm CROSS_COMPILE$CROSS dtbs之后编译内核进入内核目录cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/linux-5.10.y加载配置make ARCHarm CROSS_COMPILEarm-v01c02-linux-musleabi- hi3516cv610_defconfig进入BSP目录执行官方编译命令cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/smp/a7_linux/source/bsp make LIB_TYPEmusl CHIPhi3516cv610 DEBUG1 KERNEL_CFGhi3516cv610_new_defconfig kernel内核镜像在该路径下/home/ipc/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/smp/a7_linux/source/bsp/pub最终找到问题出现的原因必须先 source /etc/profile 刷新 PATH否则系统找不到交叉编译器。执行完之后当前终端任意手动编译 dtbs/zImage都不用再手动 export 工具链。想要新开终端自动带工具链在~/.bashrc末尾添加 source /etc/profile 。/etc/profile、 ~/.profile、~/.bashrc三者区别1. 生效时机完全不同登录系统 → 读 /etc/profile → 再读 ~/.profile打开终端 → 只读 ~/.bashrc2. 适用 shell 不同/etc/profile、~/.profile所有 shell 通用sh、bash、zsh、dash…~/.bashrc只有 bash 会用别的 shell 不认3. 放什么内容环境变量 PATH → 放 ~/.profile别名 alias、函数、提示符 → 放 ~/.bashrc系统全局配置 → 放 /etc/profile2.探查到了地址但是读寄存器数据都显示为0。原因6050默认睡眠模式在下面修改寄存器唤醒设备。i2c设备探查i2cdetect -y -r 2-r 强制使用普通 I2C 读时序兼容海思i2c设备身份确认寄存器读取i2cget -y 2 0x68 0x75 //向 I2C 总线 2 号地址 0x68 的设备读取 0x75 寄存器的值i2cset -y 2 0x68 0x6B 0x00 //唤醒MPU6050EIS防抖内容学习一、DMP固件加载并开启I2C控制对应寄存器修改配置。1. 唤醒 MPU6050。I2C_Write(0x6B,0x01); I2C_Write(0x6B,0x01); //Bit7(DEVICE_RESET)0不执行全芯片硬件复位写1才是复位复位硬件自动清0 //Bit6(SLEEP)0退出休眠MPU6050正常上电工作上电默认Bit61休眠 //Bit5(CYCLE)0关闭低功耗间歇采样模式 //Bit4保留位固定0 //Bit3(TEMP_DIS)0开启片内温度传感器 //Bit2~Bit0(CLKSEL001)选用X轴陀螺PLL时钟DMP专用时钟禁止000内部8M晶振2. 关闭信号复位。0x68(SIGNAL_PATH_RESET)写完 DMP 复位12后需要清空复位标志I2C_Write(0x68,0x00);3. 开启 DMP 模式。DMP 总使能0x6A(USER_CTRL) | (17); //Bit7DMP_ENDMP硬件开关FIFO 开启0x6A | (16); //Bit6FIFO_ENDMP姿态数据输出到FIFO必备4.载入官方DMP固件到片内 SRAMDMP固件寄存器基地址为0x0000片内RAM的全局地址REG 0x6D文档 REGISTER 49表格里 4.19I²C MASTER STATUS 前面→BANK_SEL 固件存储分区选择寄存器BANK 全局地址 8高 8 位REG 0x6EREGISTER50→MEM_START_ADDR 片内 SRAM 起始地址页内偏移全局地址 0xFF低 8 位REG 0x6FREGISTER51→MEM_R/W 固件数据读写寄存器单字节读写 DMP 片内 SRAM写固件 / 读 DMP 配置全靠它固件单字节写入流程循环遍历全部DMP固件数组uint16_t dmp_addr 0x0000; //DMP内存起始基地址 for(uint16_t i0;isizeof(dmp_firmware);i){ uint8_t bank dmp_addr 8; //高8位BANK uint8_t addr dmp_addr 0xFF; //低8位页内偏移 I2C_Write(0x6D,bank); I2C_Write(0x6E,addr); I2C_Write(0x6F,dmp_firmware[i]); dmp_addr; }5. 启动 DMP 解算固件写完后延时 5~10ms 等待 DMP 加载固件读取 0x6F 寄存器校验关键固件字节判断固件是否烧录成功总结1.0x6B退出休眠 配置陀螺 PLL 时钟唤醒 系统时钟2.0x68 Bit21DMP 软复位随后清复位3.循环批量0x6D→0x6E→0x6F从 DMP 基地址 0x0000 写入全部固件4.0x6A Bit71(BIT7开DMP)BIT61(开FIFO)正式启用 DMP5.依次配置0x19 采样分频、0x1A DLPF、0x1B 陀螺量程、0x1C 加速度量程6.0x37配置中断引脚属性 →0x38开启 DMP 数据就绪中断7.可选0x6A Bit51 开启 I2C 主机外接磁力计8.等待 DMP 运行单片机循环读取 FIFO 数据解析姿态角二、读取DMP输出→ 四元数 q0/q1/q2/q3开启配置在前面初始化已实现// 开启 FIFO 开启 DMP必须在初始化里写 I2C_Write(0x6A, 0xC0); // Bit71 DMP_EN Bit61 FIFO_EN // DMP输出到FIFO必须配置 I2C_Write(0x18, 0x00); // 使能DMP_FIFO代码示例// 标准DMP四元数读取从硬件FIFO读取最稳定、不丢帧 void MPU_Read_DMP_Quat(float *q0, float *q1, float *q2, float *q3) { u8 buf[16]; // DMP 一包数据固定 16 字节 u16 fifo_count 0; // FIFO 内剩余字节数 // 1. 读取 FIFO_COUNT 寄存器获取当前有多少字节数据 fifo_count I2C_Read(0x72) 8; // 高8位 fifo_count | I2C_Read(0x73); // 低8位 // 2. 只有数据 16 字节才读取DMP一包就是16字节 if(fifo_count 16) { // 3. 从硬件 FIFO 端口 0x74 读取 16 字节 I2C_ReadBuf(0x74, buf, 16); } else { // 数据不足直接返回防止读乱 return; } // 4. 解析 DMP 输出的四元数官方标准格式Q30 → 除以 2^30 1073741824.0f // 注意必须强转 int32_t否则负数会出错芯片只能保存整数要将芯片中保存的整数转化为实际的小数 *q0 ( (int32_t)(buf[0] 24 | buf[1] 16 | buf[2] 8 | buf[3]) ) / 1073741824.0f; *q1 ( (int32_t)(buf[4] 24 | buf[5] 16 | buf[6] 8 | buf[7]) ) / 1073741824.0f; *q2 ( (int32_t)(buf[8] 24 | buf[9] 16 | buf[10] 8 | buf[11]) ) / 1073741824.0f; *q3 ( (int32_t)(buf[12] 24 | buf[13] 16 | buf[14] 8 | buf[15]) ) / 1073741824.0f; }总结1. 读取 FIFO_COUNT 寄存器获取当前有多少字节数据2. 只有数据 16 字节才读取DMP一包就是16字节3. 从硬件 FIFO 端口 0x74 读取 16 字节4. 解析 DMP 输出的四元数芯片只能保存整数要将芯片中保存的整数转化为实际的小数三、DMP 四元数 → 直接转欧拉角 pitch上下 /yaw左右代码示例void Quat_To_Angle(float q0, float q1, float q2, float q3, float *pitch, float *yaw) { *pitch asin(-2 * q1 * q3 2 * q0 * q2) * 57.3f; *yaw atan2(2 * (q1*q2 q0*q3), q0*q0 q1*q1 - q2*q2 - q3*q3) * 57.3f; }四、得到稳定 pitch /yaw 获取hi3516硬件时间戳海思时间戳接口1.获取pitch/yawvoid Calc_Stab_Offset(float pitch, float yaw) { static float base_pitch, base_yaw; static int cnt 0; if(cnt 30){ // 前1秒校准基准 base_pitch pitch; base_yaw yaw; cnt; if(cnt30){ base_pitch /30; base_yaw /30; } return; } //由四元数计算出的base_*与diff_*会在下一步计算裁剪坐标时使用 float dp pitch - base_pitch; float dy yaw - base_yaw; float max fmax(fabs(dp), fabs(dy)); if(max 8){ enable_stab 1; diff_pitch dp; diff_yaw dy; } else if(max 10){ enable_stab 0; base_pitch pitch; base_yaw yaw; } }2.IMU 数据结构与环形缓冲// IMU 数据帧带时间戳用于视频帧同步 typedef struct { HI_U64 pts; // 海思系统时间戳与视频帧同源 float diff_pitch; // 上下抖动角度 float diff_yaw; // 左右抖动角度 int enable_stab; // 防抖使能标志 } IMU_FRAME_T; // 环形缓冲配置 #define IMU_BUFFER_SIZE 60 static IMU_FRAME_T g_ImuBuffer[IMU_BUFFER_SIZE]; static int g_ImuWriteIndex 0; static pthread_mutex_t g_ImuLock PTHREAD_MUTEX_INITIALIZER;3.获取 PTS 并写入缓冲IMU惯性测量单元即mpu6050 PTS时间戳// 获取海思硬件系统时间戳 HI_U64 imu_pts; HI_MPI_SYS_GetSysStamp(imu_pts); // 加锁写入环形缓冲区 pthread_mutex_lock(g_ImuLock); g_ImuBuffer[g_ImuWriteIndex].pts imu_pts; g_ImuBuffer[g_ImuWriteIndex].diff_pitch diff_pitch; g_ImuBuffer[g_ImuWriteIndex].diff_yaw diff_yaw; g_ImuBuffer[g_ImuWriteIndex].enable_stab enable_stab; // 环形缓冲自增 g_ImuWriteIndex (g_ImuWriteIndex 1) % IMU_BUFFER_SIZE; pthread_mutex_unlock(g_ImuLock);总结1.上电校准基准 base_pitch / base_yaw均值校准。2.计算偏移diff_*3.三段防抖策略8° → 防抖开启8~10° → 保持10° → 重置基准关闭防抖4.获取 PTS 并写入缓冲五、陀螺仪数据与视频帧匹配根据视频帧 PTS 查找匹配的 IMU 数据// 输入视频帧的 PTS // 输出最匹配的 IMU 数据 int Get_Matched_IMU(HI_U64 frame_pts, IMU_FRAME_T *pOutImu) { HI_U64 min_diff 0xFFFFFFFFFFFFFFFF; int best_idx 0; pthread_mutex_lock(g_ImuLock); // 遍历缓冲区寻找时间戳最接近的数据 for (int i 0; i IMU_BUFFER_SIZE; i) { if (g_ImuBuffer[i].pts 0) continue; HI_U64 diff llabs(g_ImuBuffer[i].pts - frame_pts); if (diff min_diff) { min_diff diff; best_idx i; } } *pOutImu g_ImuBuffer[best_idx]; pthread_mutex_unlock(g_ImuLock); return 0; }IMU 高频采集1000Hz每包数据都打上硬件时间戳PTS视频按帧率输出30fps每帧自带u64PTS裁剪前根据帧PTS找到同一时刻的 IMU 抖动数据六、角度 → 画面偏移像素角度转像素 裁剪坐标计算公式镜头向哪边抖画面裁剪起点就像反方向移动人眼看到画面保持不动就是 EIS 电子防抖。代码示例//原始采集大图宽高 #define W 1920 #define H 1080 //FOV (Field of View视场角) 镜头能拍到的左右 / 上下最大广角角度 #define FOV_H 86 #define FOV_V 57 //传感器整张大图例如横向总像素 1920px对应完整 86° 广角1°对应横向像素1920÷86≈22.33像素 float pix_h W / FOV_H; float pix_v H / FOV_V; /* 1.预留8°余量 2.diff_yaw × pix_h 镜头晃动带来的画面实际偏移像素反向防抖逻辑相机向左晃 → diff_yaw 0 -diff_yaw × pix_h变成正数X 坐标变大 → 画面裁剪起点右移抵消左晃 3.y pix_h *8 - diff_yaw * pix_h相机向上抬 → diff_pitch0 → Y 坐标变小画面向上裁 4.diff_yaw为第四步计算出的偏移 */ int x pix_h *8 - diff_yaw * pix_h; int y pix_v *8 - diff_pitch * pix_v; //不能小于0不能超出16°总冗余(左右各8°) x x0 ? 0 : (xpix_h*16 ? pix_h*16 : x); y y0 ? 0 : (ypix_v*16 ? pix_v*16 : y);为什么EIS 防抖要预留 8° 余量pix_h *8/pix_v *8基准起始坐标pix_h*88° 对应的横向总像素 22.33×8≈178.6pxpix_v*88° 对应的纵向总像素 18.95×8≈151.6px原理原始大图比最终输出 1080P 画面四周各多预留 8° 画面无抖动时画面从 X179Y152 位置开始裁剪中间居中输出这是防抖基准原点。总结1、FOV 含义FOV 视场角镜头能够捕获景物的广角范围水平 86°、垂直 57° 对应有效输出画面 1920×1080。含义镜头每旋转 1°成像在 CMOS 上横向偏移 22.33 像素、纵向偏移 18.95 像素建立「陀螺仪角度→图像像素」换算桥梁。2、预留 8° 余量的核心目的EIS 电子防抖必须在有效画面四周预留 8° 冗余画面无抖动裁剪起点xpix_h×8ypix_v×8从大图中间裁切出标准 1080P 画面小幅抖动≤8°依靠挪动裁剪坐标使用四周冗余画面反向补偿抖动抖动8°超出冗余补偿极限对应项目逻辑判定人为大幅度转动、重置基准、关闭防抖。3、裁剪坐标公式x pix_h*8 - diff_yaw*pix_hy pix_v*8 - diff_pitch*pix_v反向补偿核心逻辑镜头向哪一侧抖动VPSS 裁剪起点就反向移动镜头左摆diff_yaw0→-diff_yaw*pix_h为正 → X 变大裁剪起点右移抵消画面左飘镜头右摆diff_yaw0→ X 变小裁剪起点左移抵消画面右飘镜头上抬diff_pitch0→ Y 变小起点上移抵消画面下坠镜头下压diff_pitch0→ Y 变大起点下移抵消画面上飘七、VPSS 动态裁剪1VPSS 初始化裁剪上电执行一次在VPSS 初始化函数中开启裁剪设置默认居中窗口// 从第五部分直接沿用的全局变量 #define IMG_W 1920 #define IMG_H 1080 #define FOV_H 86.0f #define FOV_V 57.0f #define MAX_DEG 8.0f float pix_h IMG_W / FOV_H; float pix_v IMG_H / FOV_V; // 从第五部分防抖计算出来的结果 extern float diff_pitch; extern float diff_yaw; extern int enable_stab;初始化裁剪void VPSS_Init_Crop(ot_vpss_grp vpss_grp) { ot_zoom_attr stZoomAttr; memset(stZoomAttr, 0, sizeof(ot_zoom_attr)); // 1. 开启裁剪 stZoomAttr.enable TD_TRUE; stZoomAttr.mode OT_COORD_ABSOLUTE; // 绝对像素坐标 // 2. 默认居中裁剪无抖动时的基准 stZoomAttr.rect.x pix_h * MAX_DEG; stZoomAttr.rect.y pix_v * MAX_DEG; stZoomAttr.rect.width IMG_W; stZoomAttr.rect.height IMG_H; // 3. 设置到 VPSS 硬件 ss_mpi_vpss_set_grp_zoom_in_window(vpss_grp, stZoomAttr); }2.主线程循环更新裁剪核心防抖直接使用第五部分计算出的 x/y写入 VPSSvoid VPSS_Update_Crop(ot_vpss_grp vpss_grp, HI_U64 frame_pts) { int x, y; int max_x, max_y; IMU_FRAME_T imu; // // ✅ 根据帧 PTS 匹配 IMU 数据 // Get_Matched_IMU(frame_pts, imu); if(imu.enable_stab) { x pix_h * 8 - imu.diff_yaw * pix_h; y pix_v * 8 - imu.diff_pitch * pix_v; } else { x pix_h * 8; y pix_v * 8; } // 边界限幅 max_x pix_h * 16; max_y pix_v * 16; x (x 0) ? 0 : (x max_x ? max_x : x); y (y 0) ? 0 : (y max_y ? max_y : y); // 更新 VPSS 裁剪窗口 ot_zoom_attr stZoomAttr; memset(stZoomAttr, 0, sizeof(ot_zoom_attr)); stZoomAttr.enable TD_TRUE; stZoomAttr.mode OT_COORD_ABSOLUTE; stZoomAttr.rect.x x; stZoomAttr.rect.y y; stZoomAttr.rect.width IMG_W; stZoomAttr.rect.height IMG_H; ss_mpi_vpss_set_grp_zoom_in_window(vpss_grp, stZoomAttr); }总结1.VPSS 裁剪属于 Group 硬件功能通过ss_mpi_vpss_set_grp_zoom_in_window实现。2.x/y 坐标完全来自第五部分 DMP 解算不需要额外计算。3.每帧更新一次裁剪窗口实现实时电子防抖。4.预留 8° 冗余画面抖动≤8° 有效补偿10° 自动重置基准。八、主线程1. 从 VPSS 获取一帧视频图像2. 拿到这一帧的硬件时间戳 u64PTS3. 根据帧 PTS从环形缓冲区找到 时间最匹配的 IMU 抖动数据4. 使用匹配后的 diff_pitch / diff_yaw 计算裁剪坐标 (x, y)5. 调用 VPSS 接口设置动态裁剪窗口6. 送出图像进行编码、推流7. 释放视频帧8. 循环执行
4G、Wifi透传、防抖学习
4G5G工作原理详解解释图解_5g通信原理-CSDN博客整体视频流转播链路 各模块作用详解结合你硬件环境、软件版本、实际操作流程完整梳理链路、组件功能、配置逻辑全程对应你实操场景硬件拓扑HI3516 摄像机 → IMX6ULL 开发板 → 阿里云 Ubuntu 云服务器 (MediaMTX) → 本地 PC (VLC 播放器)核心协议RTSP RTP UDP Docker GStreamerimx路由表1. 访问摄像机局域网网段路由Destination改为192.168.1.0这个网段的流量都走eth0.route add -net 192.168.1.0 netmask 255.255.255.0 dev eth02. 访问公网/阿里云默认网段10.104.25.1是4G网卡ip是用来上外网的ip。此时Destination为0.0.0.0就是说默认流量都要走eth9.route add default gw 10.104.25.1 dev eth9一、完整数据流走向从采集到播放HI3516(IPC) RTSP流(192.168.1.100:554/test)↓IMX6ULL开发板(GStreamer 拉流转封包UDP推流)↓ UDP 5004端口 RTP-H264裸流阿里云服务器(DockerMediaMTX 接收RTP/转RTSP服务)↓ RTSP 8554端口本地PC(VLC 网络串流播放 rtsp://8.160.122.24:8554/cam)二、逐环节拆解设备、软件、命令、作用、问题复盘环节 1源头设备 —— HI3516 高清摄像头地址与服务内置 RTSP 服务固定流地址rtsp://192.168.1.100:554/test作用负责视频采集、H.264 编码、RTSP 对外输出是整个链路的视频源。对应链路提供原始 RTSP 视频流供下游 IMX6ULL 拉取。环节 2中转设备 —— IMX6ULL 嵌入式开发板核心转发节点板子为百问网嵌入式 Linux无 apt 包管理器、无法安装 gstreamer1.0-rtsp/rtspclientsink因此放弃 RTSP 转发方案改用 GStreamer UDPRTP 转发。执行的最终可用 GStreamer 管道命令gst-launch-1.0 \ rtspsrc locationrtsp://192.168.1.100:554/test latency0 protocolstcp ! \ rtph264depay ! h264parse ! \ rtph264pay config-interval1 pt96 ! \ udpsink host8.160.122.24 port5004 syncfalse详细的GStreamer开发教程_gstreamer教程-CSDN博客逐个元件作用按管道顺序rtspsrc功能GStreamer RTSP 拉流源插件参数说明location指定 HI3516 摄像头 RTSP 地址protocolstcp强制使用 TCP 传输 RTSP避免嵌入式网络丢包latency0关闭缓冲降低播放延迟。作用从 HI3516 拉取完整 RTSP 流剥离 RTSP 信令输出内部 RTP 视频包。rtph264depay功能RTP 解封装作用把 RTSP 自带的 RTP 包头去掉提取出原始 H.264 码流。h264parse功能H.264 码流格式化、分片整理作用修复码流格式、补充帧头保证后续重新封装 RTP 时格式合法。rtph264pay功能RTP 重新封装参数说明pt96RTP 负载类型 96对应 H.264 视频与后端 MediaMTX 的 SDP 配置严格匹配config-interval1每隔 1 帧输出 SPS/PPS 头部保证播放器能正常解码。作用将裸 H.264 码流重新打包为 标准 RTP 数据包为 UDP 传输做准备。udpsink功能UDP 网络发送插件参数说明host8.160.122.24阿里云服务器公网 IPport5004指定 UDP 目标端口syncfalse关闭音视频同步纯视频流优化防止卡顿。作用把封装好的 RTP 包通过 UDP 协议 推送到云服务器 5004 端口。本环节踩坑复盘最初想用 rtspclientsink 直接推 RTSP → 报错 no element rtspclientsink原因嵌入式系统无包管理器无法安装 RTSP 相关插件方案作废。改用 UDPRTP 中转规避插件依赖适配嵌入式板子环境。环节 3云端服务 —— 阿里云 Ubuntu 服务器 Docker MediaMTX服务器角色接收远端 UDP-RTP 流 → 转换为标准 RTSP 服务 → 对外提供播放流。3.1 前置环境Docker 部署Docker 一个超级轻量的 “独立小虚拟机 / 软件盒子”你可以把它理解成一个密封的软件集装箱里面自带运行环境、配置、依赖和你的服务器系统完全隔离点开就能跑不会污染系统不会冲突复制到任何电脑都能一模一样运行用一条命令拿到一个装好MediaMTX的盒子。先修复系统异常dpkg --configure -a解决 apt 被中断问题安装 docker.io、配置国内镜像加速器解决 Docker Hub 拉取超时核心作用使用 Docker 容器隔离 MediaMTX 服务简化部署、端口映射、配置挂载。3.2 MediaMTX 流媒体服务核心转协议组件MediaMTX 是轻量 RTSP 流媒体服务器本次作用监听 UDP 5004 端口接收 RTP-H264 流 → 转成 RTSP 流在 8554 端口对外服务。1配置文件 mediamtx.yml 详解yaml logLevel: info logDestinations: [stdout] rtspAddress: :8554 paths: cam: source: udprtp://0.0.0.0:5004 rtpSDP: | v0 o- 0 0 IN IP4 0.0.0.0 sH264 cIN IP4 0.0.0.0 t0 0 mvideo 5004 RTP/AVP 96 artpmap:96 H264/90000 afmtp:96 packetization-mode1rtspAddress: :8554新版 MediaMTX 顶层配置指定 RTSP 服务监听 8554 端口旧版 rtsp:{port} 写法会解析报错。paths.cam定义流路径 cam对应播放地址后缀 /cam。source: udprtp://0.0.0.0:5004监听本机所有网卡的 UDP 5004 端口接收 RTP 流。rtpSDPSDP 会话描述告诉 MediaMTX 流格式H.264、RTP 负载 96、采样率 90000和开发板 rtph264pay 参数一一对应是解码成功的关键。2容器启动命令 端口映射docker run -d \ --name mediamtx \ --restartalways \ -p 8554:8554/tcp \ -p 5004:5004/udp \ -v /root/mediamtx.yml:/mediamtx.yml \ bluenviron/mediamtx:latest-p 8554:8554/tcp容器 RTSP 端口映射到宿主机 TCP 8554供 VLC 拉流-p 5004:5004/udp容器 UDP 端口映射到宿主机 UDP 5004接收开发板推流-v挂载本地配置文件到容器内永久生效。本环节踩坑复盘初始写 rtp://0.0.0.0:5004 → 报错 invalid source原因MediaMTX 不支持纯 rtp://必须使用 udprtp://。旧版写法 rtsp:{port:8554} → 报错 cannot unmarshal object into Go value of type bool原因v1.12 新版配置语法变更必须使用顶层 rtspAddress。安全组 / 防火墙手动放行 TCP 8554、UDP 5004保证公网 内网端口互通。环节 4播放端 —— 本地 PC VLC 播放器播放地址rtsp://8.160.122.24:8554/cam作用作为 RTSP 客户端主动连接云服务器 MediaMTX 的 8554 端口接收 RTSP 流、解析 RTP 包、解码 H.264 视频完成画面播放。链路终点整套采集 - 转发 - 转协议 - 播放流程闭环。三、核心协议流转总表四、整套方案设计思路适配你硬件限制受限于 IMX6ULL 嵌入式系统无包管理器放弃端到端 RTSP 直推采用 RTSP 拉流 → RTP 重封装 → UDP 透传 绕开插件缺失问题云端用 MediaMTX 做 UDP-RTP → RTSP 协议转换对外提供标准公网 RTSP 服务Docker 简化服务部署与端口管理配合阿里云安全组实现跨网远程视频监控。Wifi应用层APP 数据网络层IP跨网段寻址公网 / 路由数据链路层MAC局域网寻址、信道管控、组帧、重传、加密物理层PHYQAM/OFDM/ 射频 / 电波把比特变成无线电信号WiFi基本知识总结 --- 通信框架及基础概念说明-CSDN博客WiFi工作模式详解从station到Mesh全面解析无线网络架构-CSDN博客Linux系统将WiFi配置为AP模式 --- hostapd 和 udhcpd的使用说明-CSDN博客80211 wifi帧格式--管理帧、数据帧、控制帧_wifi帧长度-CSDN博客PC与IMX开发板同连一个热点私域透传VLC进度条读秒但是黑屏。WIFI传到云服务器再用PC端的VLC拉流就正常。检查了路由表硬件连接正常无线网卡正常向外传输。可能是以下原因。gst-launch-1.0 \ rtspsrc locationrtsp://192.168.1.100:554/test latency0 protocolstcp ! \ rtph264depay ! h264parse ! \ rtph264pay config-interval1 pt96 ! \ udpsink host8.160.122.24 port5004 syncfalse与4G传输时同样的命令。MPU6050添加mpu6050调试设备树添加mpu6050节点为适配6050修改clock-frequency从 400000 改成 100000 。1.在入 Hi3516CV610_SDK_V1.0.1.0的环境中修改设备树、内核图形工具后出现下面找不到dtb文件的问题。初步解决办法修改 /home/ipc/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/Makefile 添加下面强制编译 hi3516cv610-demb-flash.dtb 。$(MAKE) -C $(BUILD_DIR)/$(KERNEL_VER) ARCH$(ARCH_TYPE) LLVM$(LLVM) LLVM_IAS$(LLVM) $(CROSS) hi3516cv610-demb-flash.dtb;5.10 内核必须先载入 hi3516cv610_defconfig 生成.config才有 dtbs 编译目标裸源码直接 make dtbs 必然报错cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/linux-5.10.y # 配置工具链 export PATH$PATH:/opt/linux/x86-arm/arm-v01c02-linux-musleabi-gcc/bin export CROSSarm-v01c02-linux-musleabi- # 1、清理旧配置 make ARCHarm CROSS_COMPILE$CROSS mrproper # 2、载入海思原厂配置关键没有这步无dtbs目标 make ARCHarm CROSS_COMPILE$CROSS hi3516cv610_defconfig # 3、再编译dtbs make ARCHarm CROSS_COMPILE$CROSS dtbs之后编译内核进入内核目录cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/linux-5.10.y加载配置make ARCHarm CROSS_COMPILEarm-v01c02-linux-musleabi- hi3516cv610_defconfig进入BSP目录执行官方编译命令cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/smp/a7_linux/source/bsp make LIB_TYPEmusl CHIPhi3516cv610 DEBUG1 KERNEL_CFGhi3516cv610_new_defconfig kernel内核镜像在该路径下/home/ipc/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/smp/a7_linux/source/bsp/pub最终找到问题出现的原因必须先 source /etc/profile 刷新 PATH否则系统找不到交叉编译器。执行完之后当前终端任意手动编译 dtbs/zImage都不用再手动 export 工具链。想要新开终端自动带工具链在~/.bashrc末尾添加 source /etc/profile 。/etc/profile、 ~/.profile、~/.bashrc三者区别1. 生效时机完全不同登录系统 → 读 /etc/profile → 再读 ~/.profile打开终端 → 只读 ~/.bashrc2. 适用 shell 不同/etc/profile、~/.profile所有 shell 通用sh、bash、zsh、dash…~/.bashrc只有 bash 会用别的 shell 不认3. 放什么内容环境变量 PATH → 放 ~/.profile别名 alias、函数、提示符 → 放 ~/.bashrc系统全局配置 → 放 /etc/profile2.探查到了地址但是读寄存器数据都显示为0。原因6050默认睡眠模式在下面修改寄存器唤醒设备。i2c设备探查i2cdetect -y -r 2-r 强制使用普通 I2C 读时序兼容海思i2c设备身份确认寄存器读取i2cget -y 2 0x68 0x75 //向 I2C 总线 2 号地址 0x68 的设备读取 0x75 寄存器的值i2cset -y 2 0x68 0x6B 0x00 //唤醒MPU6050EIS防抖内容学习一、DMP固件加载并开启I2C控制对应寄存器修改配置。1. 唤醒 MPU6050。I2C_Write(0x6B,0x01); I2C_Write(0x6B,0x01); //Bit7(DEVICE_RESET)0不执行全芯片硬件复位写1才是复位复位硬件自动清0 //Bit6(SLEEP)0退出休眠MPU6050正常上电工作上电默认Bit61休眠 //Bit5(CYCLE)0关闭低功耗间歇采样模式 //Bit4保留位固定0 //Bit3(TEMP_DIS)0开启片内温度传感器 //Bit2~Bit0(CLKSEL001)选用X轴陀螺PLL时钟DMP专用时钟禁止000内部8M晶振2. 关闭信号复位。0x68(SIGNAL_PATH_RESET)写完 DMP 复位12后需要清空复位标志I2C_Write(0x68,0x00);3. 开启 DMP 模式。DMP 总使能0x6A(USER_CTRL) | (17); //Bit7DMP_ENDMP硬件开关FIFO 开启0x6A | (16); //Bit6FIFO_ENDMP姿态数据输出到FIFO必备4.载入官方DMP固件到片内 SRAMDMP固件寄存器基地址为0x0000片内RAM的全局地址REG 0x6D文档 REGISTER 49表格里 4.19I²C MASTER STATUS 前面→BANK_SEL 固件存储分区选择寄存器BANK 全局地址 8高 8 位REG 0x6EREGISTER50→MEM_START_ADDR 片内 SRAM 起始地址页内偏移全局地址 0xFF低 8 位REG 0x6FREGISTER51→MEM_R/W 固件数据读写寄存器单字节读写 DMP 片内 SRAM写固件 / 读 DMP 配置全靠它固件单字节写入流程循环遍历全部DMP固件数组uint16_t dmp_addr 0x0000; //DMP内存起始基地址 for(uint16_t i0;isizeof(dmp_firmware);i){ uint8_t bank dmp_addr 8; //高8位BANK uint8_t addr dmp_addr 0xFF; //低8位页内偏移 I2C_Write(0x6D,bank); I2C_Write(0x6E,addr); I2C_Write(0x6F,dmp_firmware[i]); dmp_addr; }5. 启动 DMP 解算固件写完后延时 5~10ms 等待 DMP 加载固件读取 0x6F 寄存器校验关键固件字节判断固件是否烧录成功总结1.0x6B退出休眠 配置陀螺 PLL 时钟唤醒 系统时钟2.0x68 Bit21DMP 软复位随后清复位3.循环批量0x6D→0x6E→0x6F从 DMP 基地址 0x0000 写入全部固件4.0x6A Bit71(BIT7开DMP)BIT61(开FIFO)正式启用 DMP5.依次配置0x19 采样分频、0x1A DLPF、0x1B 陀螺量程、0x1C 加速度量程6.0x37配置中断引脚属性 →0x38开启 DMP 数据就绪中断7.可选0x6A Bit51 开启 I2C 主机外接磁力计8.等待 DMP 运行单片机循环读取 FIFO 数据解析姿态角二、读取DMP输出→ 四元数 q0/q1/q2/q3开启配置在前面初始化已实现// 开启 FIFO 开启 DMP必须在初始化里写 I2C_Write(0x6A, 0xC0); // Bit71 DMP_EN Bit61 FIFO_EN // DMP输出到FIFO必须配置 I2C_Write(0x18, 0x00); // 使能DMP_FIFO代码示例// 标准DMP四元数读取从硬件FIFO读取最稳定、不丢帧 void MPU_Read_DMP_Quat(float *q0, float *q1, float *q2, float *q3) { u8 buf[16]; // DMP 一包数据固定 16 字节 u16 fifo_count 0; // FIFO 内剩余字节数 // 1. 读取 FIFO_COUNT 寄存器获取当前有多少字节数据 fifo_count I2C_Read(0x72) 8; // 高8位 fifo_count | I2C_Read(0x73); // 低8位 // 2. 只有数据 16 字节才读取DMP一包就是16字节 if(fifo_count 16) { // 3. 从硬件 FIFO 端口 0x74 读取 16 字节 I2C_ReadBuf(0x74, buf, 16); } else { // 数据不足直接返回防止读乱 return; } // 4. 解析 DMP 输出的四元数官方标准格式Q30 → 除以 2^30 1073741824.0f // 注意必须强转 int32_t否则负数会出错芯片只能保存整数要将芯片中保存的整数转化为实际的小数 *q0 ( (int32_t)(buf[0] 24 | buf[1] 16 | buf[2] 8 | buf[3]) ) / 1073741824.0f; *q1 ( (int32_t)(buf[4] 24 | buf[5] 16 | buf[6] 8 | buf[7]) ) / 1073741824.0f; *q2 ( (int32_t)(buf[8] 24 | buf[9] 16 | buf[10] 8 | buf[11]) ) / 1073741824.0f; *q3 ( (int32_t)(buf[12] 24 | buf[13] 16 | buf[14] 8 | buf[15]) ) / 1073741824.0f; }总结1. 读取 FIFO_COUNT 寄存器获取当前有多少字节数据2. 只有数据 16 字节才读取DMP一包就是16字节3. 从硬件 FIFO 端口 0x74 读取 16 字节4. 解析 DMP 输出的四元数芯片只能保存整数要将芯片中保存的整数转化为实际的小数三、DMP 四元数 → 直接转欧拉角 pitch上下 /yaw左右代码示例void Quat_To_Angle(float q0, float q1, float q2, float q3, float *pitch, float *yaw) { *pitch asin(-2 * q1 * q3 2 * q0 * q2) * 57.3f; *yaw atan2(2 * (q1*q2 q0*q3), q0*q0 q1*q1 - q2*q2 - q3*q3) * 57.3f; }四、得到稳定 pitch /yaw 获取hi3516硬件时间戳海思时间戳接口1.获取pitch/yawvoid Calc_Stab_Offset(float pitch, float yaw) { static float base_pitch, base_yaw; static int cnt 0; if(cnt 30){ // 前1秒校准基准 base_pitch pitch; base_yaw yaw; cnt; if(cnt30){ base_pitch /30; base_yaw /30; } return; } //由四元数计算出的base_*与diff_*会在下一步计算裁剪坐标时使用 float dp pitch - base_pitch; float dy yaw - base_yaw; float max fmax(fabs(dp), fabs(dy)); if(max 8){ enable_stab 1; diff_pitch dp; diff_yaw dy; } else if(max 10){ enable_stab 0; base_pitch pitch; base_yaw yaw; } }2.IMU 数据结构与环形缓冲// IMU 数据帧带时间戳用于视频帧同步 typedef struct { HI_U64 pts; // 海思系统时间戳与视频帧同源 float diff_pitch; // 上下抖动角度 float diff_yaw; // 左右抖动角度 int enable_stab; // 防抖使能标志 } IMU_FRAME_T; // 环形缓冲配置 #define IMU_BUFFER_SIZE 60 static IMU_FRAME_T g_ImuBuffer[IMU_BUFFER_SIZE]; static int g_ImuWriteIndex 0; static pthread_mutex_t g_ImuLock PTHREAD_MUTEX_INITIALIZER;3.获取 PTS 并写入缓冲IMU惯性测量单元即mpu6050 PTS时间戳// 获取海思硬件系统时间戳 HI_U64 imu_pts; HI_MPI_SYS_GetSysStamp(imu_pts); // 加锁写入环形缓冲区 pthread_mutex_lock(g_ImuLock); g_ImuBuffer[g_ImuWriteIndex].pts imu_pts; g_ImuBuffer[g_ImuWriteIndex].diff_pitch diff_pitch; g_ImuBuffer[g_ImuWriteIndex].diff_yaw diff_yaw; g_ImuBuffer[g_ImuWriteIndex].enable_stab enable_stab; // 环形缓冲自增 g_ImuWriteIndex (g_ImuWriteIndex 1) % IMU_BUFFER_SIZE; pthread_mutex_unlock(g_ImuLock);总结1.上电校准基准 base_pitch / base_yaw均值校准。2.计算偏移diff_*3.三段防抖策略8° → 防抖开启8~10° → 保持10° → 重置基准关闭防抖4.获取 PTS 并写入缓冲五、陀螺仪数据与视频帧匹配根据视频帧 PTS 查找匹配的 IMU 数据// 输入视频帧的 PTS // 输出最匹配的 IMU 数据 int Get_Matched_IMU(HI_U64 frame_pts, IMU_FRAME_T *pOutImu) { HI_U64 min_diff 0xFFFFFFFFFFFFFFFF; int best_idx 0; pthread_mutex_lock(g_ImuLock); // 遍历缓冲区寻找时间戳最接近的数据 for (int i 0; i IMU_BUFFER_SIZE; i) { if (g_ImuBuffer[i].pts 0) continue; HI_U64 diff llabs(g_ImuBuffer[i].pts - frame_pts); if (diff min_diff) { min_diff diff; best_idx i; } } *pOutImu g_ImuBuffer[best_idx]; pthread_mutex_unlock(g_ImuLock); return 0; }IMU 高频采集1000Hz每包数据都打上硬件时间戳PTS视频按帧率输出30fps每帧自带u64PTS裁剪前根据帧PTS找到同一时刻的 IMU 抖动数据六、角度 → 画面偏移像素角度转像素 裁剪坐标计算公式镜头向哪边抖画面裁剪起点就像反方向移动人眼看到画面保持不动就是 EIS 电子防抖。代码示例//原始采集大图宽高 #define W 1920 #define H 1080 //FOV (Field of View视场角) 镜头能拍到的左右 / 上下最大广角角度 #define FOV_H 86 #define FOV_V 57 //传感器整张大图例如横向总像素 1920px对应完整 86° 广角1°对应横向像素1920÷86≈22.33像素 float pix_h W / FOV_H; float pix_v H / FOV_V; /* 1.预留8°余量 2.diff_yaw × pix_h 镜头晃动带来的画面实际偏移像素反向防抖逻辑相机向左晃 → diff_yaw 0 -diff_yaw × pix_h变成正数X 坐标变大 → 画面裁剪起点右移抵消左晃 3.y pix_h *8 - diff_yaw * pix_h相机向上抬 → diff_pitch0 → Y 坐标变小画面向上裁 4.diff_yaw为第四步计算出的偏移 */ int x pix_h *8 - diff_yaw * pix_h; int y pix_v *8 - diff_pitch * pix_v; //不能小于0不能超出16°总冗余(左右各8°) x x0 ? 0 : (xpix_h*16 ? pix_h*16 : x); y y0 ? 0 : (ypix_v*16 ? pix_v*16 : y);为什么EIS 防抖要预留 8° 余量pix_h *8/pix_v *8基准起始坐标pix_h*88° 对应的横向总像素 22.33×8≈178.6pxpix_v*88° 对应的纵向总像素 18.95×8≈151.6px原理原始大图比最终输出 1080P 画面四周各多预留 8° 画面无抖动时画面从 X179Y152 位置开始裁剪中间居中输出这是防抖基准原点。总结1、FOV 含义FOV 视场角镜头能够捕获景物的广角范围水平 86°、垂直 57° 对应有效输出画面 1920×1080。含义镜头每旋转 1°成像在 CMOS 上横向偏移 22.33 像素、纵向偏移 18.95 像素建立「陀螺仪角度→图像像素」换算桥梁。2、预留 8° 余量的核心目的EIS 电子防抖必须在有效画面四周预留 8° 冗余画面无抖动裁剪起点xpix_h×8ypix_v×8从大图中间裁切出标准 1080P 画面小幅抖动≤8°依靠挪动裁剪坐标使用四周冗余画面反向补偿抖动抖动8°超出冗余补偿极限对应项目逻辑判定人为大幅度转动、重置基准、关闭防抖。3、裁剪坐标公式x pix_h*8 - diff_yaw*pix_hy pix_v*8 - diff_pitch*pix_v反向补偿核心逻辑镜头向哪一侧抖动VPSS 裁剪起点就反向移动镜头左摆diff_yaw0→-diff_yaw*pix_h为正 → X 变大裁剪起点右移抵消画面左飘镜头右摆diff_yaw0→ X 变小裁剪起点左移抵消画面右飘镜头上抬diff_pitch0→ Y 变小起点上移抵消画面下坠镜头下压diff_pitch0→ Y 变大起点下移抵消画面上飘七、VPSS 动态裁剪1VPSS 初始化裁剪上电执行一次在VPSS 初始化函数中开启裁剪设置默认居中窗口// 从第五部分直接沿用的全局变量 #define IMG_W 1920 #define IMG_H 1080 #define FOV_H 86.0f #define FOV_V 57.0f #define MAX_DEG 8.0f float pix_h IMG_W / FOV_H; float pix_v IMG_H / FOV_V; // 从第五部分防抖计算出来的结果 extern float diff_pitch; extern float diff_yaw; extern int enable_stab;初始化裁剪void VPSS_Init_Crop(ot_vpss_grp vpss_grp) { ot_zoom_attr stZoomAttr; memset(stZoomAttr, 0, sizeof(ot_zoom_attr)); // 1. 开启裁剪 stZoomAttr.enable TD_TRUE; stZoomAttr.mode OT_COORD_ABSOLUTE; // 绝对像素坐标 // 2. 默认居中裁剪无抖动时的基准 stZoomAttr.rect.x pix_h * MAX_DEG; stZoomAttr.rect.y pix_v * MAX_DEG; stZoomAttr.rect.width IMG_W; stZoomAttr.rect.height IMG_H; // 3. 设置到 VPSS 硬件 ss_mpi_vpss_set_grp_zoom_in_window(vpss_grp, stZoomAttr); }2.主线程循环更新裁剪核心防抖直接使用第五部分计算出的 x/y写入 VPSSvoid VPSS_Update_Crop(ot_vpss_grp vpss_grp, HI_U64 frame_pts) { int x, y; int max_x, max_y; IMU_FRAME_T imu; // // ✅ 根据帧 PTS 匹配 IMU 数据 // Get_Matched_IMU(frame_pts, imu); if(imu.enable_stab) { x pix_h * 8 - imu.diff_yaw * pix_h; y pix_v * 8 - imu.diff_pitch * pix_v; } else { x pix_h * 8; y pix_v * 8; } // 边界限幅 max_x pix_h * 16; max_y pix_v * 16; x (x 0) ? 0 : (x max_x ? max_x : x); y (y 0) ? 0 : (y max_y ? max_y : y); // 更新 VPSS 裁剪窗口 ot_zoom_attr stZoomAttr; memset(stZoomAttr, 0, sizeof(ot_zoom_attr)); stZoomAttr.enable TD_TRUE; stZoomAttr.mode OT_COORD_ABSOLUTE; stZoomAttr.rect.x x; stZoomAttr.rect.y y; stZoomAttr.rect.width IMG_W; stZoomAttr.rect.height IMG_H; ss_mpi_vpss_set_grp_zoom_in_window(vpss_grp, stZoomAttr); }总结1.VPSS 裁剪属于 Group 硬件功能通过ss_mpi_vpss_set_grp_zoom_in_window实现。2.x/y 坐标完全来自第五部分 DMP 解算不需要额外计算。3.每帧更新一次裁剪窗口实现实时电子防抖。4.预留 8° 冗余画面抖动≤8° 有效补偿10° 自动重置基准。八、主线程1. 从 VPSS 获取一帧视频图像2. 拿到这一帧的硬件时间戳 u64PTS3. 根据帧 PTS从环形缓冲区找到 时间最匹配的 IMU 抖动数据4. 使用匹配后的 diff_pitch / diff_yaw 计算裁剪坐标 (x, y)5. 调用 VPSS 接口设置动态裁剪窗口6. 送出图像进行编码、推流7. 释放视频帧8. 循环执行