4G

5G工作原理详解(解释&图解)_5g通信原理-CSDN博客

5G特性:

1.毫米波

高频短波,绕射能力差且传播过程中衰减也大。同时高频也意味着频宽越大,也就是速度越快。

2.微基站

由于绕射能力差,需要大量的微基站中转,数量多。

3.波形束缚

对射频信号的相位控制,使波的能量更集中。

4.MIMO技术

多入多出。

5.D2D(Device to Device)

无需基站转发,但是控制消息走基站。

整体视频流转播链路 & 各模块作用详解
结合你硬件环境、软件版本、实际操作流程,完整梳理链路、组件功能、配置逻辑,全程对应你实操场景:
硬件拓扑:HI3516 摄像机 → IMX6ULL 开发板 → 阿里云 Ubuntu 云服务器 (MediaMTX) → 本地 PC (VLC 播放器)
核心协议:RTSP + RTP + UDP + Docker + GStreamer

imx路由表:

1. 访问摄像机(局域网网段路由),Destination改为192.168.1.0,这个网段的流量都走eth0.
route add -net 192.168.1.0 netmask 255.255.255.0 dev eth0
2. 访问公网/阿里云(默认网段)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裸流
阿里云服务器(Docker+MediaMTX 接收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 + UDP+RTP 转发。
执行的最终可用 GStreamer 管道命令
 

gst-launch-1.0 \
rtspsrc location=rtsp://192.168.1.100:554/test latency=0 protocols=tcp ! \
rtph264depay ! h264parse ! \
rtph264pay config-interval=1 pt=96 ! \
udpsink host=8.160.122.24 port=5004 sync=false

详细的GStreamer开发教程_gstreamer教程-CSDN博客
逐个元件作用(按管道顺序):
rtspsrc
功能:GStreamer RTSP 拉流源插件
参数说明:
location:指定 HI3516 摄像头 RTSP 地址;
protocols=tcp:强制使用 TCP 传输 RTSP,避免嵌入式网络丢包;
latency=0:关闭缓冲,降低播放延迟。
作用:从 HI3516 拉取完整 RTSP 流,剥离 RTSP 信令,输出内部 RTP 视频包。
rtph264depay
功能:RTP 解封装
作用:把 RTSP 自带的 RTP 包头去掉,提取出原始 H.264 码流。
h264parse
功能:H.264 码流格式化、分片整理
作用:修复码流格式、补充帧头,保证后续重新封装 RTP 时格式合法。
rtph264pay
功能:RTP 重新封装
参数说明:
pt=96:RTP 负载类型 96,对应 H.264 视频(与后端 MediaMTX 的 SDP 配置严格匹配);
config-interval=1:每隔 1 帧输出 SPS/PPS 头部,保证播放器能正常解码。
作用:将裸 H.264 码流重新打包为 标准 RTP 数据包,为 UDP 传输做准备。
udpsink
功能:UDP 网络发送插件
参数说明:
host=8.160.122.24:阿里云服务器公网 IP;
port=5004:指定 UDP 目标端口;
sync=false:关闭音视频同步,纯视频流优化,防止卡顿。
作用:把封装好的 RTP 包通过 UDP 协议 推送到云服务器 5004 端口。

本环节踩坑复盘:
最初想用 rtspclientsink 直接推 RTSP → 报错 no element "rtspclientsink"
原因:嵌入式系统无包管理器,无法安装 RTSP 相关插件,方案作废。
改用 UDP+RTP 中转,规避插件依赖,适配嵌入式板子环境。
环节 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: udp+rtp://0.0.0.0:5004
    rtpSDP: |
      v=0
      o=- 0 0 IN IP4 0.0.0.0
      s=H264
      c=IN IP4 0.0.0.0
      t=0 0
      m=video 5004 RTP/AVP 96
      a=rtpmap:96 H264/90000
      a=fmtp:96 packetization-mode=1


rtspAddress: :8554:新版 MediaMTX 顶层配置,指定 RTSP 服务监听 8554 端口(旧版 rtsp:{port} 写法会解析报错)。
paths.cam:定义流路径 cam,对应播放地址后缀 /cam。
source: udp+rtp://0.0.0.0:5004:监听本机所有网卡的 UDP 5004 端口,接收 RTP 流。
rtpSDP:SDP 会话描述,告诉 MediaMTX 流格式:H.264、RTP 负载 96、采样率 90000,和开发板 rtph264pay 参数一一对应,是解码成功的关键。
2)容器启动命令 & 端口映射

docker run -d \
  --name mediamtx \
  --restart=always \
  -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://,必须使用 udp+rtp://。
旧版写法 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):局域网寻址、信道管控、组帧、重传、加密
  • 物理层(PHY):QAM/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 location=rtsp://192.168.1.100:554/test latency=0 protocols=tcp ! \
rtph264depay ! h264parse ! \
rtph264pay config-interval=1 pt=96 ! \
udpsink host=8.160.122.24 port=5004 sync=false

与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 CROSS=arm-v01c02-linux-musleabi-

# 1、清理旧配置
make ARCH=arm CROSS_COMPILE=$CROSS mrproper
# 2、载入海思原厂配置(关键!没有这步无dtbs目标)
# 海思bsp目录下的makefiel原厂自动加载的是 hi3516cv610_emmc_debug_defconfig 
make ARCH=arm CROSS_COMPILE=$CROSS hi3516cv610_defconfig
# 3、再编译dtbs
make ARCH=arm CROSS_COMPILE=$CROSS dtbs

之后编译内核,进入内核目录如下

cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/open_source/linux/linux-5.10.y

加载海思原厂内核配置。执行下面命令会用出厂内核配置(hi3516cv610_defconfig)覆盖本地.config

make ARCH=arm CROSS_COMPILE=arm-v01c02-linux-musleabi- hi3516cv610_defconfig

进入BSP目录,执行官方编译命令如下

cd ~/sdk/Hi3516CV610_SDK_V1.0.1.0-BAK/smp/a7_linux/source/bsp

make LIB_TYPE=musl CHIP=hi3516cv610 DEBUG=1 KERNEL_CFG=hi3516cv610_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
打开终端 → 只读 ~/.bashrc
2. 适用 shell 不同
/etc/profile、~/.profile:所有 shell 通用(sh、bash、zsh、dash…)
~/.bashrc:只有 bash 会用,别的 shell 不认
3. 放什么内容
环境变量 PATH → 放 ~/.profile
别名 alias、函数、提示符 → 放 ~/.bashrc
系统全局配置 → 放 /etc/profile

2.探查到了地址但是读寄存器数据都显示为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 //唤醒MPU6050

EIS防抖内容学习:

一、DMP固件加载并开启,I2C控制对应寄存器修改配置。

1. 唤醒 MPU6050。

I2C_Write(0x6B,0x01);
I2C_Write(0x6B,0x01);
//Bit7(DEVICE_RESET)=0:不执行全芯片硬件复位(写1才是复位,复位硬件自动清0)
//Bit6(SLEEP)=0:退出休眠,MPU6050正常上电工作(上电默认Bit6=1休眠)
//Bit5(CYCLE)=0:关闭低功耗间歇采样模式
//Bit4:保留位固定0
//Bit3(TEMP_DIS)=0:开启片内温度传感器
//Bit2~Bit0(CLKSEL=001):选用X轴陀螺PLL时钟,DMP专用时钟(禁止000内部8M晶振)

2. 关闭信号复位。

0x68(SIGNAL_PATH_RESET):写完 DMP 复位1<<2后,需要清空复位标志,I2C_Write(0x68,0x00);
3. 开启 DMP 模式。

  • DMP 总使能:0x6A(USER_CTRL) |= (1<<7); //Bit7=DMP_EN,DMP硬件开关
  • FIFO 开启:0x6A |= (1<<6); //Bit6=FIFO_EN,DMP姿态数据输出到FIFO必备

4.载入官方DMP固件到片内  SRAM 

DMP固件寄存器基地址为0x0000(片内RAM的全局地址)

REG 0x6D(文档 REGISTER 49,表格里 4.19:I²C MASTER STATUS 前面)→BANK_SEL 固件存储分区选择寄存器,BANK = 全局地址 >>8(高 8 位)
REG 0x6E(REGISTER50)→MEM_START_ADDR 片内 SRAM 起始地址,页内偏移:全局地址 & 0xFF(低 8 位)
REG 0x6F(REGISTER51)→MEM_R/W 固件数据读写寄存器,单字节读写 DMP 片内 SRAM,写固件 / 读 DMP 配置全靠它

固件单字节写入流程,循环遍历全部DMP固件数组:

uint16_t dmp_addr = 0x0000; //DMP内存起始基地址
for(uint16_t i=0;i<sizeof(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 Bit2=1 DMP 软复位,随后清复位

3.循环批量:0x6D→0x6E→0x6F,从 DMP 基地址 0x0000 写入全部固件

4.0x6A Bit7=1(BIT7开DMP)+BIT6=1(开FIFO)正式启用 DMP

5.依次配置:0x19 采样分频、0x1A DLPF、0x1B 陀螺量程、0x1C 加速度量程

6.0x37配置中断引脚属性 → 0x38开启 DMP 数据就绪中断

7.(可选)0x6A Bit5=1 开启 I2C 主机,外接磁力计

8.等待 DMP 运行,单片机循环读取 FIFO 数据,解析姿态角

二、读取DMP输出→ 四元数 q0/q1/q2/q3

开启配置(在前面初始化已实现):

// 开启 FIFO + 开启 DMP(必须在初始化里写)
I2C_Write(0x6A, 0xC0);  // Bit7=1 DMP_EN + Bit6=1 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/yaw

void 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(cnt==30){
            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_pitch>0 → 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 = x<0 ? 0 : (x>pix_h*16 ? pix_h*16 : x);
y = y<0 ? 0 : (y>pix_v*16 ? pix_v*16 : y);

为什么EIS 防抖要预留 8° 余量?

pix_h *8 / pix_v *8:基准起始坐标

  • pix_h*88° 对应的横向总像素 = 22.33×8≈178.6px
  • pix_v*88° 对应的纵向总像素 = 18.95×8≈151.6px

原理:

原始大图比最终输出 1080P 画面四周各多预留 8° 画面,无抖动时,画面从 X=179,Y=152 位置开始裁剪(中间居中输出),这是防抖基准原点。

总结:

1、FOV 含义

FOV 视场角:镜头能够捕获景物的广角范围,水平 86°、垂直 57° 对应有效输出画面 1920×1080。含义:镜头每旋转 1°,成像在 CMOS 上横向偏移 22.33 像素、纵向偏移 18.95 像素,建立「陀螺仪角度→图像像素」换算桥梁

2、预留 8° 余量的核心目的

EIS 电子防抖必须在有效画面四周预留 8° 冗余画面

  • 无抖动:裁剪起点x=pix_h×8,y=pix_v×8,从大图中间裁切出标准 1080P 画面;
  • 小幅抖动≤8°:依靠挪动裁剪坐标,使用四周冗余画面反向补偿抖动;
  • 抖动>8°:超出冗余补偿极限,对应项目逻辑:判定人为大幅度转动、重置基准、关闭防抖。

3、裁剪坐标公式:x = pix_h*8 - diff_yaw*pix_h;y = pix_v*8 - diff_pitch*pix_v

反向补偿核心逻辑:镜头向哪一侧抖动,VPSS 裁剪起点就反向移动

镜头左摆diff_yaw<0-diff_yaw*pix_h为正 → X 变大,裁剪起点右移,抵消画面左飘;

镜头右摆diff_yaw>0 → X 变小,裁剪起点左移,抵消画面右飘;

镜头上抬diff_pitch>0 → Y 变小,起点上移,抵消画面下坠;

镜头下压diff_pitch<0 → Y 变大,起点下移,抵消画面上飘;

七、VPSS 动态裁剪

1:VPSS 初始化裁剪(上电执行一次)

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,写入 VPSS

void 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. 拿到这一帧的硬件时间戳 u64PTS

3. 根据帧 PTS,从环形缓冲区找到 时间最匹配的 IMU 抖动数据

4. 使用匹配后的 diff_pitch / diff_yaw 计算裁剪坐标 (x, y)

5. 调用 VPSS 接口,设置动态裁剪窗口

6. 送出图像进行编码、推流

7. 释放视频帧

8. 循环执行

流媒体协议

RTP\RTCP\RTSP\RTMP

实时传输协议(RTP)、实时传输控制协议(RTCP)、实时流协议(RTSP)和实时消息传输协议(RTMP)是多媒体通信领域的核心协议,它们在音视频传输、直播、视频会议等场景中发挥着关键作用。以下将对这些协议的技术原理、应用场景及差异进行详细解析。

image

一、RTP(Real-time Transport Protocol)

1. 基本概念

RTP是一种基于UDP的传输协议,专为实时数据传输设计,由IETF在RFC 3550中定义。其核心功能是提供时间戳、序列号和负载类型标识,确保音视频数据的时序同步和丢包检测。RTP本身不保证服务质量(QoS),但通过RTCP实现监控和反馈。

2. 技术特点

● 时间戳机制:标记数据包的生成时间,解决网络抖动导致的播放不同步问题。

● 序列号:检测丢包和乱序,支持接收端重组数据。

● 负载类型标识:动态适应不同编码格式(如H.264、AAC)。

● 多路复用:通过SSRC(同步源标识符)区分同一会话中的不同流。

3. 应用场景

● 视频会议:如Zoom、WebRTC底层使用RTP传输音视频流。

● IP电话:VoIP系统依赖RTP实现实时语音通信。

● 直播推流:与RTCP配合优化传输质量。

二、RTCP(Real-time Transport Control Protocol)

1. 角色与功能

RTCP是RTP的伴生协议,负责传输控制信息,而非媒体数据。主要功能包括:

● QoS监控:通过发送接收报告(RR)和发送报告(SR),反馈丢包率、延迟等指标。

● 同步协调:同步多媒体的音画同步(如唇音同步)。

● 参与者管理:在多方会话中标识成员状态。

2. 报文类型

● SR(Sender Report):发送端统计信息(如发送字节数、时间戳)。

● RR(Receiver Report):接收端反馈网络状况。

● SDES(Source Description):参与者描述信息(如用户名)。

● BYE:会话终止通知。

3. 实际应用

在直播场景中,RTCP帮助服务器动态调整码率。例如,当接收端反馈高丢包率时,发送端可降低分辨率以适配网络状况。

三、RTSP(Real-time Streaming Protocol)

1. 协议定位

RTSP是一种应用层协议(RFC 2326),用于控制媒体服务器的播放、暂停等操作,类似“网络遥控器”。其特点是:

● 无传输功能:依赖RTP/RTCP或TCP传输数据。

● 状态性协议:通过会话ID管理连接生命周期。

2. 交互流程

1. OPTIONS:查询服务器支持的方法。

2. DESCRIBE:获取媒体描述(如SDP文件)。

3. SETUP:建立传输通道(指定RTP端口)。

4. PLAY/PAUSE/TEARDOWN:控制播放状态。

3. 典型场景

● 安防监控:通过RTSP调取摄像头实时流。

● IPTV:支持点播与直播的交互控制。

四、RTMP(Real-time Messaging Protocol)

1. 协议演进

RTMP由Adobe开发,最初用于Flash播放器与服务器通信。尽管Flash已淘汰,但RTMP因低延迟特性仍广泛用于直播推流。

2. 核心特性

● 基于TCP:确保可靠性,但延迟高于RTP/UDP。

● 分块传输(Chunking):将数据拆分为小块,适应不同带宽。

● 多路复用:在一个连接上传输音视频、元数据和控制命令。

3. 工作流程

●握手阶段:客户端与服务器交换C0-C2数据包。

●连接阶段:建立NetConnection。

●流创建:通过NetStream传输媒体数据。

4. 现代应用

● 直播推流:OBS等工具通过RTMP将流推送到CDN(如腾讯云、阿里云)。

● 兼容性适配:通过转协议(如RTMP转HLS)适配移动端。

五、协议对比与选型建议

image

选型建议:

● 低延迟交互:优先RTP+RTCP(如WebRTC)。

● 直播推流:RTMP仍是主流,但可结合WebRTC优化。

● 点播与控制:RTSP适合需要精细控制的场景(如IPTV)。

六、技术趋势与挑战

1. WebRTC的崛起:逐渐替代RTMP和RTSP,提供端到端加密和更低延迟。

2. QUIC协议整合:Google推动的QUIC可能替代RTP/UDP,提升抗丢包能力。

3. 5G与边缘计算:高带宽环境下,协议优化重点转向减少端到端延迟。

未来,随着实时交互需求增长,这些协议将不断演进,或在新架构中融合(如SRT替代RTMP),但核心思想——平衡实时性与可靠性——仍将持续影响多媒体传输技术发展。

出处:rtp、rtcp、rtsp、rtmp协议详解

ONVIF 协议

Onvif协议:什么是Onvif-CSDN博客

Onvif协议:门外汉理解ONVIF协议-CSDN博客

Onvif协议:理解什么是Web Services_onvif中主函数onvifwebservices函数名是固定的么-CSDN博客

1.ONVIF 是为了统一拿到 URL (Uniform Resource Locator 统一资源定位符)的方法。在开发中通过添加ONVIF客户端代码,自动发现摄像头、自动获取RTSP URL,不用硬编码不同厂商的地址。

2.ONVIF 里,监控设备(IPC)是 Web Service 服务端,一直监听;平台是客户端,主动连接并控制设备,所有的控制都由客户端发起。

3.ONVIF规范能为视频监控带来什么

a) 抽象了功能的接口。统一了对设备的配置以及操作的方式。

b) 控制端关心的不是设备的型号,而是设备所提供的Web Service。

c) 规范了视频系统中Web Service范围之外的行为。

d) ONVIF提供了各个模块的WSDL,拥有效率非常高的开发方式。

4.SOAP协议 = RPC机制 + HTTP传输协议 + XML数据格式

理论基础

  • 全称:Open Network Video Interface Forum,开放网络视频接口论坛制定的安防设备通信标准。
  • 本质:一套基于 SOAP/XML 的 Web 服务协议,定义了设备发现、媒体控制、事件告警等接口。

核心特性

  • 设备发现(WS-Discovery):设备自动广播自己的服务地址,客户端可自动发现局域网内的 IPC。
  • Profile 规范
    • Profile S:核心视频流配置,定义了如何获取 RTSP 流、设置视频参数,是 IPC 与 NVR 对接的基础。
    • Profile T:音频支持、双向对讲;Profile G:存储和回放。
  • 兼容性强:只要设备支持 ONVIF Profile S,理论上所有支持 ONVIF 的 NVR / 平台都能接入。

典型应用

  • 安防 IPC/NVR 跨品牌对接,比如海康摄像头接入大华 NVR,或第三方平台接入不同品牌设备
  • 局域网内视频监控设备的统一管理、配置和流获取

GB28181

GB28181协议总结(含详细报文解析)-CSDN博客

1.SIP(Session Initiation Protocol)是会话构建协议,TCP、UDP是传输层协议,本身不关心传输的是信令还是媒体。

2.信令层:设备向平台 REGISTER(信令) 注册(设备ID、密码),注册成功后定时发送心跳(MESSAGE)保活。平台通过SIP下发指令:点播、回放、报警。

3.媒体层:平台发(INVITE);设备回复 200 OK ;平台回复 ASK ;设备主动向平台IP、Port发送RTP视频流。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐