目录一、基础驱动的加载二、运行 sample_rtsp.c1、sample_venc_online_wrap_start_sys_vi_vpss2、sample_venc_normal_start_encode3、sample_easyvenc_comm_venc_start_get_stream三、总结一、基础驱动的加载在 Hi3516CV610 SDK 中smp目录主要存放芯片厂商提供的 MPP、驱动、sample 示例程序等源码open_source目录主要存放第三方开源组件。因此像pin 复用、时钟配置、sensor 接口类型、VI/MIPI 接口模式等底层硬件配置一般不会放在open_source中处理而是在smp目录下的interdrv/sysconfig模块中完成。本工程中对应源码路径为smp/a7_linux/source/interdrv/sysconfig/其中sys_cfg.c、pin_mux.c、clk_cfg.c等文件会一起编译生成内核模块sys_config.ko加载基础驱动时脚本smp/a7_linux/source/out/ko/load3516cv610/load3516cv610_20g_debug会先设置 sensor、board、ir_auto 等参数例如SNS_TYPE0sc4336p SNS_TYPE1sc4336p BOARDdmeb_qfn IR_AUTO0随后在insert_ko()中优先加载sys_config.koinsmod sys_config.ko sensorssns0$SNS_TYPE0,sns1$SNS_TYPE1 board$BOARD ir_auto$IR_AUTO这里将 sensor 类型传递给sys_config.ko是为了让sys_config.ko根据 sensor 名称完成对应的底层硬件配置。例如当 sensor 类型为sc4336p时系统会识别它为 MIPI sensor并配置对应的 sensor MCLK、VI/MIPI 接口模式以及相关 pinmux。sys_config.ko完成基础硬件配置后加载脚本会继续加载 MPP 相关内核模块例如ot_osal.ko ot_mmz.ko ot_vb.ko ot_sys.ko ot_vi.ko ot_isp.ko ot_vpss.ko ot_venc.ko ot_mipi_rx.ko需要注意的是sys_config.ko主要负责 sensor 相关的底层时钟、pinmux、复位、电源控制以及 VI/MIPI 接口类型配置。它并不负责真正初始化sc4336p的寄存器。真正的 sensor 初始化会在后续运行 sample 程序时完成。用户态 sample 会根据SENSOR0_TYPE选择对应的 sensor 库例如libsns_sc4336p然后完成 sensor 注册、I2C 配置、寄存器初始化、AE/AWB 回调注册以及 ISP 绑定等工作。二、运行 sample_rtsp.c程序启动后main()里先调用create_rtsp_demo(554)。实际内部会创建一个 RTSP server在554 端口上创建 TCP socket、bind、listen用来等待客户端通过 RTSP 连接访问。随后调用create_rtsp_session(g_rtsplive, /test.264)创建一个 RTSP session。这个 session 对应一个访问路径也就是客户端可以通过rtsp://板端IP/test.264 来拉流。create_rtsp_session()内部会先创建 session然后调用rtsp_set_video()设置该 session 的视频参数。当前代码默认设置为H.264s-vcodec_id codec_id; 用于记录当前 RTSP session 的视频编码格式例如 H.264 或 H.265。 s-vrtpe.pt VRTP_PT_ID; 设置 RTP 包头中的 Payload Type。这里 VRTP_PT_ID 为 96属于动态负载类型常用于 H.264/H.265 这类视频流。 s-vrtpe.seq 0; 初始化 RTP 包序号后续每封装一个 RTP 包都会递增。 s-vrtpe.ssrc 0; 这里只是初始化 RTP encoder 里的 SSRC 字段。真正发送给每个客户端的 SSRC 会在客户端 SETUP 时生成 并在发送 RTP 包前写入包头因此不同客户端可以有自己的 RTP 连接状态。 s-vrtpe.sample_rate 90000; 设置视频 RTP 时间戳频率。视频 RTP 通常使用 90000Hz后续会根据 VENC 输出的 pts 换算 RTP timestamp。 memset(s-vcodec_data, 0, sizeof(s-vcodec_data)); 清空 session 中缓存的编码参数集信息例如 H.264 的 SPS/PPS或者 H.265 的 VPS/SPS/PPS。 如果用户传入了 codec_data则会根据编码格式调用 rtsp_codec_data_parse_from_user_h264() 解析 H.264 的 SPS/PPS rtsp_codec_data_parse_from_user_h265() 解析 H.265 的 VPS/SPS/PPS。 但当前 sample 传入的是 NULL所以这里不会从用户 codec_data 中解析参数集。 后续 rtsp_sever_tx_video() 发送真实码流时会再尝试从帧数据中解析 SPS/PPS。 s-vstreamq streamq_alloc(RTP_MAX_PKTSIZ, VRTP_MAX_NBPKTS * 2 1); 创建视频 RTP 包缓存队列。VENC 输出的 H.264/H.265 NALU 会先被封装成 RTP 包 然后暂存在这个队列中再按照当前已经 PLAY 的客户端连接发送出去。 后面 VENC 获取到编码流后sample_rtsp.c 中调用 rtsp_sever_tx_video() 把每个 H.264 NALU 按 RTP 格式封装并发送给 RTSP 客户端。1、sample_venc_online_wrap_start_sys_vi_vpsssample_venc_online_wrap_start_sys_vi_vpss(sns_type, vi_cfg, vpss_cfg, enc_param)这个函数的主要作用是根据sns_type获取并配置默认的vi_cfg、vpss_cfg和enc_param然后完成 SYS、VI、VPSS 的初始化和启动最后把 VI 输出绑定到 VPSS 输入。函数开始后会先通过sample_venc_online_wrap_get_default_sys_cfg()设置系统工作模式。当前使用的是OT_VI_ONLINE_VPSS_ONLINE模式表示 VI 和 VPSS 采用在线工作方式同时默认打开 VPSS wrap。然后调用sample_comm_vi_get_default_vi_cfg(sns_type, vi_cfg)根据 sensor 类型生成默认的 VI 配置。这个 VI 配置主要包括 sensor 信息、MIPI 信息、VI dev 信息、dev 绑定的 pipe 信息、grp 信息以及 pipe/channel 信息。后续sample_venc_vi_init()会继续调用sample_comm_vi_start_vi()完成 VI 的真正启动。在 VI 启动过程中会遍历创建 VI pipe。这里主要决定哪些 pipe 需要启动 ISP以及 pipe 和 VI channel 之间的输出关系例如把pipe0绑定到VI channel0输出。也就是说VI 部分的核心作用是把 sensor 采集到的图像接入进来并通过 pipe/channel 形成后续可以使用的视频数据流。如果当前配置需要启动 VI wrap则还会根据图像类型、图像分辨率等信息计算一行图像数据所需要的内存大小也就是 wrap 模式下需要使用的行缓存大小。之后函数会通过sample_venc_vpss_get_wrap_cfg()根据 sensor 类型判断是否支持 VPSS wrap。如果当前 sensor 支持 VPSS wrap就会根据 VPSS channel 属性、VI/VPSS 在线模式以及 sensor 的full_lines_std等信息计算 VPSS wrap 所需的buf_line和buf_size。如果当前 sensor 类型不在支持列表中则会关闭 VPSS wrap。vpss_cfg主要用于配置 VPSS 的处理组例如默认使用VPSS grp0。同时还会配置 VPSS channel 的数量和输出方式例如有多少个 VPSS channel 用于裁剪、缩放或者输出不同分辨率的视频流。如果 VPSS 也需要启动 wrap同样会根据图像类型、图像分辨率等信息计算 wrap 缓冲区大小用于 VPSS 在线处理时的行缓存管理。接着调用sample_venc_online_wrap_sys_init()初始化系统 VB 池。VB 池大小会根据enc_param中的小码流输出尺寸以及 VPSS wrap 计算出的buf_size来分配。同时该函数还会设置 VI-VPSS 的工作模式为OT_VI_ONLINE_VPSS_ONLINESYS 初始化完成后函数会依次启动 VI 和 VPSS。最后调用sample_comm_vi_bind_vpss(vi_pipe, 0, vpss_grp, 0)把VI pipe0的VI channel0绑定到VPSS grp0的VPSS channel0。这样sensor 采集到的图像会先进入 VI再由 VI 输出到 VPSS最后由 VPSS 进行缩放、裁剪、格式处理等操作为后续编码做准备。需要注意的是sample_venc_online_wrap_start_sys_vi_vpss()这个函数只负责启动 SYS、VI、VPSS并完成 VI 到 VPSS 的绑定并不会创建 VENC 编码通道。真正的编码通道创建、编码格式设置、帧率设置、GOP 设置、码率控制参数设置以及 VPSS channel 绑定到 VENC channel是后面的sample_venc_normal_start_encode()完成的。2、sample_venc_normal_start_encodesample_venc_normal_start_encode()的主要作用是配置并启动 VENC 编码通道然后把 VPSS channel 绑定到对应的 VENC channel最后启动取流线程。需要注意的是这个函数不会根据摄像头类型生成enc_param。在调用它之前enc_param.enc_size[]已经准备好了。该函数只是根据传入的enc_size、vpss_grp和venc_vpss_chn来配置编码通道。函数开始后先设置 GOP 模式为OT_VENC_GOP_MODE_NORMAL_P并通过sample_comm_venc_get_gop_attr()获取 GOP 属性。然后调用sample_venc_set_video_param()配置两个 VENC 通道VENC chn0H.265 编码用于大码流 VENC chn1H.264 编码用于小码流每个编码通道都会配置帧率、GOP、码率控制模式、编码分辨率、profile 等参数。例如当前代码中帧率是 30fpsGOP 是 60码率控制模式是 CBR。参数配置完成后函数调用sample_comm_venc_mini_buf_en()开启 VENC mini buffer 相关配置。接着启动两个编码通道并完成绑定关系VPSS grp0 chn0 - VENC chn0 VPSS grp0 chn1 - VENC chn1这样VPSS 输出的视频帧就会进入对应的 VENC 通道进行编码。最后调用sample_easyvenc_comm_venc_start_get_stream()启动取流线程从 VENC 通道获取编码后的码流。在当前sample_rtsp.c中取流流程里会把VENC chn1得到的 H.264 小码流通过 RTSP 发送出去。3、sample_easyvenc_comm_venc_start_get_streamsample_easyvenc_comm_venc_start_get_stream()的主要作用是启动 VENC 取流线程。前面已经通过sample_comm_venc_start()创建并启动了 VENC 编码通道所以这里需要再创建一个线程专门从 VENC 通道中取出编码后的码流。该函数会先把需要取流的 VENC 通道号保存到全局参数g_param中然后通过pthread_create()创建取流线程sample_easyvenc_comm_venc_get_venc_stream_proc。这个线程才是真正负责取流的地方。线程启动后会先调用sample_easyvenc_comm_set_name_save_stream()获取每个 VENC 通道的信息例如编码类型、文件后缀、VENC fd、stream buffer 信息等并保存到stream_proc_info中。其中payload_type记录当前通道是 H.264、H.265 还是 JPEG venc_fd用于配合 select() 判断该通道是否有码流可读准备完成后线程进入while循环。每次循环都会把各个 VENC 通道的 fd 加入fd_set然后调用select()等待 VENC 通道产生数据。如果select()检测到某个通道有数据就调用sample_easyvenc_comm_fd_isset()判断具体是哪一个通道可读然后再调用sample_easyvenc_comm_get_stream_from_one_channl()从该通道取出码流。sample_easyvenc_comm_get_stream_from_one_channl()内部会先调用ss_mpi_venc_query_status()查询当前帧有多少个pack然后申请stream.pack内存接着调用ss_mpi_venc_get_stream()获取编码后的码流。取到码流后会调用sample_easyvenc_comm_save_frame_to_file()处理这一帧数据。在原始 sample 中这个函数通常是把码流保存到本地文件如果是 JPEG就会生成 JPEG 文件名并保存图片。但在当前sample_rtsp.c中普通文件保存逻辑被注释掉了改成了 RTSP 发送逻辑。当前代码只处理index 1的通道也就是VENC chn1。这个通道前面被配置成 H.264 小码流。函数会遍历stream-pack[]跳过 H.264 的 SEI 数据然后取出每个 pack 的有效码流地址、长度和 PTSpStremData stream-pack[j].addr stream-pack[j].offset; nSize stream-pack[j].len - stream-pack[j].offset; pts stream-pack[j].pts;然后调用rtsp_sever_tx_video(g_rtsplive, session, pStremData, nSize, stream-pack[j].pts);把 H.264 小码流送给 RTSP 发送函数。rtsp_sever_tx_video()后续会把 H.264 码流解析成 NALU再封装成 RTP 包并发送给所有处于PLAYING状态的 RTSP 客户端。整体流程可以简化理解为启动 VENC 编码通道 ↓ 启动取流线程 ↓ select() 监听 VENC fd ↓ 发现 VENC 通道有码流 ↓ ss_mpi_venc_get_stream() 取出编码流 ↓ 当前 sample_rtsp.c 中把 VENC chn1 的 H.264 小码流通过 RTSP 发出去所以sample_easyvenc_comm_venc_start_get_stream()的核心作用就是启动一个取流线程 不断从 VENC 通道取出编码后的码流 然后根据代码逻辑保存到文件或通过 RTSP 发送出去。三、总结RTSP 出流的主要流程如下第一步先加载 MPP 相关基础驱动./load3516cv610_20g_debug -i。该脚本会加载运行 MPP sample 所需的内核模块例如sys_config.ko、ot_sys.ko、sensor I2C 驱动等。驱动加载完成后用户态 sample 才能调用 VI、VPSS、VENC 等 MPI 接口。第二步运行./sample_rtsp。sample_rtsp启动后会先创建RTSP server 和 RTSP session。RTSP server 监听 554 端口session 对应访问路径/test.264客户端可以通过下面的地址拉流rtsp://板端IP/test.264然后调用sample_venc_online_wrap_start_sys_vi_vpss()完成 SYS、VI、VPSS 的配置和启动。该函数会根据 sensor 类型生成默认 VI 配置启动 MIPI、VI dev、VI pipe、ISP 等模块同时配置 VPSS grp0 和对应的 VPSS channel并根据 sensor 类型判断是否开启 VPSS wrap。最后把VI pipe0的VI channel0绑定到VPSS grp0的VPSS channel0。接着调用sample_venc_normal_start_encode()配置并启动 VENC 编码通道。当前代码中会启动两路编码VPSS chn0 - VENC chn0 - H.265 大码流 VPSS chn1 - VENC chn1 - H.264 小码流该函数会配置编码类型、分辨率、帧率、GOP、码率控制模式等参数然后启动 VENC channel并完成 VPSS channel 到 VENC channel 的绑定。然后调用sample_easyvenc_comm_venc_start_get_stream()启动 VENC 取流线程。取流线程会获取各个 VENC 通道的属性、编码类型、VENC fd 和 stream buffer 信息然后通过select()监听各个 VENC fd。当某个 VENC 通道有编码数据时就调用ss_mpi_venc_get_stream()从 VENC 通道取出编码后的码流。取到码流后会进入sample_easyvenc_comm_save_frame_to_file()。在原始 sample 中这个函数通常用于保存码流文件但在当前sample_rtsp.c中文件保存逻辑被改成了 RTSP 发送逻辑。当前代码只处理index 1的通道也就是VENC chn1的 H.264 小码流。它会遍历stream-pack[]跳过 SEI 数据然后取出有效码流地址、长度和 PTS有效地址 pack.addr pack.offset 有效长度 pack.len - pack.offset 时间戳 pack.pts最后调用rtsp_sever_tx_video(g_rtsplive, session, data, size, pts)。rtsp_sever_tx_video()会把 H.264 码流解析成 NALU封装成 RTP 包并发送给所有处于PLAYING状态的 RTSP 客户端。
Hi3516CV100 RTSP 视频推流实操
目录一、基础驱动的加载二、运行 sample_rtsp.c1、sample_venc_online_wrap_start_sys_vi_vpss2、sample_venc_normal_start_encode3、sample_easyvenc_comm_venc_start_get_stream三、总结一、基础驱动的加载在 Hi3516CV610 SDK 中smp目录主要存放芯片厂商提供的 MPP、驱动、sample 示例程序等源码open_source目录主要存放第三方开源组件。因此像pin 复用、时钟配置、sensor 接口类型、VI/MIPI 接口模式等底层硬件配置一般不会放在open_source中处理而是在smp目录下的interdrv/sysconfig模块中完成。本工程中对应源码路径为smp/a7_linux/source/interdrv/sysconfig/其中sys_cfg.c、pin_mux.c、clk_cfg.c等文件会一起编译生成内核模块sys_config.ko加载基础驱动时脚本smp/a7_linux/source/out/ko/load3516cv610/load3516cv610_20g_debug会先设置 sensor、board、ir_auto 等参数例如SNS_TYPE0sc4336p SNS_TYPE1sc4336p BOARDdmeb_qfn IR_AUTO0随后在insert_ko()中优先加载sys_config.koinsmod sys_config.ko sensorssns0$SNS_TYPE0,sns1$SNS_TYPE1 board$BOARD ir_auto$IR_AUTO这里将 sensor 类型传递给sys_config.ko是为了让sys_config.ko根据 sensor 名称完成对应的底层硬件配置。例如当 sensor 类型为sc4336p时系统会识别它为 MIPI sensor并配置对应的 sensor MCLK、VI/MIPI 接口模式以及相关 pinmux。sys_config.ko完成基础硬件配置后加载脚本会继续加载 MPP 相关内核模块例如ot_osal.ko ot_mmz.ko ot_vb.ko ot_sys.ko ot_vi.ko ot_isp.ko ot_vpss.ko ot_venc.ko ot_mipi_rx.ko需要注意的是sys_config.ko主要负责 sensor 相关的底层时钟、pinmux、复位、电源控制以及 VI/MIPI 接口类型配置。它并不负责真正初始化sc4336p的寄存器。真正的 sensor 初始化会在后续运行 sample 程序时完成。用户态 sample 会根据SENSOR0_TYPE选择对应的 sensor 库例如libsns_sc4336p然后完成 sensor 注册、I2C 配置、寄存器初始化、AE/AWB 回调注册以及 ISP 绑定等工作。二、运行 sample_rtsp.c程序启动后main()里先调用create_rtsp_demo(554)。实际内部会创建一个 RTSP server在554 端口上创建 TCP socket、bind、listen用来等待客户端通过 RTSP 连接访问。随后调用create_rtsp_session(g_rtsplive, /test.264)创建一个 RTSP session。这个 session 对应一个访问路径也就是客户端可以通过rtsp://板端IP/test.264 来拉流。create_rtsp_session()内部会先创建 session然后调用rtsp_set_video()设置该 session 的视频参数。当前代码默认设置为H.264s-vcodec_id codec_id; 用于记录当前 RTSP session 的视频编码格式例如 H.264 或 H.265。 s-vrtpe.pt VRTP_PT_ID; 设置 RTP 包头中的 Payload Type。这里 VRTP_PT_ID 为 96属于动态负载类型常用于 H.264/H.265 这类视频流。 s-vrtpe.seq 0; 初始化 RTP 包序号后续每封装一个 RTP 包都会递增。 s-vrtpe.ssrc 0; 这里只是初始化 RTP encoder 里的 SSRC 字段。真正发送给每个客户端的 SSRC 会在客户端 SETUP 时生成 并在发送 RTP 包前写入包头因此不同客户端可以有自己的 RTP 连接状态。 s-vrtpe.sample_rate 90000; 设置视频 RTP 时间戳频率。视频 RTP 通常使用 90000Hz后续会根据 VENC 输出的 pts 换算 RTP timestamp。 memset(s-vcodec_data, 0, sizeof(s-vcodec_data)); 清空 session 中缓存的编码参数集信息例如 H.264 的 SPS/PPS或者 H.265 的 VPS/SPS/PPS。 如果用户传入了 codec_data则会根据编码格式调用 rtsp_codec_data_parse_from_user_h264() 解析 H.264 的 SPS/PPS rtsp_codec_data_parse_from_user_h265() 解析 H.265 的 VPS/SPS/PPS。 但当前 sample 传入的是 NULL所以这里不会从用户 codec_data 中解析参数集。 后续 rtsp_sever_tx_video() 发送真实码流时会再尝试从帧数据中解析 SPS/PPS。 s-vstreamq streamq_alloc(RTP_MAX_PKTSIZ, VRTP_MAX_NBPKTS * 2 1); 创建视频 RTP 包缓存队列。VENC 输出的 H.264/H.265 NALU 会先被封装成 RTP 包 然后暂存在这个队列中再按照当前已经 PLAY 的客户端连接发送出去。 后面 VENC 获取到编码流后sample_rtsp.c 中调用 rtsp_sever_tx_video() 把每个 H.264 NALU 按 RTP 格式封装并发送给 RTSP 客户端。1、sample_venc_online_wrap_start_sys_vi_vpsssample_venc_online_wrap_start_sys_vi_vpss(sns_type, vi_cfg, vpss_cfg, enc_param)这个函数的主要作用是根据sns_type获取并配置默认的vi_cfg、vpss_cfg和enc_param然后完成 SYS、VI、VPSS 的初始化和启动最后把 VI 输出绑定到 VPSS 输入。函数开始后会先通过sample_venc_online_wrap_get_default_sys_cfg()设置系统工作模式。当前使用的是OT_VI_ONLINE_VPSS_ONLINE模式表示 VI 和 VPSS 采用在线工作方式同时默认打开 VPSS wrap。然后调用sample_comm_vi_get_default_vi_cfg(sns_type, vi_cfg)根据 sensor 类型生成默认的 VI 配置。这个 VI 配置主要包括 sensor 信息、MIPI 信息、VI dev 信息、dev 绑定的 pipe 信息、grp 信息以及 pipe/channel 信息。后续sample_venc_vi_init()会继续调用sample_comm_vi_start_vi()完成 VI 的真正启动。在 VI 启动过程中会遍历创建 VI pipe。这里主要决定哪些 pipe 需要启动 ISP以及 pipe 和 VI channel 之间的输出关系例如把pipe0绑定到VI channel0输出。也就是说VI 部分的核心作用是把 sensor 采集到的图像接入进来并通过 pipe/channel 形成后续可以使用的视频数据流。如果当前配置需要启动 VI wrap则还会根据图像类型、图像分辨率等信息计算一行图像数据所需要的内存大小也就是 wrap 模式下需要使用的行缓存大小。之后函数会通过sample_venc_vpss_get_wrap_cfg()根据 sensor 类型判断是否支持 VPSS wrap。如果当前 sensor 支持 VPSS wrap就会根据 VPSS channel 属性、VI/VPSS 在线模式以及 sensor 的full_lines_std等信息计算 VPSS wrap 所需的buf_line和buf_size。如果当前 sensor 类型不在支持列表中则会关闭 VPSS wrap。vpss_cfg主要用于配置 VPSS 的处理组例如默认使用VPSS grp0。同时还会配置 VPSS channel 的数量和输出方式例如有多少个 VPSS channel 用于裁剪、缩放或者输出不同分辨率的视频流。如果 VPSS 也需要启动 wrap同样会根据图像类型、图像分辨率等信息计算 wrap 缓冲区大小用于 VPSS 在线处理时的行缓存管理。接着调用sample_venc_online_wrap_sys_init()初始化系统 VB 池。VB 池大小会根据enc_param中的小码流输出尺寸以及 VPSS wrap 计算出的buf_size来分配。同时该函数还会设置 VI-VPSS 的工作模式为OT_VI_ONLINE_VPSS_ONLINESYS 初始化完成后函数会依次启动 VI 和 VPSS。最后调用sample_comm_vi_bind_vpss(vi_pipe, 0, vpss_grp, 0)把VI pipe0的VI channel0绑定到VPSS grp0的VPSS channel0。这样sensor 采集到的图像会先进入 VI再由 VI 输出到 VPSS最后由 VPSS 进行缩放、裁剪、格式处理等操作为后续编码做准备。需要注意的是sample_venc_online_wrap_start_sys_vi_vpss()这个函数只负责启动 SYS、VI、VPSS并完成 VI 到 VPSS 的绑定并不会创建 VENC 编码通道。真正的编码通道创建、编码格式设置、帧率设置、GOP 设置、码率控制参数设置以及 VPSS channel 绑定到 VENC channel是后面的sample_venc_normal_start_encode()完成的。2、sample_venc_normal_start_encodesample_venc_normal_start_encode()的主要作用是配置并启动 VENC 编码通道然后把 VPSS channel 绑定到对应的 VENC channel最后启动取流线程。需要注意的是这个函数不会根据摄像头类型生成enc_param。在调用它之前enc_param.enc_size[]已经准备好了。该函数只是根据传入的enc_size、vpss_grp和venc_vpss_chn来配置编码通道。函数开始后先设置 GOP 模式为OT_VENC_GOP_MODE_NORMAL_P并通过sample_comm_venc_get_gop_attr()获取 GOP 属性。然后调用sample_venc_set_video_param()配置两个 VENC 通道VENC chn0H.265 编码用于大码流 VENC chn1H.264 编码用于小码流每个编码通道都会配置帧率、GOP、码率控制模式、编码分辨率、profile 等参数。例如当前代码中帧率是 30fpsGOP 是 60码率控制模式是 CBR。参数配置完成后函数调用sample_comm_venc_mini_buf_en()开启 VENC mini buffer 相关配置。接着启动两个编码通道并完成绑定关系VPSS grp0 chn0 - VENC chn0 VPSS grp0 chn1 - VENC chn1这样VPSS 输出的视频帧就会进入对应的 VENC 通道进行编码。最后调用sample_easyvenc_comm_venc_start_get_stream()启动取流线程从 VENC 通道获取编码后的码流。在当前sample_rtsp.c中取流流程里会把VENC chn1得到的 H.264 小码流通过 RTSP 发送出去。3、sample_easyvenc_comm_venc_start_get_streamsample_easyvenc_comm_venc_start_get_stream()的主要作用是启动 VENC 取流线程。前面已经通过sample_comm_venc_start()创建并启动了 VENC 编码通道所以这里需要再创建一个线程专门从 VENC 通道中取出编码后的码流。该函数会先把需要取流的 VENC 通道号保存到全局参数g_param中然后通过pthread_create()创建取流线程sample_easyvenc_comm_venc_get_venc_stream_proc。这个线程才是真正负责取流的地方。线程启动后会先调用sample_easyvenc_comm_set_name_save_stream()获取每个 VENC 通道的信息例如编码类型、文件后缀、VENC fd、stream buffer 信息等并保存到stream_proc_info中。其中payload_type记录当前通道是 H.264、H.265 还是 JPEG venc_fd用于配合 select() 判断该通道是否有码流可读准备完成后线程进入while循环。每次循环都会把各个 VENC 通道的 fd 加入fd_set然后调用select()等待 VENC 通道产生数据。如果select()检测到某个通道有数据就调用sample_easyvenc_comm_fd_isset()判断具体是哪一个通道可读然后再调用sample_easyvenc_comm_get_stream_from_one_channl()从该通道取出码流。sample_easyvenc_comm_get_stream_from_one_channl()内部会先调用ss_mpi_venc_query_status()查询当前帧有多少个pack然后申请stream.pack内存接着调用ss_mpi_venc_get_stream()获取编码后的码流。取到码流后会调用sample_easyvenc_comm_save_frame_to_file()处理这一帧数据。在原始 sample 中这个函数通常是把码流保存到本地文件如果是 JPEG就会生成 JPEG 文件名并保存图片。但在当前sample_rtsp.c中普通文件保存逻辑被注释掉了改成了 RTSP 发送逻辑。当前代码只处理index 1的通道也就是VENC chn1。这个通道前面被配置成 H.264 小码流。函数会遍历stream-pack[]跳过 H.264 的 SEI 数据然后取出每个 pack 的有效码流地址、长度和 PTSpStremData stream-pack[j].addr stream-pack[j].offset; nSize stream-pack[j].len - stream-pack[j].offset; pts stream-pack[j].pts;然后调用rtsp_sever_tx_video(g_rtsplive, session, pStremData, nSize, stream-pack[j].pts);把 H.264 小码流送给 RTSP 发送函数。rtsp_sever_tx_video()后续会把 H.264 码流解析成 NALU再封装成 RTP 包并发送给所有处于PLAYING状态的 RTSP 客户端。整体流程可以简化理解为启动 VENC 编码通道 ↓ 启动取流线程 ↓ select() 监听 VENC fd ↓ 发现 VENC 通道有码流 ↓ ss_mpi_venc_get_stream() 取出编码流 ↓ 当前 sample_rtsp.c 中把 VENC chn1 的 H.264 小码流通过 RTSP 发出去所以sample_easyvenc_comm_venc_start_get_stream()的核心作用就是启动一个取流线程 不断从 VENC 通道取出编码后的码流 然后根据代码逻辑保存到文件或通过 RTSP 发送出去。三、总结RTSP 出流的主要流程如下第一步先加载 MPP 相关基础驱动./load3516cv610_20g_debug -i。该脚本会加载运行 MPP sample 所需的内核模块例如sys_config.ko、ot_sys.ko、sensor I2C 驱动等。驱动加载完成后用户态 sample 才能调用 VI、VPSS、VENC 等 MPI 接口。第二步运行./sample_rtsp。sample_rtsp启动后会先创建RTSP server 和 RTSP session。RTSP server 监听 554 端口session 对应访问路径/test.264客户端可以通过下面的地址拉流rtsp://板端IP/test.264然后调用sample_venc_online_wrap_start_sys_vi_vpss()完成 SYS、VI、VPSS 的配置和启动。该函数会根据 sensor 类型生成默认 VI 配置启动 MIPI、VI dev、VI pipe、ISP 等模块同时配置 VPSS grp0 和对应的 VPSS channel并根据 sensor 类型判断是否开启 VPSS wrap。最后把VI pipe0的VI channel0绑定到VPSS grp0的VPSS channel0。接着调用sample_venc_normal_start_encode()配置并启动 VENC 编码通道。当前代码中会启动两路编码VPSS chn0 - VENC chn0 - H.265 大码流 VPSS chn1 - VENC chn1 - H.264 小码流该函数会配置编码类型、分辨率、帧率、GOP、码率控制模式等参数然后启动 VENC channel并完成 VPSS channel 到 VENC channel 的绑定。然后调用sample_easyvenc_comm_venc_start_get_stream()启动 VENC 取流线程。取流线程会获取各个 VENC 通道的属性、编码类型、VENC fd 和 stream buffer 信息然后通过select()监听各个 VENC fd。当某个 VENC 通道有编码数据时就调用ss_mpi_venc_get_stream()从 VENC 通道取出编码后的码流。取到码流后会进入sample_easyvenc_comm_save_frame_to_file()。在原始 sample 中这个函数通常用于保存码流文件但在当前sample_rtsp.c中文件保存逻辑被改成了 RTSP 发送逻辑。当前代码只处理index 1的通道也就是VENC chn1的 H.264 小码流。它会遍历stream-pack[]跳过 SEI 数据然后取出有效码流地址、长度和 PTS有效地址 pack.addr pack.offset 有效长度 pack.len - pack.offset 时间戳 pack.pts最后调用rtsp_sever_tx_video(g_rtsplive, session, data, size, pts)。rtsp_sever_tx_video()会把 H.264 码流解析成 NALU封装成 RTP 包并发送给所有处于PLAYING状态的 RTSP 客户端。