4.RV1126+FFMPEG多路码流监控项目

4.1RV1126+FFMPEG多路码流推流:RV1126+FFMPEG多路码流监控项目概述

  • 项目介绍:

本项目采用的是易百纳RV1126开发板和CMOS摄像头,使用的推流框架是FFMPEG开源项目。这个项目的工作流程如下(如上图):通过采集摄像头的VI模块,再通过硬件编码VENC模块进行H264/H265的编码压缩,并把压缩后的数据通过FFMPEG传输到两个流媒体服务器(如同时推送到流媒体服务器:rtmp://xxx.xxx.xx.xxx:1935/live/01和rtmp://xxx.xxx.xx.xxx:1935/live/02)。这个项目可以学习到:多线程,队列,FFMPEG音视频数据传输等重要知识。

  • 项目框架思维导图

上面是整个项目思维导图可以看出来,这个项目的main函数是整个项目的入口函数。在这里入口函数里面,需要做四个比较重要的步骤:分别是rkmedia组件和功能的初始化初始化高分辨率队列HIGH_VIDEO_QUEUE初始化低分辨率队列LOW_VIDEO_QUEUEinit_rv1126_first_assignment开启RV1126的推流任务。

2.1. init_rkmedia_module_function:

这个函数主要是做RKMEDIA的组件初始化,组件包括:VI模块的初始化、高分辨率VENC模块的初始化、低分辨率VENC模块的初始化、RGA模块初始化。

2.1.1. VI模块初始化:初始化摄像头模块让其摄像头模块能够正常工作,具体的VI模块初始化在rkmedia_vi_init里面。

2.1.2. RGA模块的初始化:RGA主要是对VI模块的数据进行缩放操作,把1920 * 1080的视频数据转换成1280 * 720的视频数据。

2.1.3. VENC模块初始化(分别是高、低分辨率):初始化高、低分辨率VENC硬件编码器,这里的编码器主要针对的是1920 * 1080和1280 * 720两种分辨率,具体的高分辨率VENC模块初始化在rkmedia_venc_init里面。

2.2. 高分辨率队列的初始化HIGH_VIDEO_QUEUE:

初始化搞分辨率编码数据队列,这个队列主要是存储1920 * 1080编码的视频数据

2.3. 低分辨率队列的初始化LOW_VIDEO_QUEUE:

初始化搞分辨率编码数据队列,这个队列主要是存储1280* 720编码的视频数据

2.4. init_rv1126_first_assignment启动RV1126推流任务讲解:

这个函数主要进行多路码流推流的业务实现,这里面包含了:init_rkmedia_ffmpeg_context分别初始化高分辨率的ffmpeg推流器和低分辨率的ffmpeg推流器、创建camera_venc_thread线程、创建get_rga_thread线程、创建low_camera_venc_thread线程、创建high_video_push_thread线程、创建low_video_push_thread线程

2.4.1. init_rkmedia_ffmpeg_context初始化高分辨率和低分辨率的推流器:

在这个函数里面主要是对FFMPEG推流器参数进行设置,它需要对高分辨率(1920 * 1080)和低分辨率(1280 * 720)的FFMPEG推流器进行初始化。

2.4.2. 创建camera_venc_thread线程

camera_venc_thread线程最重要的作用是编码1920 * 1080的编码视频数据流并且入到HIGH_VIDEO_QUEUE队列

2.4.3. 创建get_rga_thread线程

get_rga_thread线程最重要的作用是处理1920 * 1080的摄像头数据,把它的分辨率降低到1280 * 720,并且把1280 * 720的原始码流传输到低分辨率(1280 * 720)的编码器

2.4.4. 创建low_camera_venc_thread线程

low_camera_venc_thread线程最重要的作用是获取分辨率1280 * 720的编码数据,并且入到LOW_VIDEO_QUEU队列

2.4.5. 创建high_video_push_thread线程

high_video_push_thread线程作用是从HIGH_VIDEO_QUEUE队里取出每一帧1920*1080的视频编码数据,然后利用FFMPEG的推流到对应的流媒体服务器

2.4.6. 创建low_video_push_thread线程

low_video_push_thread线程作用是从LOW_VIDEO_QUEUE队里取出每一帧1280*720的视频编码数据,然后利用FFMPEG的推流到对应的流媒体服务器

  • 项目代码源文件截图

上图是整个RV1126多路码流推流项目的代码源文件

4.2RV1126的VI模块

VI视频模块是所有摄像头数据的入口。VI模块的配置在源文件rkmedia_module_function.cpp里面。

  • VI模块的思维导图:

上面思维导图主要是描述VI模块设置的大致流程,首先对RV1126_VI_CONFIG结构体进行参数设置,然后调用rkmedia_vi_init对VI模块进行设置和使能,设置完成后再把VI的模块ID放到VI数组里面(vi_containers)。

三.VI模块代码的截图

下图就是整个VI模块配置的具体参数,这里使用设置的结构体是RV1126_VI_CONFIG。RV1126_VI_CONFIG里面主要包含了VI_CHN_ATTR_S结构体进行设置。

idVI模块的id号,用于初始化和使能VI模块

pcVideoNode: 摄像头的视频节点,这里默认是rkispp_scale0

u32BufCnt缓冲区计数,默认是3

u32WidthVI模块分辨率宽度1920

u32HeightVI模块分辨率高度1080

enPixFmt图像格式默认是NV12,这里填的是IMAGE_TYPE_NV12

enBufTypeVI模块捕捉视频的类型,这里默认填写MMAP

enWorkModeVI工作模式,这里写的是VI_WORK_MODE_NORMAL

填写完上述的配置参数后,就会调用rkmedia_vi_init这个自己封装的函数,这个函数主要是实现VI模块的初始化和使能的具体操作,具体看下图

rkmedia_vi_init这个自定义函数里面,关键是对VI进行初始化和使能,它分别调用了RK_MPI_VI_SetChnAttr的API对VI模块的属性进行初始化,然后再调用RK_MPI_VI_EnableChn对其使能。

设置完VI模块后,就要把VI模块的ID号设置到容器里面,调用自己封装的函数是set_vi_container

目的:把ID号放到数组里,这样别的.cpp文件也可以用这个ID,别的文件里的函数从这里获取对应的ID,方便AI的ID号能够在多个cpp或者c文件上使用,增加扩展性。

set_vi_container的具体实现是:

在这个自定义的函数里面,最主要是把VI的ID号存放在VI模块数组里面(vi_containers),具体结构如下图:

RV1126_ALL_CONTAINER结构体里面包含了四个模块的数组存储分别是VI模块(vi_contaianers)、AI模块(ai_containers)、VENC模块(venc_containers)、AENC模块(aenc_containers)。这四个模块容器就是分别存储,四个模块的ID号,让其能够更加方便的管理起来。

4.3RV1126的高分辨率VENC编码模块的设置

视觉项目的高分辨率(1920 * 1080)的VENC编码模块,在这个项目中VENC编码模块参数的设置是至关重要的,它可以对VI数据进行硬件编码让其可以进行高分辨率编码码流的推流(如上图)。高分辨率VENC模块的配置在源文件rkmedia_module_function.cpp里面。

  • 高分辨率VENC的思维导图

上面思维导图主要是描述VENC模块设置的大致流程,首先对RV1126_VENC_CONFIG结构体进行参数设置,然后调用rkmedia_venc_init对VENC模块进行设置,设置完成后再把VENC的模块ID放到VENC管理数组里面(venc_containers)。

  • 高分辨率VENC模块设置的代码截图

上述的参数是高分辨率VENC编码器的参数设置,我们来看看每个参数设置的意义

stVencAttr.enType编码器协议类型,这里填写的是H264编码器类型,RK_CODEC_TYPE_H264

stVencAttr.imageType编码器图像类型,这里写的类型要和图像输入类型保持一致,所以填写IMAGE_TYPE_NV12

stVencAttr.enType.u32PicWidth编码图像的分辨率宽度,这里是高分辨率所以写1920

stVencAttr.enType.u32PicHeight编码图像的分辨率高度,这里是高分辨率所以写1080

stVencAttr.enType.u32VirWidth编码图像的分辨率虚宽,这里写的跟u32PicWidth保持一致,所以填写1920

stVencAttr.enType.u32VirHeight编码图像的分辨率虚高,这里写的跟u32PicHeight保持一致,所以填写1080

stVencAttr.enType.u32Profile编码等级,这里填写66,指的是Baseline,这种模式更加适用于视频传输

stRcAttr.enRcMode编码器码率控制类型,这里填写的是H264 CBR码率控制模式,VENC_RC_MODE_H264CBR,下面码率控制的结构体都是以stH264Cbr作为设置

stRcAttr.stH264Cbr.u32GopH264的CBR码率控制GOP设置,这里GOP的设置是25

stRcAttr.stH264Cbr.u32BitRateH264的CBR码率控制码率大小的设置,这里是1920 * 1080 * 3 = 6220800比特率/s ~= 700KB/s

stRcAttr.stH264Cbr.u32SrcFrameRateDenH264的CBR码率控制控制结构体的源帧率分母的设置,这里写的是25

stRcAttr.stH264Cbr.u32SrcFrameRateNumH264的CBR码率控制控制结构体的源帧率分子的设置,这里写的是1

stRcAttr.stH264Cbr.u32DstFrameRateDenH264的CBR码率控制控制结构体的目标帧率分母的设置,这里写的是25

stRcAttr.stH264Cbr.u32DstFrameRateNumH264的CBR码率控制控制结构体的目标帧率分子的设置,这里写的是1

(备注:通常情况下源帧率的分子、分母和目标帧率的分子、分母数值保持一致)

设置完上述VENC编码参数后,我们就要调用自己封装的函数rkmedia_venc_init函数,对VENC模块进行设置,具体的实现如下图:

这个自定义函数还是非常简单的,就是把RK_MPI_VENC_CreateChn封装了一层,然后把RV1126_VENC_CONFIG的结构体指针传进去。

设置完VENC模块后,就要把VENC模块的ID号设置到VENC容器数组里面,高分辨率VENC的ID号是0,调用自己封装的函数是set_venc_container

set_venc_container具体的实现:

在这个自定义的函数里面,最主要是把VENC的ID号存放在VENC模块数组里面(vi_containers),具体结构如下图:

在这个自定义的函数里面,最主要是把VENC的ID号存放在VENC模块数组里面(venc_containers),具体结构如下图:

关于RV1126_ALL_CONTAINER的作用已经讲过了,这次VENC的ID号需要存放到venc_containers数组里面,这样更容易管理VENC模块号ID。

4.4RV1126的RGA模块

视觉项目的RGA模块,RGA模块是视频处理模块,这个模块可以对VI视频数据进行缩放、裁剪、格式转换、图片叠加等的功能,在这个项目里面RGA模块最重要的功能是把1920 * 1080的分辨率转换成1280 * 720的分辨率。RGA模块的配置在源文件rkmedia_module_function.cpp里面。

  • RGA模块设置的思维导图

上面思维导图是RGA设置的大体流程,RGA设置需要对RGA_ATTR_S结构体进行参数设置,设置完之后调用RK_MPI_RGA_CreateChn设置RGA模块。若成功则打印RGA Set Success,失败则打印RGA Set Failed。

  • RGA模块设置的代码截图

RGA_ATTR_S结构体里面包含了两个重要的结构体,分别是stImgIn和stImgOut。stImgIn是视频输入的结构体,stImgOut是处理后的视频结构体。除了这两个重要的结构体外,还有公共参数需要设置

stImgIn结构体的设置:

stImgIn.u32Width视频输入的分辨率宽度,这里使用的是1920

stImgIn.u32Height视频输入的分辨率宽度,这里使用的是1080

stImgIn.u32Width视频输入的分辨率宽度,这里使用的是1920

stImgIn.u32Height视频输入的分辨率宽度,这里使用的是1080                    

stImgIn.u32HorStride视频输入的分辨率虚宽,虚宽的数值跟分辨率宽度数值一致都是1920

stImgIn.u32VirStride视频输入的分辨率虚高,虚高的数值跟分辨率高度数值一致都是1080

stImgIn.imgType视频输入的格式这里设置的是NV12也就是IMAGE_TYPE_NV12

stImgIn.u32X设置stImgIn的X坐标

stImgIn.u32Y设置stImgIn的Y坐标

stImgOut结构体的设置:

stImgOut.u32Width视频输入的分辨率宽度,这里使用的是1280

stImgOut.u32Height视频输入的分辨率宽度,这里使用的是720

stImgOut.u32Width视频输入的分辨率宽度,这里使用的是1280

stImgOut.u32Height视频输入的分辨率宽度,这里使用的是720                   

stImgOut.u32HorStride视频输入的分辨率虚宽,虚宽的数值跟分辨率宽度数值一致都是1280

stImgOut.u32VirStride视频输入的分辨率虚高,虚高的数值跟分辨率高度数值一致都是720

stImgOut.imgType视频输入的格式这里设置的是NV12也就是IMAGE_TYPE_NV12

stImgIn.u32X设置stImgOut的X坐标

stImgIn.u32Y设置stImgOut的Y坐标

RGA公共参数
u16BufPoolCnt缓冲区计数,默认是3

u16Rotation旋转角度,这里填0,没有旋转角度

enFlip镜像控制,这里选择RGA_FLIP_H,水平镜像处理

bEnBufPool使能缓冲池

设置完上述的参数后,调用RK_MPI_RGA_CreateChn设置RGA模块,这块内容在之前的课程已经详细说过,这里不做说明。

4.5RV1126的低分辨率(1280 * 720)VENC编码模块的设置

视觉项目的低分辨率(1280* 720)的VENC编码模块,低分辨率VENC编码器的设置。低分辨率VENC的设置和高分辨率的设置方法基本上是一致的,唯一的区别在于分辨率要写成1280 * 720。获取低分辨率编码数据的流程如上图,分别是VI模块获取视频数据->RGA模块处理->获取1280*720的原始数据->送到低分辨率编码器处理->获取1280 * 720的编码(h264/h265)压缩数据。低分辨率VENC模块的配置在源文件rkmedia_module_function.cpp里面。

  • 低分辨率VENC的思维导图

上面思维导图主要是描述低分辨率VENC模块设置的大致流程,它的流程基本上和高分辨率是一致的。首先对RV1126_VENC_CONFIG结构体进行参数设置,然后调用rkmedia_venc_init对VENC模块进行设置,设置完成后再把VENC的模块ID放到VENC管理数组里面(venc_containers)。

  • 低分辨率VENC模块设置的代码截图

上述的参数是低分辨率VENC编码器的参数设置,大部分设置都和高分辨率设置是一样的。区别在于分辨率,码率这两个参数

stVencAttr.enType编码器协议类型,这里填写的是H264编码器类型,RK_CODEC_TYPE_H264

stVencAttr.imageType编码器图像类型,这里写的类型要和图像输入类型保持一致,所以填写IMAGE_TYPE_NV12

stVencAttr.enType.u32PicWidth编码图像的分辨率宽度,这里是低分辨率所以写1280

stVencAttr.enType.u32PicHeight编码图像的分辨率高度,这里是低分辨率所以写720

stVencAttr.enType.u32VirWidth编码图像的分辨率虚宽,这里写的跟u32PicWidth保持一致,所以填写1280

stVencAttr.enType.u32VirHeight编码图像的分辨率虚高,这里写的跟u32PicHeight保持一致,所以填写720

stVencAttr.enType.u32Profile编码等级,这里填写66,指的是Baseline,这种模式更加适用于视频传输

stRcAttr.enRcMode编码器码率控制类型,这里填写的是H264 CBR码率控制模式,VENC_RC_MODE_H264CBR,下面码率控制的结构体都是以stH264Cbr作为设置

stRcAttr.stH264Cbr.u32GopH264的CBR码率控制GOP设置,这里GOP的设置是25

stRcAttr.stH264Cbr.u32BitRateH264的CBR码率控制码率大小的设置,这里是1280* 720* 3 = 2,764,800比特率/s ~= 337.5KB/s

stRcAttr.stH264Cbr.u32SrcFrameRateDenH264的CBR码率控制控制结构体的源帧率分母的设置,这里写的是25

stRcAttr.stH264Cbr.u32SrcFrameRateNumH264的CBR码率控制控制结构体的源帧率分子的设置,这里写的是1

stRcAttr.stH264Cbr.u32DstFrameRateDenH264的CBR码率控制控制结构体的目标帧率分母的设置,这里写的是25

stRcAttr.stH264Cbr.u32DstFrameRateNumH264的CBR码率控制控制结构体的目标帧率分子的设置,这里写的是1

(备注:通常情况下源帧率的分子、分母和目标帧率的分子、分母数值保持一致)

设置完上述VENC编码参数后,我们同样要调用自己封装的函数rkmedia_venc_init函数,对低分辨率VENC模块进行设置,具体的实现如下图:

这个自定义函数还是非常简单的,就是把RK_MPI_VENC_CreateChn封装了一层,然后把RV1126_VENC_CONFIG的结构体指针传进去。

设置完VENC模块后,就要把VENC模块的ID号设置到VENC容器数组里面,低分辨率VENC的ID号是1,调用自己封装的函数是set_venc_container

set_venc_container具体的实现:

在这个自定义的函数里面,最主要是把低分辨率VENC的ID号存放在VENC模块数组里面(venc_containers),具体结构如下图:

在这个自定义的函数里面,最主要是把VENC的ID号存放在VENC模块数组里面(venc_containers),具体结构如下图:

关于RV1126_ALL_CONTAINER的作用已经在上节课讲解过了,这次VENC的ID号需要存放到venc_containers数组里面,这样更容易管理VENC模块号ID。

4.6camera_venc_thread线程获取高分辨率编码码流

camera_venc_thread线程获取高分辨率编码码流

介绍如何通过camera_venc_thread线程获取高分辨率(1920 * 1080)的编码码流数据,并且把编码码流插入到高分辨率编码码流队列里面。上图就是camera_venc_thread线程获取高分辨率编码码流的大体流程,我们要从VI节点容器和VENC节点容器里面获取到对应的VI节点和VENC节点,然后调用RK_MPI_SYS_Bind这个API绑定VI节点和VENC节点。然后创建camera_venc_thread线程获取高分辨率VENC码流,然后入到HIGH_VIDEO_QUEUE队列。这部分代码的实现在rkmedia_assignment_manage.cpprkmedia_data_process.cpp这两个源文件里面。

开发:多线程,响应速度快,同时利用cpu资源进行数据处理,效率提高。使用多线程之后,各个线程之间如何传输数据:利用队列,线程1的数据输入到队列里,线程2从队列提取数据进行下一步处理。

  • camera_venc_thread线程的思维导图

上图是获取1920 * 1080视频编码数据的思维导图,整个流程最关键是get_vi_container获取VI节点,get_venc_container获取VENC节点,然后调用RK_SYS_MPI_Bind绑定VI和VENC节点,并启动camera_venc_thread线程获取编码数据赋值到video_data_packet_t结构体,然后入队列。

  • camera_venc_thread代码截图并讲解

venc里 index0是高分辨率 index1是低分辨率

上面三个图就是关于camera_venc_thread整个流程,我们首先要通过get_vi_container从VI容器里面获取到VI节点,然后再调用get_venc_container从venc容器里面获取venc节点。利用RK_MPI_SYS_Bind把VI节点和VENC节点绑定起来,绑定起来后创建camera_venc_thread线程,从这个线程里面获取1920 * 1080的编码码流数据。

调用的API是RK_MPI_SYS_GetMediaBuffer,MOD_ID是RK_ID_VENC, CHN_ID是创建的VENC的CHNID来直接获取高分辨率的VENC码流数据,并且把数据拷贝到video_data_packet_t结构体,包括每一帧的视频流数据RK_MPI_GetPtr(mb),还有每一帧的视频长度RK_MPI_GetSize(mb)。然后把整个video_data_packet包入队,high_video_queue->putVideoPacketQueue里面。video_data_packet_t结构体里面有两个成员变量,一个是buffer(视频缓冲区)、video_frame_size是每一帧视频的长度,frame_flag关键帧标识符。下面是RKMEDIA_BUFFER赋值到VIDEO_DATA_PACKET_T的核心代码:

memcpy(video_data_packet->buffer, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb));

video_data_packet->video_frame_size = RK_MPI_MB_GetSize(mb);

总结:利用一个线程,获取venc数据,再把数据放到结构体,venc长度也放到结构体,再把结构体放到队列。

4.7get_rga_thread线程和low_camera_venc_thread线程获取低分辨率VENC码流数据

介绍如何通过get_rga_thread线程和low_camera_venc_thread共同获取低分辨率(1280 * 720)的编码码流并且入队列。从上图我们可以看出。我们经过几个步骤首先要调用get_vi_container获取VI节点,然后把VI节点和RGA节点绑定起来,通过get_rga_thread线程获取1280 * 720的原始数据并把1280 * 720的原始数据发送到1280 * 720的VENC低分辨率编码器。

二.低分辨率码流获取的思维导图

上图是获取低分辨率1280 * 720编码码流的思维导图,从上面的思维导图可以看出来,整个流程最关键是get_vi_container获取VI节点,然后调用RK_SYS_MPI_Bind绑定VI和RGA节点。然后创建get_rga_thread线程获取每一帧rga的1280 * 720的视频原始数据,调用的API是RK_MPI_SYS_GetMediaBuffer,获取每一帧1280 * 720原始数据后再调用RK_MPI_SYS_SendMediaBuffer发送每一帧原始数据到1280 * 720的VENC编码器。

创建low_camera_venc_thread线程获取每一帧1280 * 720的编码码流数据,调用的API是RK_MPI_SYS_GetMediaBuffer获取完每一帧1280 * 720编码视频数据后存放到low_video_queue里面,调用的函数是putVideoPacketQueue。

  • 代码架构截图并讲解

上面的截图就是如何通过get_rga_thread和low_camera_thread线程的结合获取低分辨率(1280 * 720)的编码码流。首先要通过RGA的节点和VENC的节点进行RK_SYS_MPI_Bind绑定,然后开启get_rga_thread获取每一帧的RGA处理过后的1280 * 720原始数据,并且调用RK_MPI_SYS_SendMediaBuffer这个API把每一帧1280 * 720的原始数据发送到低分辨率的编码器里面,核心代码,如下:。

while (1)

    {

        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA, 0 , -1); //获取每一帧RGA处理过后的数据

        if(!mb)

        {

            break;

        }

        RK_MPI_SYS_SendMediaBuffer(RK_ID_VENC, 1, mb); //把每一帧RGA数据传输到低分辨率VENC里面

        RK_MPI_MB_ReleaseBuffer(mb); //释放资源

    }

然后再创建low_camera_thread现成获取每一帧1280 * 720的编码视频数据,然后把每一帧低分辨率的编码数据赋值到video_data_packet_t结构体,包括每一帧的视频流数据RK_MPI_GetPtr(mb),还有每一帧的视频长度RK_MPI_GetSize(mb)。然后把整个video_data_packet包入队,low_video_queue->putVideoPacketQueue里面。video_data_packet_t结构体里面有两个成员变量,一个是buffer(视频缓冲区)、video_frame_size是每一帧视频的长度,frame_flag关键帧标识符。下面是RKMEDIABUFFER赋值到VIDEO_DATA_PACKET_T的核心代码:

memcpy(video_data_packet->buffer, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb));

video_data_packet->video_frame_size = RK_MPI_MB_GetSize(mb);

4.8FFMPEG推流器讲解(一):FFMPEG重要结构体

本章节主要介绍FFMPEG的结构体,FFMPEG是音视频的瑞士军刀,它提供了一系列丰富的音视频处理接口。如:编码、解码、推流、滤镜等等。在我们这个项目里面,FFMPEG主要的作用是进行视频推流的功能,就是把RV1126编码的视频码流利用FFMPEG框架推送到流媒体服务器。

  • FFMPEG重要结构体:

FFMPEG中有六个比较重要的结构体,分别是AVFormatContextAVOutputFormat AVStreamAVCodecAVCodecContextAVPacketAVFrameAVIOContext结构体,这几个结构体是贯穿着整个FFMPEG核心功能,并且也是我们这个项目中最重要的几个结构体。下面我们来重点介绍这几个结构体:

2.1. AVFormatContext结构体

这个结构体是统领全局的基本结构体,这个结构体最主要作用的是处理封装、解封装等核心功能。我们来看看AVFormatContext具体的成员变量:

2.1.1. AVInputFormat * iformat输入数据的封装格式,仅作用于解封装用avformat_open_input

2.1.2. AVOutputFormat * ofomat输出数据的封装格式,仅作用于解封装用avformat_write_header

2.1.3. AVIOContext * pbI/O的上下文,在解封装中由用户在avformat_open_input之前来设置,若封装的时候用户在avformat_write_header之前设置

2.1.4. int nb_streams流的个数,若只有视频流nb_streams = 1, 若同时有视频流和音频流则nb_streams = 2。

2.1.5. AVStream **stream列出文件中所有流的列表

2.1.6. int64_t start_time第一帧的位置

2.1.7. int64_t duration流的持续时间

2.1.8. int64_t bit_rate流的比特率

2.1.9. int64_t probsize输入读取的用于确定容器格式的大小

2.1.10. AVDictionary *metadata元数据

2.1.10. AVDictionary *metadata元数据

2.1.11. AVCodec * video_codec视频编解码器

2.1.12. AVCodec * audio_codec音频编解码器

2.1.13. AVCodec *subtitle_codec字幕编解码器

2.1.14. AVCodec *data_codec数据编解码器

2.2. AVOutputFormat 结构体:

这个结构体的功能类似于COM接口,表示文件容器输出格式,这个结构体的特点是着重于函数的实现

具体的成员变量:

可以看到AVOutputFormat结构体有很多个成员变量,我们来重点说一下这里面比较重要的成员变量,因为这里面有一些成员变量不是必须的。

2.2.1 const char *name;  //描述的名称

2.2.2. const char *long_name;//格式的描述性名称,易于阅读。

2.2.3. enum AVCodecID audio_codec; //默认的音频编解码器

2.2.4. enum AVCodecID video_codec; //默认的视频编解码器

2.2.5. enum AVCodecID subtitle_codec; //默认的字幕编解码器

2.2.6. struct AVOutputFormat *next; //链表NEXT

2.2.7. int (*write_header)(struct AVFormatContext *);  //写入数据头部

2.2.8. int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);//写一个数据包。 如果在标志中设置AVFMT_ALLOW_FLUSH,则pkt可以为NULL

2.2.9. int (*write_trailer)(struct AVFormatContext *); //写入数据尾部

2.2.10. int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush); //刷新AVPacket,并写入

2.2.11. int (*control_message)(struct AVFormatContext *s, int type, void *data, size_t data_size);//允许从应用程序向设备发送消息。

2.2.12. int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,   AVFrame **frame, unsigned flags);//写一个未编码的AVFrame

2.2.13. int (*init)(struct AVFormatContext *);//初始化格式。 可以在此处分配数据,并设置在发送数据包之前需要设置的任何AVFormatContextAVStream参数。

2.2.14.void (*deinit)(struct AVFormatContext *);//取消初始化格式。

2.2.15. int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);//设置任何必要的比特流过滤,并提取全局头部所需的任何额外数据

2.3.AVStream结构体:

AVStream包含了每一个视频/音频流信息的结构体,同样的这个结构体也有很多重要的成员变量,我们来看看这些成员变量具体的含义。

2.3.1. index/id这个数字是自动生成的,根据index可以从streams表中找到该流

2.3.2.time_base流的时间基,这是一个实数,该AVStream中流媒体数据的PTS和DTS都将会以这个时间基准。视频时间基准以帧率为基准,音频以采样率为时间基准

2.3.3.start_time流的起始时间,以流的时间基准为单位,通常是该流的第一个PTS

2.3.4.duration流的总时间,以流的时间基准为单位

2.3.5.need_parsing对该流的parsing过程的控制

2.3.6.nb_frames流内的帧数目

2.3.7.avg_frame_rate平均帧率

2.3.8.codec该流对应的AVCodecContext结构,调用avformat_open_input生成

2.3.9.parser指向该流对应的AVCodecParserContext结构,调用av_find_stream_info生成

2.4.AVCodec结构体:

AVCodec是ffmpeg的音视频编解码,它提供了个钟音视频的编码库和解码库,FFMPEG通过AVCODEC可以将音视频数据编码成对应的数据压缩包。

2.4.1. AVCodecIDAVCODECID编码器的ID号,这里的编码器ID包含了视频的编码器ID,如:AV_CODEC_ID_H264、AV_CODEC_ID_H265等等。音频的编码器ID:AV_CODEC_ID_AAC、AV_CODEC_ID_MP3等等。

2.4.2. AVMediaType指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。

AVMediaType具体定义:

enum AVMediaType {
    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
    AVMEDIA_TYPE_VIDEO,
    AVMEDIA_TYPE_AUDIO,
    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
    AVMEDIA_TYPE_SUBTITLE,
    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
    AVMEDIA_TYPE_NB
};

2.4.3. AVRotational supported_framerates支持的帧率,这个参数仅支持视频设置

2.4.4. enum AVPixelFormat * pix_fmts支持的像素格式,这个参数支持视频设置

2.4.5. int * supported_samplerates支持的采样率,这个参数支持音频设置

2.4.6. enum AVSampleFormat * sample_fmts支持的采样格式,这个参数仅支持音频设置

2.4.7. uint64_t * channel_layouts支持的声道数,这个参数仅支持音频设置

2.4.8. int private_data_size私有数据的大小

2.5.AVCodecContext结构体:

AVCodecContext是FFMPEG编解码上下文的结构体,它内部包含了AVCodec编解码参数结构体。除了AVCodec结构体外,还有AVCodecInternal、AVRotational结构体,这包含了AVCodecID、AVMediaType、AVPixelFormat、AVSampleFormat等类型,其中包含视频的分辨率width、height、帧率framerate、码率bitrate等。

2.5.2. AVMediaType codec_type指明当前编码器的类型,包括:视频(AVMEDIA_TYPE_VIDEO)、音频(AVMEDIA_TYPE_AUDIO)、字幕(AVMEDIA_TYPE_SUBTITILE)。

2.5.3. AVCodec * codec指明相应的编解码器,如H264/H265等等

2.5.4. AVCodecID * codec_id编解码器的ID,这个在上面有详细的说明

2.5.5. void * priv_data指向相对应的编解码器

2.5.6. int bit_rate编码的码流,这里包含了音频码流和视频码流码率的设置

2.5.7.thread_count编解码时候线程的数量,这个由用户自己设置和CPU数量有关

2.5.8. AVRational time_base根据该参数可以将pts转换为时间

2.5.9. int width, height每一帧的宽和高

2.5.10. AVRational time_base根据该参数可以将pts转换为时间

2.5.11. int gop_size一组图片的数量,专门用于视频编码

2.5.12. enum AVPixelFormat pix_fmt像素格式,编码的时候用户设置

2.5.13. int refs参考帧的数量

2.5.14.enum AVColorSpace colorspaceYUV色彩空间类型

2.5.15.enum AVColorSpace color_rangeMPEG JPEG YUV范围

2.5.16.int sample_rate音频采样率

2.5.17.int channel声道数(音频)

2.5.18. AVSampleFormat sample_fmt采样格式

2.5.19.int frame_size每个音频帧中每个声道的采样数量

2.5.20.int profile配置类型

2.5.21.int level级别

2.6.AVPacket

AVPacket是FFMPEG中一个非常重要的结构体,它保存了解复用之后,解码之前的数据,它存的是压缩数据的音视频数据。除了压缩数据后,它还包含了一些重要的参数信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长、流媒体索引等。

AVPacket如果是存储视频数据的话,基本上是存储H264/H265这些码流,如果存储音频数据的话则会保存AAC/MP3码流。

重要的结构体成员变量:

2.6.1. AVBufferRef * buf对数据包所在的引用的计数缓冲区引用

2.6.2. int64_t pts显示时间戳,它是来视频播放的时候用于控制视频显示顺序和控制速度。PTS是一个相对的时间戳,通常它以毫秒为单位,表示该帧在播放器的哪个时间节点进行显示

2.6.3. int64_t dts解码时间戳,它是来指视频或者音频在解码时候的时间戳,用于控制解码器的解码顺序和速度。DTS也是一个相对的时间戳,通常它以毫秒为单位,表示该帧在解码器中哪个时间节点进行解码

2.6.4. uint8_t * data具体的缓冲区数据,这里的数据可以是视频H264数据也可以是音频的AAC数据

2.6.5. int size缓冲区长度,这里就是具体的码流数据的长度

2.6.6.int stream_index流媒体索引值,假设同时有音频和视频。那stream_index = 0等于视频数据,stream_index = 1等于音频数据

2.6.7.flags专门用在视频编码码流的标识符,默认都要添加AV_PKT_FLAG,这指的是每一帧都要添加一个关键帧,否则画面则无法正常解码出来。

2.6.8. AVPacketSideData * side_data容器可以提供的额外数据包数据,包含多种类型的边信息。

2.6.9. int side_data_element额外数据包的长度

2.6.10. int64_t durationAVPacket的持续时间,它的时间以AVStream->time_base为单位作为计算,若未知则为0.

2.6.11.int64_t pos字节的位置,这个很少用

2.7.AVFrame

AVFrame的结构体一般存储音视频的原始数据,若存储视频数据的话则存储YUV/RGB等数据;若存储音频数据的话,则会存储PCM数据,此外还包含了一些重要的相关信息。

2.7.1. uint8_t * data[AV_NUM_DATA_POINTERS]原始数据,如果是视频数据则是(YUV,RGB这些数据;音频数据的话是PCM)

2.7.2.int linesize[AV_NUM_DATA_POINTERS]:data中一行数据的大小,要注意:这个值未必就完全等于图像的宽,一般都会大于图像的宽度

2.7.3. widthheight视频帧的宽度和高度

2.7.4. int nb_samples音频的一个AVFrame种可能包含多个音频帧,这个参数就是包含了多少个音频帧

2.7.5. format解码后的原始数据类型(如:YUV420、YUV422、RGB24等等)

2.7.6. int key_frame是否是关键帧

2.7.7. eum AVPictureType pict_type帧类型(I帧、P帧、B帧),下面是PictureType的枚举类型

enum AVPictureType {

    AV_PICTURE_TYPE_NONE = 0, ///< Undefined

    AV_PICTURE_TYPE_I,     ///< Intra

    AV_PICTURE_TYPE_P,     ///< Predicted

    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted

    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG-4

    AV_PICTURE_TYPE_SI,    ///< Switching Intra

    AV_PICTURE_TYPE_SP,    ///< Switching Predicted

    AV_PICTURE_TYPE_BI,    ///< BI type

};

2.7.8. AVRational sample_aspect_ration宽高比(16:9、4:3)

2.7.9. int64_t pts显示时间戳

2.7.10. coded_picture_number编码帧序号

2.7.11. display_picture_number显示帧序号

2.8. AVIOContext结构体

AVIOContext结构体是FFMPEG管理输入输出的结构体,下面我们来看看这里面比较重要的结构体成员变量

2.8.4.1.  AVClass它是在FFMPEG中具体应用很多,它类似于C++中的虚基类的结构体,用于实现多继承的功能。它定义了一系列的函数指针,这些函数指针可以被子类所覆盖了,从而实现子类对父类的重载。

2.8.42.  URLProtocol每个协议对应一个URLProtocol,这个结构体也不在FFMPEG提供的头文件中。我们具体来看看这个结构体的定义

typedef struct URLProtocol {
    const char *name;
    int (*url_open)(URLContext *h, const char *url, int flags);
    int (*url_read)(URLContext *h, unsigned char *buf, int size);
    int (*url_write)(URLContext *h, const unsigned char *buf, int size);
    int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
    int (*url_close)(URLContext *h);
    struct URLProtocol *next;
    int (*url_read_pause)(URLContext *h, int pause);
    int64_t (*url_read_seek)(URLContext *h, int stream_index,
        int64_t timestamp, int flags);
    int (*url_get_file_handle)(URLContext *h);
    int priv_data_size;
    const AVClass *priv_data_class;
    int flags;
    int (*url_check)(URLContext *h, int mask);
} URLProtocol;

2.8.4.2.1. url_open打开对应的文件IO的url地址

2.8.4.2.2. url_read读取对应的文件IO的url地址的数据内容

2.8.4.2.3. url_write写入对应的文件IO的url地址的数据内容

2.8.4.2.4. url_seek移动文件指针

2.8.4.2.5. url_close关闭文件指针

在这个结构体里面提供了一些列的回调函数,每个回调函数都会有对应的协议进行编写。

总结:

avformatcontext核心结构体,下面四个主要结构体

①avoutputformat :ffmpeg的输出结构体,实现不同协议的数据包写入,根据不同的流媒体协议去写入头部(rtmp,rtsp,srt,udp,tcp等等),对不同的流媒体服务器,写入每一帧AVPacket包,对每一个AVPacket刷新。

②aviocontext:处理ffmpeg,io文件操作。

③avcodec:描述编解码信息,帧率,像素,采样率等,根据此结构体打开avcodeccontext上下文结构体,真正起作用的还是avcodeccontext,和avcodec很像,很多是从avcodec传过来的。

④avstream:存储音视频流具体流的信息,包括时间基,帧率等等。

rv1126的h264/h265的数据放到avpacket结构体之后,ffmpeg才能根据avpacket进行推流。

4.9FFMPEG推流器讲解(二):FFMPEG输出模块初始化

FFMPEG输出模块,输出模块的最大作用是对音视频推流模块进行初始化让其能够正常工作起来,RV1126的码流通过FFMPEG进行推流,输出模块一般由几个步骤。分别由avformat_alloc_output_context2分配AVFormatContextavformat_new_stream初始化AVStream结构体、avcodec_find_encoder找出对应的codec编码器、利用avcodec_alloc_context3分配AVCodecCotext、设置AVCodecContext结构体参数、利用avcodec_parameters_from_context把codec参数传输到AVStream里面的参数、avio_open初始化FFMPEG的IO结构体、avformat_write_header初始化AVFormatContext。在RV1126+FFMPEG多路码流推流的项目中,FFMPEG输出模块的初始化在rkmedia_ffmpeg_config.cpp的init_rkmedia_ffmpeg_context里面

rv1126 采集编码,通过ffmpeg传输框架,把每一帧视频流传输到rtmp流媒体服务器

初始化ffmpeg输出模块,传输才能正常启动

  • FFMPEG输出配置的框图:

上图是整体的框图,我们具体来看看每个框图的代码实现。

2.1. 分配FFMPEG AVFormatContext输出的上下文结构体指针

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename)

第一个传输参数:AVFormatContext结构体指针的指针,是存储音视频封装格式中包含的信息的结构体,所有对文件的封装、编码都是从这个结构体开始。

第二个传输参数:AVOutputFormat的结构体指针,它主要存储复合流信息的常规配置,默认为设置NULL。

第三个传输参数:format_name指的是复合流(裸流:码流里只有一种类型的流,比如只有视频或只有音频。复合流:结合起来(假设只有单流,ffmpeg底层也会把数据进行填充,让其变成复合流)。流媒体服务器解析的是复合流,rtmp服务器主要是解析FLV复合流信息)的格式,比方说:flv、ts、mp4等等

第四个传输参数:filename是输出地址,输出地址可以是本地文件(如:xxx.mp4、xxx.ts等等)。也可以是网络流地址(如:rtmp://xxx.xxx.xxx.xxx:1935/live/01)

上面这个API是根据我们流媒体类型去分配AVFormatContext结构体。我们传进来的类型会分为FLV_PROTOCOLTS_PROTOCOL,具体如何配置如下面:

若TS_PROTOCOL类型:avformat_alloc_output_context2(&group->oc, NULL, "mpegts", group->url_addr);

若FLV_PROTOCOL类型:avformat_alloc_output_context2(&group->oc, NULL, "flv", group->url_addr);

注意:TS格式分别可以适配以下流媒体复合流,包括:SRT、UDP、TS本地文件等。flv格式包括:RTMP、FLV本地文件等等。

2.2. 配置推流器编码参数和AVStream结构体

AVStream主要是存储流信息结构体,这个流信息包含音频流和视频流。创建的API是avformat_new_stream如下图

AVStream * avformat_new_stream(AVFormatContext *s, AVDictionary **options);

第一个传输参数:AVFormatContext的结构体指针

第二个传输参数:AVDictionary结构体指针的指针

返回值:AVStream结构体指针

2.3. 设置对应的推流器编码器参数

AVCodec *avcodec_find_encoder(enum AVCodecID id); //

第一个传输参数:传递参数AVCodecI

2.4. 根据编码器ID分配AVCodecContext结构体

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

第一个参数:传递AVCodec结构体指针

avcodec_find_encoder的主要作用是通过codec_id(编码器id )找到对应的AVCodec结构体。在RV1126推流项目中codec_id我们使用两种,分别是AV_CODEC_ID_H264AV_CODEC_ID_H265并利用avcodec_alloc_context3去创建AVCodecContext上下文。

初始化完AVStream和编码上下文结构体之后,我们就需要对这些参数进行配置。重点:推流编码器参数和RV1126编码器的参数要完全一样,否则可能会出问题,具体的如下图:

1920 * 1080编码器和FFMPEG推流器的配置

1280* 720编码器和FFMPEG推流器的配置

FFMPEG的视频编码参数如:分辨率(WIDTHHEIGHT)、时间基(time_base)、 帧率(r_frame_rate)、GOP_SIZE等都需要和右边VENC的参数要一一对应起来。其中time_base的值要和视频帧率必须要一致。如RV1126高编码器分辨率是1920 * 1080,则FFMPEG推流器的WIDTH = 1920,HEIGHT = 1080;若RV1126编码器的分辨率是1280 * 720,则FFMPEG推流器的WIDTH = 1280,HEIGHT = 720;若RV1126的GOP的值是25,那右边FFMPEG的gop_size 也等于25;time_base的数值和帧率保持一致

AV_CODEC_FLAG_GLOBAL_HEADER发送视频数据的时候都会在关键帧前面添加SPS/PPS,这个标识符在FFMPEG初始化的时候都需要添加。

2.5. 设置完上述参数之后,拷贝参数到AVStream编解码器,具体的操作如下:

拷贝参数到AVStream,我们封装到open_video自定义函数里面,要先调用avcodec_open2打开编码器,然后再调用avcodec_parameters_from_context把编码器参数传输到AVStream里面

2.5.1.int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

这个函数的具体作用是,打开编解码器

第一个参数:AVCodecContext结构体指针

第二个参数:AVCodec结构体指针

第三个参数:AVDictionary二级指针

2.5.2. int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);

这个函数的具体作用是,把AVCodecContext的参数拷贝到AVCodecParameters里面。

第一个参数:AVCodecParameters结构体指针

第二个参数:AVCodecContext结构体指针

2.6. 打开IO文件操作

使用avio_open打开对应的文件,注意这里的文件不仅是指本地的文件也指的是网络流媒体文件,下面是avio_open的定义。

int avio_open(AVIOContext **s, const char *url, int flags);

第一个参数:AVIOContext的结构体指针,它主要是管理数据输入输出的结构体

第二个参数: url地址,这个URL地址既包括本地文件如(xxx.ts、xxx.mp4),也可以是网络流媒体地址,如(rtmp://192.168.22.22:1935/live/01)等

第三个参数:flags标识符

#define AVIO_FLAG_READ  1                                      /**< read-only */

#define AVIO_FLAG_WRITE 2                                      /**< write-only */

#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE)  /**< read-write pseudo flag */

2.7. avformat_write_header对头部进行初始化,输出模块头部进行初始化

int avformat_write_header(AVFormatContext *s, AVDictionary **options);

第一个参数:传递AVFormatContext结构体指针

第二个参数:传递AVDictionary结构体指针的指针

4.10FFMPEG推流器(三):FLV流数据

什么是FLV:

FLV流媒体协议是美国Adobe公司推出来的一种流媒体协议。FLV流媒体格式的特点是封装过后的音视频数据非常小、并且封装的规范相对更加简单,所以FLV流媒体格式非常适合网络传输。但是FLV格式是Adobe公司的私有协议,所以它支持的网络传输协议比较有限:如RTMP、HTTP-FLV。

FLV流媒体复合格式,包含视频和音频的码流。合并起来传输给RTMP流媒体服务器

二. FLV流媒体格式讲解:

FLV流媒体封装格式一般由两个部分组成,一个是FLV Header、另外一个是FLV Body。其中,FLV Header长度固定式9个字节,FLV BODY则是由一组组的Previous Tags Size + Tag组成。Previous Tag Size一般在整个Tag的前面,它一般记录前一个Tag的大小。Tag的类型一般分为三种、分别是脚本数据帧类型、视频数据类型、音频数据类型。

2.1. FLV Header的讲解:

FLV头部前三个字节是固定FLV的十六进制,Version是版本号固定为1。其他的根据自己的业务配置

2.3. FLV BODY的讲解:

FLV BODY一般由FLV Tag Header和Tag Data组成。如果是视频的TAG DATA,则FLV Tag Header+ Video Tag Data。而如果是音频的TAG DATA,则是FLV Tag Header + Audio Tag Data。但无论是视频的Tag Data还是音频的Tag Data它们的FLV Tag Header都是相同的,下面我们来看看公共部分的FLV Tag Header

2.3.1. FLV Script Tag的讲解:

FLV Script Tag也是由FLV Tag Header + Script Data Tag组成。Script Tag的类型一般被称为MedtaData Tag,它一般会存储一些关于FLV音视频的参数信息,比方说:分辨率(width、height)、duration,通常来说Script Tag Data是第一个出现的Tag,并且有且只有一个。

Script Tag是由两个AMF包组合起来(AMF 包 = 数据类型(看下图) + 数据长度 + 数据)。AMF1的第一个字节表示包类型、默认0x02。第2-3个字节代表的是字符串的长度,默认0X00A。 而后面的字节是具体的字符串(“onMetaData”)用十六进制表示:(6f、6e、4d、65、74、61、44、61、74、61)。

AMF数据类型:

而第二个AMF包,第一个字节是0x08表示数组类型。第2-5个字节表示的是数组元素的个数、而后面的数组则是每个数组的键值对。

AMF2数组对应的键值对:

具体的数据分析:

AMF1的分析:

AMF2的分析:

2.3.2. FLV VIDEO TAG的讲解:

FLV VIDEO TAG是由两部分组成,FLV Tag HEADER + VIDEO DATA TAG。具体的我们来看看图解:

从这张图可以看出,VIDEO DATA TAG是视频的具体信息,这其中包括:STREAMID视频流ID、FrameType视频帧类型(1: avc keyframe指的是关键帧、2: avc inter frame指的是普通帧)、CODECID编码ID(默认7:AVC编码)、AVCPacketType编码包类型(0: avc sequence hdr、1: NALU类型)、CompositionTime构造时间、Data具体的视频数据

2.3.3. FLV AUDIO TAG的讲解

FLV AUDIO TAG是由两部分组成,FLV Tag HEADER + VIDEO DATA TAG。具体的我们来看看图解:

从这张图可以看出,AUDIO DATA TAG是视频的具体信息,这其中包括:STREAMID音频流ID、SoundFormat音频类型(10: aac)、SoundRate音频采样率、SoundSize音频采样深度、SoundType音频编码类型、AACPacketType AAC包的类型、Data就是具体的音频数据。下面这个是每一个Audio Data Tag的具体定义:

三.利用flvAnalysis去分析flv码流

上图是FLV的分析软件,这个软件可以清晰明了看到FLV每个数据的类型详情

ffmpeg软件打开bin目录下的文件,点击data按钮

4.11FFMPEG推流器讲解(四):RTMP协议

  • 什么是RTMP协议

RTMP协议是实时消息传输协议的缩写(Real Time Messaging Protocol)的缩写。RTMP协议是基于TCP协议开发的消息传输协议,它是Adobe公司开发的一种私有传输协议。它主要的用途是音视频推流、实时交互语音和数据交互等功能。

  • RTMP的交互流程:

RTMP交互的过程总共可以分为以下几步:握手、建立网络对话、创建网络流、播放。握手,RTMP的握手主要是TCP和RTMP协议之间的握手,它是整个交互过程的开始。建立网络对话,当握手成功之后,就要建立起Client和Server端的连同关系。建立网络流,就是当网络会话建立成功后,建立起发送多媒体数据的通道。播放,指的是Client和Server传输音视频数据的过程。下面是一个大体的RTMP 客户端和服务端交互的过程。

  • RTMP通信步骤的讲解:

3.1. 握手:

一个RTMP的开始都是以握手开始的,握手的过程分别为以下步骤:

1). Client端发送C0(版本号),C1(随机字符串)到Server,而Server收到C0或者C1后发送应答S0,S1

2). 当Client收到Server端发来的应答数据S0(版本号),S1(随机字符串)后,则发送C2数据到Server。若此时Server同时收到C0、C1后,就开始发送S2到Client

3). 当Client端和Server端分别收到S2和C2信号后,则代表握手完成。

9次握手,前三次是tcp握手,后六次是rtmp自己的握手

3.2.建立网络会话

1). Client端发送Connect指令到Server端,这个指令的用处就是请求和Server端建立一个连接。

2). Server端接收到Client端发送的指令后,接着发送窗口协议到Client端,并同时连接到应用程序。(拥塞控制,客户端缓存一部分数据,一点一点往服务端传输,避免卡顿)

3). Server端发送设置带宽的协议到Client端

4). Client端处理带宽协议后,发送确认窗口大小协议到Server端

5). Server端发送用户控制信息指令“Stream Begin”到Client端

6). Server端发送用户消息指令”_result”通知Client端连接状态

主要是窗口大小和带宽大小

3.3.建立网络流(NetStream)

1). Client端发送指令”createStream”到Server端

2). Server端接收到”createStream”指令后,则成功创建网络流。S并同时发送”_result”指令通知Client端.

创建rtmp传输流通道

3.4.播放流(Play)

1). Client端发送指令”play”到Server端。

2). Server端接收后,Server端发送ChunkSize协议消息到Client端

3). Server端发送控制指令”streambegin”给Client端

4). 若3)步发送成功之后,Server端会发送”响应状态” NetStream.Play.Start & NetStream.Play.reset通知客户端“Play”指令成功。

主要是传输块的信息,音视频的信息,播放状态

  • RTMP消息协议的讲解

RTMP的消息协议一般分为三种,分别是消息(Message)、消息块(Chunk)、消息分块(Msg)。

4.1. 消息(Messgae)(传输的结构体)(头和具体数据,rtmp具体数据根flv格式的内容很像)

Message是RTMP传输协议的最基本单元。在Messgae中,不同类型的Messgae含有不同的Message Type Id。在RTMP协议中,一共有十几种Message Type,比方说Message Type ID区间在1-6则代表此消息协议是控制协议。

Message Type ID为8代表的是此消息传输音频数据,若Message Type ID 为9则代表的是此消息传输的是视频数据。Message Type ID区间在15到20则用于发送AMF编码指令(AMF指的是Flash和服务端常见的编码方式),比方说播放、暂停、Client和Server端进行交互等。

说完Message Type Id后,我们来介绍一下RTMP的Message的首部信息(Rtmp Header),Rtmp Header是由三元组组成,包括:timestamp delta、mesage_length、type_id。具体的我们来看下图(B代表的是字节):

timestamp delta和上一个chunk的时间差,若这个值大于等于16777215,该字段必须等于16777215

timestamp 时间戳,每一个Messgae生成的时间戳。

BodySize指的是message的长度

Typeid指的是message 类型

下面的表格是Message类型的分类:

每一帧音视频数据,都是解析rtmp message协议信息

4.2. 消息块(Chunk)

由于在网络传输中,若网络环境不好的情况下,一个消息单元需要拆分成数据量更小的数据块才能够适应网络的传输。在RTMP协议中规定,每个消息都需要被拆分才能够正常传输。拆分的消息分块(Chunk)由Chunk Header 和 Chunk Data组成。Chunk Header由三部分组成:Chunk Basic Header(块基本头)、Chunk Message Header(块消息头)、Extended TimeStamp(扩展时间戳),具体的如下图:

4.2.2.1.Chunk Basic Header:这个字段包含流ID(chunk stream id)和分块类型(fmt),流id一般用csid来表示,chunk type决定了Message Header的格式,Chunk Type长度固定为2bit。若Basic Header长度等于1Byte,则cs id取值范围在[0,64]。

而CSID: 0, 1是由协议保存的特殊协议:0代表的是整个Chunk Basic Header要占用两个字节,CSID的取值范围是[64,319]; 1代表的还是占用三个字节,CSID取值范围[64,65599]。

Chunk Basic Header:1 Byte (8 bit)

Chunk Basic Header:2 Byte (16bit),csid = 0

上面这个图是chunk basic header长度为2byte的情况,这种chunk header的特点是type所在的位置全部设置成0。而第三个字节用CSID----64表现出来,CSID取值范围是[64,319]。

Chunk Basic Header:3 Byte (24bit),csid = 1

上面这个图是chunk basic header长度为3byte的情况,这种情况的chunk header的特点是,第一个字节的位置全部设置为1。而剩余的CSID取值的范围是[64,65599]。

4.2.2.2.Chunk Message Header:Chunk Message Header一般分成四种类型的消息头,分别是0, 1 , 2 , 3。Message Header主要包含了要发送的具体信息。

Chunk Type(fmt) = 0,占用11bytes(88bit)

当fmt = 0的时候,整个Message Header占用了11个字节,下面我们来分析每一个数据单元的作用:

TimeStamp(时间戳):表示的是时间戳,占用三个字节。它代表的是解析时候的实时时间戳,它最大的取值范围是0xffffff,当时间戳超过这个最大值的时候,所有字节位设置为1。

Message Length(消息数据的长度):Message Length也和TimeStamp一样占用3个字节,它代表的是实际发送的数据信息,比方说:音频数据长度、视频数据长度。这里要注意的是,Message Length是整个Chunk的总长度,并不是Chunk Data的长度

Message type id(消息类型id):消息类型id占用1个字节,它代表的是实际发送数据的类型id,比方说: 8代表的是音频数据、9代表的是视频数据。

Message stream id(消息流id):消息流id占用4个字节,它代表的是chunk所在的流id。

Chunk Type(fmt) = 1,占用7bytes(56bit)

当Chunk Type等于1的时候,没有了表示message stream id的字段(总共占用4个字节),另外这里的时间戳也变成了TimeStamp Delta。TimeStamp Delta和TimeStamp最大不同的是,它存储的是和上一个Chunk的时间差,其他的和TimeStamp相似。

Chunk Type(fmt) = 2,占用3bytes(24bit)

当ChunkType等于2的时候,整个结构就更简单了,仅仅只有一个TimeStampDelta单元。它占用3个字节,它表示的是所在的流信息、消息长度和类型都是相同的。剩余的所有字节都来表示TimeStamp Delta。

Chunk Type(fmt) = 3,占用0bytes(0bit)

当type == 3的时候,为0个字节。它代表的是此Chunk的Message Header和前一个Message Header所有信息都相同。

4.2.2.3.Extended TimeStamp(扩展时间戳):

这个值默认为0,当启动这个值的时候,timestamp和timestamp delta全部设置为1。

4.3.消息分块(Msg)

在网络传输中,每一个RTMP协议单元都需要被分割成一块一块内容才能够发送。其中Message Body指的是每一个分割消息的负载部分,而每个被分割的数据块大小固定为128个字节,并且在首部加上Chunk Header就组成了一块完整的消息分块。一个大小为307个字节的消息分块,被分割成128个字节进行传输。

                (图1:这个是一块完整的消息分块,默认307个字节)

           (图二:这个是把307个字节的消息块,分割成每个128个字节)

(图三:是剩余51个字节的消息块)

抓包软件看。

4.12FFMPEG推流器讲解(五):FFMPEG时间戳、时间基、时间转换

介绍FFMPEG的时间概念,包括时间基时间戳、时间转换等知识点。这些知识点对于我们了解推流至关重要,因为音视频合成本质上是各种时间转换的过程。

  • FFMPEG 时间基、 时间戳的讲解:

2.1. 时间基(time_base):时间基也称之为时间基准,它代表的是每个刻度是多少秒。比方说:视频帧率是25FPS,那它的时间刻度是{1,25}。相当于1s内划分出25个等分,也就是每隔1/25秒后显示一帧视频数据。具体的如下图所示:

在FFMPEG中时间基准都是用AVRational结构体来表示:

num它是numerator的缩小,代表的是分子

den它是denominator的缩小,代表的是分母

在视频时间基都是以帧率为单位,比方说25帧。FFMPEG就以AVRational video_timebase = {1, 25}来表示。

在音频时间基都是以采样率为单位,比方说音频采样率是48000HZ。FFMPEG就以AVRational audio_timebase = {1, 48000}来表示。

对于封装格式来说:flv 封装格式的 time_base 为{1,1000},ts 封装格式的 time_base 为{1,90000}

从上图ffplay的信息我们可以看到有很多关于时间基的信息:

tbr表示帧率,该帧率是一个基准,通常来说tbr和fps是一致的

tbn表示视频流timebase(时间基),比方说:TS格式的数据timebase是90000,flv格式的视频流timebase为1000

tbc表示视频流codec timebase,这个值一般为帧率的两倍。比方说:帧率是25fps,则tbc是50

2.2. 时间戳(PTS、DTS):

首先时间戳它指的是在时间轴里面占了多少个格子,时间戳的单位不是具体的秒数,而是时间刻度。只有当时间基和时间戳结合在一起的时候,才能够真正表达出来时间是多少。

比方说:

有一把尺子pts = 25个刻度,time_base = {1,25} 每一个刻度是1/25厘米

所以这把尺子的长度 = pts * time_base = 25* 1/25= 1厘米

PTS全称是Presentation Time Stamp(显示时间戳),它主要的作用是度量解码后的视频帧什么时候显示出来。

视频PTS计算:n为第n帧视频帧,timebase是{1,framerate},fps是framerate

pts = n *(( 1 / timebase) / fps):

pts = pts++;

举例子:n = 1, pts = 1

        n = 2, pts = 2

        n =3, pts = 3

音频PTS计算:n为第n帧音频帧,nb_samples指的是采样个数(AAC默认1024),timebase是{1,samplerate},samplerate是采样率

Samplerate = 48000, nb_sample=1024, timebase = {1,48000}

num_pkt = samplerate/nb_samples

pts = n * ( ( 1/ timebase) / num_pkt)

pts = pts+1024

举例子:n = 1, pts = 1024

        n = 2, pts = 2048

        n = 3, pts = 3072

2.3. DTS表示的是压缩解码的时间戳,在没有B帧的情况下PTS 等于 DTS。假设编码的里面引入了B帧,则还要计算B帧的时间。

没有B帧:dts = pts

存在B帧:dts = pts + b_time

  • 时间转换的原理:

在FFMPEG中由于不同的复合流,时间基是不同的,比方说:flv的时间基time_base= {1,1000},假设一个视频time_base = {1,25},我们需要合成mpegts文件,它就需要把time_base = {1,25}占的格子转换成time_base = {1,1000}占的格子。

在FFMPEG中用以下的API进行时间基转换:

void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);

第一个参数:AVPacket结构体指针

第二个参数:源时间基

第三个参数:目的时间基

上面这个api的用法是,把AVPacket的时间基tb_src转换成时间基tb_dst。下面我们用H264和AAC时间基TS转换的例子来说明这个转换时间基的用法:

视频H264时间基转换成MPEGTS时间基:

**DST_VIDEO_PTS = VIDEO_PTS * VIDEO_TIME_BASE / DST_TIME_BASE

H264 {1,25}                                              flv{1,1000}

pts = 1                                                  pts = 40

pts = 2              av_packet_rescale_ts                  pts = 80

pts = 3                                                  pts = 120

pts = 4                                                  pts = 160

    

音频AAC时间基转换成FLV时间基:

**DST_AUDIO_PTS = AUDIO_PTS * AUDIO_TIME_BASE / DST_TIME_BASE

AAC {1,48000}                                           FLV{1,1000}

pts =1024                                               pts ~= 21.3

pts =2048           av_packet_rescale_ts                  pts ~= 42.6

pts =3072                                               pts ~= 64

pts =4096                                               pts ~= 85.3

从上述推导的结果可以看出来,如果使用av_packet_rescale_ts的API对视频时间基进行转换,实际上是使用DST_VIDEO_PTS = VIDEO_PTS * VIDEO_TIME_BASE / DST_TIME_BASE去计算推流的视频时间戳。

同理用av_packet_rescale_ts对音频时间基进行转换,实际上是使用DST_AUDIO_PTS = AUDIO_PTS * AUDIO_TIME_BASE / DST_TIME_BASE去计算我们真实推流的音频时间戳。

4.13高分辨率编码码流推送流媒体

流程:初始化RKMEDIA_FFMPEG_CONFIG结构体,调用init_rkmedia_ffmpeg_context设置1920*1080推流器,创建high_video_push_thread线程,从HIGH_VIDEO_QUEUE队列获取每一帧视频数据,把每一帧的AVPacket的PTS进行计算和时间基转换,利用FFMPEG的API推送每一帧视频数据到流媒体服务器。

1920*1080的high_queue队列里的视频数据----要把视频数据赋值到AVPacet结构体(FFMPEG利用这个结构体推流)------把AVPacket的PTS(时间戳)进行累加,进行时间基转换-----每一帧数据推送到流媒体服务器。

rv1126:视频缓冲区数据--------avpacket:buffer

              视频长度-----------------                   size

注意:avpacket中的buf是不能直接赋值的。

video_data_packet_t的buffer size---------AVBufferRef->data--------pkt->data=AVBufferRef->data

关键帧标识符必须设置

h264:pst1               pst2            pst3        pst4      -------------flv:pts40        pts80     

        avpacket1   avpacket2  avpacket3  avpacket4                   avpacket1   avpacket2

4.14:低分辨率(1280 * 720)编码码流推送流媒体

如何通过低分辨率队列的每一帧数据,并且通过FFMPEG推流器传输到低码流分辨率流媒体服务器,本章节代码在rkmedia_assignment_manage.cpp和rkmedia_data_process.cpp里面。低分辨率编码码流推送的过程和高分辨率编码码流的推送的过程基本上一致,唯一的区别在于分辨率的设置。

  • 低分辨率编码码流推流的流程

上面是低分辨率推流的过程,总共分成6个步骤。分别是初始化RKMEDIA_FFMPEG_CONFIG结构体、调用init_rkmedia_ffmpeg_context设置1280* 720推流器、创建low_video_push_thread线程、从LOW_VIDEO_QUEUE队列获取每一帧视频数据 、每一帧AVPacket计算PTS并进行时间基转换、利用FFMPEG的API推送每一帧视频数据到流媒体服务器,下面我们来具体看每一个步骤的实现过程:

2.1. 初始化RKMEDIA_FFMPEG_CONFIG结构体

我们来看看RKMEDIA_FFMPEG_CONFIG的成员变量

2.2.1. width推流器的width,width和rv1126编码器的width一致

2.2.2. height推流器的height,height和rv1126编码器的height一致

2.2.3. config_idconfig_id,暂时没用到

2.2.4. protocol_type流媒体的类型

2.2.5.network_addr流媒体地址

2.2.6.video_codec视频编码器ID
2.2.7.audio_codec音频编码器ID

2.2.8.video_stream自定义VIDEO的STREAM结构体配置

2.2.9.audio_stream自定义AUDIO的STREAM结构体配置


上面是高分辨率rkmedia_ffmpeg_config的设置

2.2. 调用init_rkmedia_ffmpeg_context来初始化1280* 720推流器

init_rkmedia_ffmpeg_context是初始化rkmedia_ffmpeg_config的设置,关于这个函数的内容在之前的课程已经说了。这里不做过多的介绍。

2.3. 创建low_video_push_thread线程

Low_video_push_thread最主要作用是在LOW_VIDEO_QUEUE队列获取每一帧1280* 720的H264编码视频流,然后再把每一帧H264的码流数据先赋值到AVPacket,再调用FFMPEG的API把视频流传输到流媒体服务器。

2.4. LOW_VIDEO_QUEUE获取每一帧H264数据码流并且赋值到AVPacket

上面的代码是从LOW_VIDEO_QUEUE队列里面取出每一帧1280* 720的H264数据,并且赋值到AVPacket的过程。整个函数封装到deal_low_video_packet里面。在deal_high_video_packet主要是实现从LOW_VIDEO_QUEUE队列获取每一帧数据并赋值到AVPacket的具体实现过程,具体如上图:

这里面有几个比较核心的地方:video_data_packet的视频数据包赋值到AVPacket,这里要赋值两部分:一部分是AVPacket缓冲区数据的赋值,另外一个是AVPacket的长度赋值。

2.4.1. 我们先来看看AVPacket缓冲区的赋值:首先用av_buffer_realloc分配每一个缓冲区数据。要注意的是AVPacket中缓冲区的buf是不能直接赋值的,如: memcpy(pkt->data, video_data_packet->buffer, video_data_packet->frame_size)否则程序就会出现core_dump情况。我们需要先把video_data_packet_t的视频数据(video_data_packet->buffer)先拷贝到pkt->buf->data,然后再把pkt->buf->data的数据赋值到pkt->data。

2.4.2. AVPacket缓冲区长度的赋值:把video_data_packet的video_frame_size长度直接赋值给AVPacket的pkt->size。

2.4.3. AVPacket关键帧标识符的赋值:添加了这个标识符后,每个AVPacket中都进行关键帧设置,这个标识符必须要加,否则播放器则无法正常解码出视频。

2.5. 每一帧AVPacket计算PTS时间戳

根据AVPacket的数据去计算视频的PTS,若AVPacket的数据不为空。则让视频pts = ost->next_timestamp++(关于videoPTS计算,上一节课已经讲了)

把视频PTS进行时间基的转换,调用av_packet_rescale_ts把采集的视频时间基转换成复合流的时间基。

2.6. 把每一帧视频数据传输到流媒体服务器

时间基转换完成之后,就把视频数据写入到复合流文件里面,调用的API是av_interleaved_write_frame (注意:复合流文件可以是本地文件也可以是流媒体地址)。

4.15:队列(上):C++队列的使用

  • 队列的介绍:

队列是C/C++中最基础的数据结构之一,队列本质上是一种线性表。它遵循着先进先出(fifo)的特点,在队列中一般在队尾插入,队头出队。这就相当于排队一样,刚入队的人需要排在队尾(rear),每次出队的都是在队首(front)。在实际开发中队列发挥着巨大的作用,比方说多线程数据传输、缓存数据的存储、中间件的设计等等。

从上面这张图我们可以看到,队尾入队了三个元素分别是1,2,3。1号数据最早入队、2号数据第二入队、3号数据最后入队。出队的时候,1号最早出队(pop1)、2号排在1号数据后面(pop2)、3号最后出队(pop3)。所以使用队列的时候,我们可以保证数据的顺序不会出现乱序的错误。

  • 队列的用处:

队列常用于在多线程数据传输、数据解耦、缓存数据等方面。由于在大型的项目开发中,往往有许多线程同时运作。此时,许多线程之间需要进行数据的传递,所以此时我们就需要通过队列作为一条桥梁把数据从一个线程送到另外一个线程里面(如下图一就是队列在两个线程之间的通信)。线程一把数据按照顺序把数据包存储到Queue上、线程二、三也按照顺序从队列拿到数据。

                               (图一)

除了线程之间通信之外,队列还常用于数据量缓存方面。比方说,在音视频解码的时候,音视频数据会大量传入解码端。假设此时没有一个缓冲的时间,解码端可能会因为处理速度的问题,导致解码视频的时候会出现花屏、卡顿等问题。所以,此时我们就需要用队列进行缓冲,使其传输速度降下来,那解码端的解码压力就会大大降下来,此时解码出来的画面质量就会高很多,具体的流程如图二。

                                  (图二)

C++ STL队列的用法:

C++库已经提供了一套队列的api方便开发者进行开发,这样我们就不用重新再新造轮子去实现队列。下面我们就来看看我们用stl queue去实现队列:

3.1. queue的初始化:

#include <queue>

std::queue<object> object_queue;

初始化stl的queue,需要做两步第一步要包含<queue>头文件,#include<queue>; 第二步声明queue,std::queue<object> object_queue。这里的<object>里面的object是任意类型的数据,也包括结构体的数据。

3.2.queue的操作api:

front()返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。

back()返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。

push(const T& obj)在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。

push(T&& obj)以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。

pop()删除 queue 中的第一个元素。

size()返回 queue 中元素的个数。

empty()如果 queue 中没有元素的话,返回 true。

emplace()用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。

swap(queue<T> &other_q)将当前 queue 中的元素和参数 queue 中的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap() 来完成同样的操作。

3.3.queue的demo

上面这个是一个简单的stl queue操作,先入队6个元素(0-5)。然后再连续出队pop,这里总共出队了4次,此时元素0 1 2 3全部出队并删除,所以打印front的元素是4。

4.16:队列(下):C++多线程队列使用

  • 多线程队列的框图:

上图是同一个典型的多线程入队,出队的过程。这里需要创建两个线程,一个是入队线程、一个是出队线程。入队线程主要是通过push的api向Queue的队尾插入数据,插入数据的同时通过pthread_cond_broadcast通知出队线程取出数据。此时出队线程正在等待入队线程的唤醒(pthread_cond_wait),若收到唤醒通知则让队列数据出队。

二·.Linux多线程的基本API:

2.1. pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

第一个传入参数:pthread_mutex_t结构体指针

功能:这个是互斥锁加锁功能,就是每次线程调用的时候都会把锁加上,使其保证访问数据的原子性,直到解锁为止。

2.2. pthread_mutex_unlock:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

第一个传入参数:pthread_mutex_t结构体指针

功能:这个是互斥锁解锁功能,就是每次线程访问完资源的时候都会把锁解锁。

2.3. pthread_cond_broadcast

int pthread_cond_broadcast(pthread_cond_t *cond)

传入参数:pthread_cond_t的结构体指针

功能:唤醒所有正在pthread_cond_wait(线程等待)的线程

2.4. pthread_cond_wait:

int pthread_cond_wait (pthread_cond_t *__restrict __cond , pthread_mutex_t *__restrict __mutex)

第一个参数:pthread_cond_t的结构体指针

第二个参数:pthread_mutex_t结构体指针

功能:线程等待并挂起,若被唤醒了,则直接跳出挂起状态。

  • 推流项目中视频队列的实现:

这张图是视频队列实现的过程,VIDEO_QUEUE是一个类。这个类里面,封装了添加视频队列(putVideoPacketQueue)、获取视频队列数据(getVideoPacketQueue)、获取视频队列长度(getVideoQueueSize)。

3.1. VIDEO_QUEUE构造器

这里创建一个VIDEO_QUEUE的C++的构造器,C++构造器主要初始化了线程的量。包括:线程锁的初始化(pthread_mutex_init)、线程条件变量的初始化(pthread_cond_init)。

3.2. putVideoPacketQueue的讲解:

putVideoPacketQueue主要是video_data_packet_t入队的过程,入队前需要加锁pthread_mutex_lock。然后进行入队操作video_packet_queue.push(video_packet),入队完成之后再通知出队线程取出队列数据pthread_cond_broadcast,最后解锁pthread_mutex_unlock

3.3. getVideoPacketQueue的讲解:

getVideoPacketQueue主要是video_data_packet_t入队的过程,入队前需要加锁pthread_mutex_lock。然后判断视频队列是否有数据(video_packet_queue.size()==0)。若没有数据,则用pthread_cond_wait去等待线程被唤醒。若队列有数据则唤醒的此线程,则直接从队列取出数据。这里取出数据分两步:第一步,先把队列移动到最前面video_packet_queue.front()。第二步,video_packet_queue.pop出队并删除数据。

3.4. getVideoQueueSize的讲解:

getVideoQueueSize主要是获取当前队列的长度,获取长度的步骤跟上面也差不多。

首先,pthread_mutex_lock加锁,然后通过count = video_packet_queue.size(),获取队列的数量。然后pthread_mutex_unlock解锁。

项目运行:

编译好的可执行文件放入板子中,/userdata中,

Please Input ./rv1126_ffmpeg_main high_stream_type high_url_address low_stream_type low_url_address. Notice URL_TYPE: 0-->FLV  1-->TS
./rv1126_ffmpeg_main 0 rtmp://192.168.100.80:1935/live/01 0 rtmp://192.168.100.80:1935/live/02

其中的ip是板子和pc连接的ip,流是从这个通道发出来的,nginx到rtmp服务器也只有从这能连上。

执行上面的运行。

流媒体服务器开启:命令行打开nginx软件目录,start .\nginx.exe

然后运行程序

看效果:点开ffmpeg的bin文件

终端打开:ffmpeg目录下的bin,打开两个一个高分辨率,一个低分辨率

执行.\ffplay.exe -x 400 -y 400 rtmp://192.168.100.80:1935/live/01

.\ffplay.exe -x 400 -y 400 rtmp://192.168.100.80:1935/live/02

源码:

ffmpeg_audio_queue.cpp

#include "ffmpeg_audio_queue.h"

//AUDIO队列的构造器,包含mutex的初始化和条件变量初始化
AUDIO_QUEUE::AUDIO_QUEUE()
{
    pthread_mutex_init(&audioMutex, NULL);//mutex的初始化
    pthread_cond_init(&audioCond, NULL);//条件变量初始化
}

//AUDIO队列的析构函数,锁的销毁和条件变量的销毁
AUDIO_QUEUE ::~AUDIO_QUEUE()
{
    pthread_mutex_destroy(&audioMutex);
    pthread_cond_destroy(&audioCond);
}

//AUDIO_QUEUE的插入音频队列操作
int AUDIO_QUEUE::putAudioPacketQueue(audio_data_packet *audio_packet)
{
    pthread_mutex_lock(&audioMutex);//上音频锁
    audio_packet_queue.push(audio_packet);//向音频队列插入audio_data_packet包
    pthread_cond_broadcast(&audioCond);//唤醒视音频队列
    pthread_mutex_unlock(&audioMutex);//解音频锁
    return 0;
}

//AUDIO_QUEUE取出音频包
audio_data_packet *AUDIO_QUEUE::getAudioPacketQueue()
{
    pthread_mutex_lock(&audioMutex);//上音频锁
    while (audio_packet_queue.size() == 0)
    {
        pthread_cond_wait(&audioCond, &audioMutex);//当音频队列没有数据的时候,等待被唤醒
    }
    audio_data_packet *item = audio_packet_queue.front();//把音频数据包移到最前面
    audio_packet_queue.pop();//pop取出音频数据并删除
    pthread_mutex_unlock(&audioMutex);//解音频锁
    return item;
}

//AUDIO_QUEUE音频队列长度
int AUDIO_QUEUE::getAudioPacketQueueSize()
{
    unsigned int count = 0;
    pthread_mutex_lock(&audioMutex);//上音频锁
    count = audio_packet_queue.size();//获取音频队列长度
    pthread_mutex_unlock(&audioMutex);//解音频锁
    return count;
}

ffmpeg_video_queue.cpp

#include "ffmpeg_video_queue.h"

//VIDEO队列的构造器,包含mutex的初始化和条件变量初始化
VIDEO_QUEUE::VIDEO_QUEUE()
{
    pthread_mutex_init(&videoMutex, NULL);//mutex的初始化
    pthread_cond_init(&videoCond, NULL);//条件变量初始化
}

 //VIDEO队列的析构函数,锁的销毁和条件变量的销毁
VIDEO_QUEUE ::~VIDEO_QUEUE()
{
    pthread_mutex_destroy(&videoMutex);//锁的销毁
    pthread_cond_destroy(&videoCond);//条件变量的销毁
}


//VIDEO_QUEUE的插入视频队列操作
int VIDEO_QUEUE::putVideoPacketQueue(video_data_packet_t *video_packet)
{
    pthread_mutex_lock(&videoMutex); //上视频锁
    video_packet_queue.push(video_packet);//向视频队列插入video_data_packet_t包
    pthread_cond_broadcast(&videoCond);//唤醒视频队列
    pthread_mutex_unlock(&videoMutex);//解视频锁
    return 0;
}

//VIDEO_QUEUE取出视频包
video_data_packet_t *VIDEO_QUEUE::getVideoPacketQueue()
{
    pthread_mutex_lock(&videoMutex);//上视频锁
    while (video_packet_queue.size() == 0)
    {
        pthread_cond_wait(&videoCond, &videoMutex);  //当视频队列没有数据的时候,等待被唤醒
    }
    video_data_packet_t *item = video_packet_queue.front();//把视频数据包移到最前面
    video_packet_queue.pop();//pop取出视频数据并删除
    pthread_mutex_unlock(&videoMutex);//解视频锁
    return item;
}

//VIDEO_QUEUE视频队列长度
int VIDEO_QUEUE::getVideoQueueSize()
{
    unsigned int count = 0;
    pthread_mutex_lock(&videoMutex);//上视频锁
    count = video_packet_queue.size();//获取视频队列长度
    pthread_mutex_unlock(&videoMutex);//解视频锁
    return count;
}

rkmedia_assignment_manage.cpp

#include "rkmedia_assignment_manage.h"
#include "rkmedia_data_process.h"
#include "rkmedia_ffmpeg_config.h"
#include "rkmedia_module.h"

#include "rkmedia_ffmpeg_config.h"
#include "rkmedia_container.h"

int init_rv1126_first_assignment(int protocol_type, char * network_address, int low_url_type, char *low_url_address)
{
    int ret;
    RKMEDIA_FFMPEG_CONFIG *ffmpeg_config = (RKMEDIA_FFMPEG_CONFIG *)malloc(sizeof(RKMEDIA_FFMPEG_CONFIG));
    if (ffmpeg_config == NULL)
    {
        printf("malloc ffmpeg_config failed\n");
    }
    ffmpeg_config->width = 1920;
    ffmpeg_config->height = 1080;
    ffmpeg_config->config_id = 0;
    ffmpeg_config->protocol_type = protocol_type;
    ffmpeg_config->video_codec = AV_CODEC_ID_H264;
    ffmpeg_config->audio_codec = AV_CODEC_ID_AAC;
    memcpy(ffmpeg_config->network_addr, network_address, strlen(network_address));
    //初始化ffmpeg输出模块
    init_rkmedia_ffmpeg_context(ffmpeg_config);

    RKMEDIA_FFMPEG_CONFIG *low_ffmpeg_config = (RKMEDIA_FFMPEG_CONFIG *)malloc(sizeof(RKMEDIA_FFMPEG_CONFIG));
    if (ffmpeg_config == NULL)
    {
        printf("malloc ffmpeg_config failed\n");
    }

    //char  * low_network_address = "rtmp://192.168.1.66:1935/live/02";
    low_ffmpeg_config->width = 1280;
    low_ffmpeg_config->height = 720;
    low_ffmpeg_config->config_id = 1;
    low_ffmpeg_config->protocol_type = protocol_type;
    low_ffmpeg_config->video_codec = AV_CODEC_ID_H264;
    low_ffmpeg_config->audio_codec = AV_CODEC_ID_AAC;
    //memcpy(low_ffmpeg_config->network_addr, low_network_address, strlen(low_network_address));
    memcpy(low_ffmpeg_config->network_addr, low_url_address, strlen(low_url_address));

    init_rkmedia_ffmpeg_context(low_ffmpeg_config);

    printf("Bind Before...\n");

    MPP_CHN_S vi_channel;
    MPP_CHN_S venc_channel;

    MPP_CHN_S rga_channel;
    MPP_CHN_S low_venc_channel;
    
    //从VI容器里面获取VI_ID
    RV1126_VI_CONTAINTER vi_container;
    get_vi_container(0, &vi_container);

    //从VENC容器里面获取VENC_ID
    RV1126_VENC_CONTAINER venc_container;
    get_venc_container(0, &venc_container);

    vi_channel.enModId = RK_ID_VI;  //VI模块ID
    vi_channel.s32ChnId = vi_container.vi_id;//VI通道ID
    
    venc_channel.enModId = RK_ID_VENC;//VENC模块ID
    venc_channel.s32ChnId = venc_container.venc_id;//VENC通道ID

    //绑定VI和VENC节点
    ret = RK_MPI_SYS_Bind(&vi_channel, &venc_channel);
    if (ret != 0)
    {
        printf("bind venc error\n");
        return -1;
    }
    else
    {
        printf("bind venc success\n");
    }

    RV1126_VENC_CONTAINER low_venc_container;
    get_venc_container(1, &low_venc_container);
    rga_channel.enModId = RK_ID_RGA;
    rga_channel.s32ChnId = 0;
    ret = RK_MPI_SYS_Bind(&vi_channel, &rga_channel);
    if (ret != 0)
    {
        printf("vi bind rga error\n");
        return -1;
    }
    else
    {
        printf("vi bind rga success\n");
    }

    pthread_t pid;
    //VENC线程的参数
    VENC_PROC_PARAM *venc_arg_params = (VENC_PROC_PARAM *)malloc(sizeof(VENC_PROC_PARAM));
    if (venc_arg_params == NULL)
    {
        printf("malloc venc arg error\n");
        free(venc_arg_params);
    }

    venc_arg_params->vencId = venc_channel.s32ChnId;
    //创建VENC线程,获取摄像头编码数据
    ret = pthread_create(&pid, NULL, camera_venc_thread, (void *)venc_arg_params);
    if (ret != 0)
    {
        printf("create camera_venc_thread failed\n");
    }

    ret = pthread_create(&pid, NULL, get_rga_thread, NULL);
    if(ret != 0)
    {
        printf("create get_rga_thread failed\n");
    }

    //VENC线程的参数
    VENC_PROC_PARAM *low_venc_arg_params = (VENC_PROC_PARAM *)malloc(sizeof(VENC_PROC_PARAM));
    if (venc_arg_params == NULL)
    {
        printf("malloc venc arg error\n");
        free(venc_arg_params);
    }

    low_venc_arg_params->vencId = low_venc_channel.s32ChnId;
    //创建VENC线程,获取摄像头编码数据
    ret = pthread_create(&pid, NULL, low_camera_venc_thread, (void *)low_venc_arg_params);
    if (ret != 0)
    {
        printf("create camera_venc_thread failed\n");
    }

    //创建HIGH_PUSH线程
    ret = pthread_create(&pid, NULL, high_video_push_thread, (void *)ffmpeg_config);
    if (ret != 0)
    {
        printf("push_server_thread error\n");
    }

    //创建LOW_PUSH线程
    ret = pthread_create(&pid, NULL, low_video_push_thread, (void *)low_ffmpeg_config);
    if (ret != 0)
    {
        printf("push_server_thread error\n");
    }

    return 0;
}

rkmedia_container.cpp

#include <pthread.h>
#include <string.h>
#include "rkmedia_container.h"

RV1126_ALL_CONTAINER all_containers;
pthread_mutex_t all_containers_mutex;

int init_all_container_function()
{
    pthread_mutex_init(&all_containers_mutex, NULL);
    memset(&all_containers, 0, sizeof(all_containers));

    return 0;
}

int set_vi_container(unsigned int index, RV1126_VI_CONTAINTER *vi_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    all_containers.vi_containers[index] = *vi_container;
    pthread_mutex_unlock(&all_containers_mutex);

    return 0;
}

int get_vi_container(unsigned int index, RV1126_VI_CONTAINTER *vi_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    *vi_container = all_containers.vi_containers[index];
    pthread_mutex_unlock(&all_containers_mutex);

    return 0;
}

int set_ai_container(unsigned int index, RV1126_AI_CONTAINTER *ai_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    all_containers.ai_containers[index] = *ai_container;
    pthread_mutex_unlock(&all_containers_mutex);

    return 0;
}

int get_ai_container(unsigned int index, RV1126_AI_CONTAINTER *ai_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    *ai_container = all_containers.ai_containers[index];
    pthread_mutex_unlock(&all_containers_mutex);

    return 0;
}


int set_venc_container(unsigned int index, RV1126_VENC_CONTAINER *venc_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    all_containers.venc_containers[index] = *venc_container;
    pthread_mutex_unlock(&all_containers_mutex);
    return 0;
}

int get_venc_container(unsigned int index, RV1126_VENC_CONTAINER *venc_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    *venc_container = all_containers.venc_containers[index];
    pthread_mutex_unlock(&all_containers_mutex);

    return 0;
}

int set_aenc_container(unsigned int index, RV1126_AENC_CONTAINER *aenc_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    all_containers.aenc_containers[index] = *aenc_container;
    pthread_mutex_unlock(&all_containers_mutex);
    return 0;
}

int get_aenc_container(unsigned int index, RV1126_AENC_CONTAINER *aenc_container)
{
    pthread_mutex_lock(&all_containers_mutex);
    *aenc_container = all_containers.aenc_containers[index];
    pthread_mutex_unlock(&all_containers_mutex);
    return 0;
}

rkmedia_data_process.cpp

#include "rkmedia_data_process.h"
#include "ffmpeg_video_queue.h"
#include "ffmpeg_audio_queue.h"
#include "rkmedia_module.h"
#include "rkmedia_ffmpeg_config.h"
#include "rkmedia_data_process.h"
#include "SDL.h"
#include "SDL_ttf.h"

extern VIDEO_QUEUE *high_video_queue;
extern VIDEO_QUEUE *low_video_queue;

static int get_align16_value(int input_value, int align)
{
    int handle_value = 0;
    if (align && (input_value % align))
        handle_value = (input_value / align + 1) * align;
    return handle_value;
}

// 从RV1126视频编码数据赋值到FFMPEG的Video AVPacket中
AVPacket *get_high_ffmpeg_video_avpacket(AVPacket *pkt)
{
    video_data_packet_t *video_data_packet = high_video_queue->getVideoPacketQueue(); // 从视频队列获取数据

    if (video_data_packet != NULL)
    {
        /*
     重新分配给定的缓冲区
   1.  如果入参的 AVBufferRef 为空,直接调用 av_realloc 分配一个新的缓存区,并调用 av_buffer_create 返回一个新的 AVBufferRef 结构;
   2.  如果入参的缓存区长度和入参 size 相等,直接返回 0;
   3.  如果对应的 AVBuffer 设置了 BUFFER_FLAG_REALLOCATABLE 标志,或者不可写,再或者 AVBufferRef data 字段指向的数据地址和 AVBuffer 的 data 地址不同,递归调用 av_buffer_realloc 分配一个新
的 buffer,并将 data 拷贝过去;
   4.  不满足上面的条件,直接调用 av_realloc 重新分配缓存区。
 */
        int ret = av_buffer_realloc(&pkt->buf, video_data_packet->video_frame_size + 70);
        if (ret < 0)
        {
            return NULL;
        }
        pkt->size = video_data_packet->video_frame_size;                                        // rv1126的视频长度赋值到AVPacket Size
        memcpy(pkt->buf->data, video_data_packet->buffer, video_data_packet->video_frame_size); // rv1126的视频数据赋值到AVPacket data
        pkt->data = pkt->buf->data;                                                             // 把pkt->buf->data赋值到pkt->data
        pkt->flags |= AV_PKT_FLAG_KEY;                                                          // 默认flags是AV_PKT_FLAG_KEY
        if (video_data_packet != NULL)
        {
            free(video_data_packet);
            video_data_packet = NULL;
        }

        return pkt;
    }
    else
    {
        return NULL;
    }
}


AVPacket *get_low_ffmpeg_video_avpacket(AVPacket *pkt)
{
    video_data_packet_t *video_data_packet = low_video_queue->getVideoPacketQueue(); // 从视频队列获取数据

    if (video_data_packet != NULL)
    {
        /*
     重新分配给定的缓冲区
   1.  如果入参的 AVBufferRef 为空,直接调用 av_realloc 分配一个新的缓存区,并调用 av_buffer_create 返回一个新的 AVBufferRef 结构;
   2.  如果入参的缓存区长度和入参 size 相等,直接返回 0;
   3.  如果对应的 AVBuffer 设置了 BUFFER_FLAG_REALLOCATABLE 标志,或者不可写,再或者 AVBufferRef data 字段指向的数据地址和 AVBuffer 的 data 地址不同,递归调用 av_buffer_realloc 分配一个新
的 buffer,并将 data 拷贝过去;
   4.  不满足上面的条件,直接调用 av_realloc 重新分配缓存区。
 */
        int ret = av_buffer_realloc(&pkt->buf, video_data_packet->video_frame_size + 70);
        if (ret < 0)
        {
            return NULL;
        }
        pkt->size = video_data_packet->video_frame_size;                                        // rv1126的视频长度赋值到AVPacket Size
        memcpy(pkt->buf->data, video_data_packet->buffer, video_data_packet->video_frame_size); // rv1126的视频数据赋值到AVPacket data
        pkt->data = pkt->buf->data;                                                             // 把pkt->buf->data赋值到pkt->data
        pkt->flags |= AV_PKT_FLAG_KEY;                                                          // 默认flags是AV_PKT_FLAG_KEY
        if (video_data_packet != NULL)
        {
            free(video_data_packet);
            video_data_packet = NULL;
        }

        return pkt;
    }
    else
    {
        return NULL;
    }
}



int write_ffmpeg_avpacket(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)
{
    /*将输出数据包时间戳值从编解码器重新调整为流时基 */
    av_packet_rescale_ts(pkt, *time_base, st->time_base);
    pkt->stream_index = st->index;

    return av_interleaved_write_frame(fmt_ctx, pkt);
}

int deal_high_video_avpacket(AVFormatContext *oc, OutputStream *ost)
{
    int ret;
    AVCodecContext *c = ost->enc;
    AVPacket *video_packet = get_high_ffmpeg_video_avpacket(ost->packet); // 从RV1126视频编码数据赋值到FFMPEG的Video AVPacket中
    if (video_packet != NULL)
    {
        video_packet->pts = ost->next_timestamp++; // VIDEO_PTS按照帧率进行累加
    }

    ret = write_ffmpeg_avpacket(oc, &c->time_base, ost->stream, video_packet); // 向复合流写入视频数据
    if (ret != 0)
    {
        printf("write video avpacket error");
        return -1;
    }

    return 0;
}

int deal_low_video_avpacket(AVFormatContext *oc, OutputStream *ost)
{
    int ret;
    AVCodecContext *c = ost->enc;
    AVPacket *video_packet = get_low_ffmpeg_video_avpacket(ost->packet); // 从RV1126视频编码数据赋值到FFMPEG的Video AVPacket中
    if (video_packet != NULL)
    {
        video_packet->pts = ost->next_timestamp++; // VIDEO_PTS按照帧率进行累加
    }

    ret = write_ffmpeg_avpacket(oc, &c->time_base, ost->stream, video_packet); // 向复合流写入视频数据
    if (ret != 0)
    {
        printf("write video avpacket error");
        return -1;
    }

    return 0;
}

#if 0
void *osd_venc_thread(void *args)
{
    pthread_detach(pthread_self());
    int ret;
    TTF_Font *ttf_font;
    char *pstr = "2019-11-21 15:40:29";
    SDL_Surface *text_surface;
    SDL_Surface *convert_text_surface;
    SDL_PixelFormat *pixel_format;

    // TTF模块的初始化
    ret = TTF_Init();
    if (ret < 0)
    {
        printf("TTF_Init Failed...\n");
    }

    // 打开TTF的字库
    ttf_font = TTF_OpenFont("./fzlth.ttf", 48);
    if (ttf_font == NULL)
    {
        printf("TTF_OpenFont Failed...\n");
    }

    // SDL_COLOR黑色,RGB(0,0,0)
    SDL_Color sdl_color;
    sdl_color.r = 0;
    sdl_color.g = 0;
    sdl_color.b = 0;
    text_surface = TTF_RenderText_Solid(ttf_font, pstr, sdl_color); ////渲染文字

    // ARGB_8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
    pixel_format->BitsPerPixel = 32;  // 每个像素所占的比特位数
    pixel_format->BytesPerPixel = 4;  // 每个像素所占的字节数
    pixel_format->Amask = 0XFF000000; // ARGB的A掩码,A位0xff
    pixel_format->Rmask = 0X00FF0000; // ARGB的R掩码,R位0xff
    pixel_format->Gmask = 0X0000FF00; // ARGB的G掩码,G位0xff
    pixel_format->Bmask = 0X000000FF; // ARGB的B掩码,B位0xff
    convert_text_surface = SDL_ConvertSurface(text_surface, pixel_format, 0);
    if (convert_text_surface == NULL)
    {
        printf("convert_text_surface failed...\n");
    }

    BITMAP_S bitmap;                                                                                                                         // Bitmap位图结构体
    bitmap.u32Width = get_align16_value(convert_text_surface->w, 16);                                                                        // Bitmap的宽度
    bitmap.u32Height = get_align16_value(convert_text_surface->h, 16);                                                                       // Bitmap的高度
    bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;                                                                                           ////像素格式ARGB8888
    bitmap.pData = malloc((bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel);                                             ////bitmap的data的分配大小
    memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) * pixel_format->BytesPerPixel); ////bitmap的data赋值

    OSD_REGION_INFO_S rgn_info;            // OSD_RGN_INFO结构体
    rgn_info.enRegionId = REGION_ID_0;     // rgn的区域ID
    rgn_info.u32Width = bitmap.u32Width;   // osd的长度
    rgn_info.u32Height = bitmap.u32Height; // osd的高度
    rgn_info.u32PosX = 128;                // Osd的X轴方向
    rgn_info.u32PosY = 128;                // Osd的Y轴方向
    rgn_info.u8Enable = 1;                 ////使能OSD模块,1是使能,0为禁止。
    rgn_info.u8Inverse = 0;                // 禁止翻转

    ret = RK_MPI_VENC_RGN_SetBitMap(0, &rgn_info, &bitmap); // 设置OSD位图
    if (ret)
    {
        printf("HIGI_RK_MPI_VENC_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("HIGI_RK_MPI_VENC_RGN_SetBitMap Success...\n");
    }

    return NULL;
}


void *low_osd_venc_thread(void *args)
{
    pthread_detach(pthread_self());
    int ret;
    TTF_Font *ttf_font;
    char *pstr = "2019-11-21 15:40:29";
    SDL_Surface *text_surface;
    SDL_Surface *convert_text_surface;
    SDL_PixelFormat *pixel_format;

    // TTF模块的初始化
    ret = TTF_Init();
    if (ret < 0)
    {
        printf("TTF_Init Failed...\n");
    }

    // 打开TTF的字库
    ttf_font = TTF_OpenFont("./fzlth.ttf", 48);
    if (ttf_font == NULL)
    {
        printf("TTF_OpenFont Failed...\n");
    }

    // SDL_COLOR黑色,RGB(0,0,0)
    SDL_Color sdl_color;
    sdl_color.r = 0;
    sdl_color.g = 0;
    sdl_color.b = 0;
    text_surface = TTF_RenderText_Solid(ttf_font, pstr, sdl_color); ////渲染文字

    // ARGB_8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
    pixel_format->BitsPerPixel = 32;  // 每个像素所占的比特位数
    pixel_format->BytesPerPixel = 4;  // 每个像素所占的字节数
    pixel_format->Amask = 0XFF000000; // ARGB的A掩码,A位0xff
    pixel_format->Rmask = 0X00FF0000; // ARGB的R掩码,R位0xff
    pixel_format->Gmask = 0X0000FF00; // ARGB的G掩码,G位0xff
    pixel_format->Bmask = 0X000000FF; // ARGB的B掩码,B位0xff
    convert_text_surface = SDL_ConvertSurface(text_surface, pixel_format, 0);
    if (convert_text_surface == NULL)
    {
        printf("convert_text_surface failed...\n");
    }

    BITMAP_S bitmap;                                                                                                                         // Bitmap位图结构体
    bitmap.u32Width = get_align16_value(convert_text_surface->w, 16);                                                                        // Bitmap的宽度
    bitmap.u32Height = get_align16_value(convert_text_surface->h, 16);                                                                       // Bitmap的高度
    bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;                                                                                           ////像素格式ARGB8888
    bitmap.pData = malloc((bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel);                                             ////bitmap的data的分配大小
    memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) * pixel_format->BytesPerPixel); ////bitmap的data赋值

    OSD_REGION_INFO_S rgn_info;            // OSD_RGN_INFO结构体
    rgn_info.enRegionId = REGION_ID_0;     // rgn的区域ID
    rgn_info.u32Width = bitmap.u32Width;   // osd的长度
    rgn_info.u32Height = bitmap.u32Height; // osd的高度
    rgn_info.u32PosX = 256;                // Osd的X轴方向
    rgn_info.u32PosY = 256;                // Osd的Y轴方向
    rgn_info.u8Enable = 1;                 ////使能OSD模块,1是使能,0为禁止。
    rgn_info.u8Inverse = 0;                // 禁止翻转

    ret = RK_MPI_VENC_RGN_SetBitMap(0, &rgn_info, &bitmap); // 设置OSD位图
    if (ret)
    {
        printf("HIGI_RK_MPI_VENC_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("HIGI_RK_MPI_VENC_RGN_SetBitMap Success...\n");
    }

    ret =  RK_MPI_RGA_RGN_SetBitMap(0, &rgn_info, &bitmap);
    if (ret)
    {
        printf("RK_MPI_RGA_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("RK_MPI_RGA_RGN_SetBitMap Success...\n");
    }

    return NULL;
}
#endif


void *camera_venc_thread(void *args)
{
    pthread_detach(pthread_self());
    MEDIA_BUFFER mb = NULL;

    VENC_PROC_PARAM venc_arg = *(VENC_PROC_PARAM *)args;
    free(args);

    printf("video_venc_thread...\n");

    while (1)
    {
        // 从指定通道中获取VENC数据
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, venc_arg.vencId, -1);
        if (!mb)
        {
            printf("high_get venc media buffer error\n");
            break;
        }

        // int naluType = RK_MPI_MB_GetFlag(mb);
        // 分配video_data_packet_t结构体
        video_data_packet_t *video_data_packet = (video_data_packet_t *)malloc(sizeof(video_data_packet_t));
        // 把VENC视频缓冲区数据传输到video_data_packet的buffer中
        memcpy(video_data_packet->buffer, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb));
        // 把VENC的长度赋值给video_data_packet的video_frame_size中
        video_data_packet->video_frame_size = RK_MPI_MB_GetSize(mb);
        // video_data_packet->frame_flag = naluType;
        // 入到视频压缩队列
        high_video_queue->putVideoPacketQueue(video_data_packet);
        // printf("#naluType = %d \n", naluType);
        // 释放VENC资源
        RK_MPI_MB_ReleaseBuffer(mb);
    }

    MPP_CHN_S vi_channel;
    MPP_CHN_S venc_channel;

    vi_channel.enModId = RK_ID_VI;
    vi_channel.s32ChnId = 0;

    venc_channel.enModId = RK_ID_VENC;
    venc_channel.s32ChnId = venc_arg.vencId;

    int ret;
    ret = RK_MPI_SYS_UnBind(&vi_channel, &venc_channel);
    if (ret != 0)
    {
        printf("VI UnBind failed \n");
    }
    else
    {
        printf("Vi UnBind success\n");
    }

    ret = RK_MPI_VENC_DestroyChn(0);
    if (ret)
    {
        printf("Destroy Venc error! ret=%d\n", ret);
        return 0;
    }
    // destroy vi
    ret = RK_MPI_VI_DisableChn(0, 0);
    if (ret)
    {
        printf("Disable Chn Venc error! ret=%d\n", ret);
        return 0;
    }

    return NULL;
}

void * get_rga_thread(void * args)
{
    MEDIA_BUFFER mb = NULL;

    while (1)
    {
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA, 0 , -1);  //获取RGA的数据
        if(!mb)
        {
            break;
        }

        RK_MPI_SYS_SendMediaBuffer(RK_ID_VENC, 1, mb); //
        RK_MPI_MB_ReleaseBuffer(mb);
    }

    return NULL;
}


void *low_camera_venc_thread(void *args)
{
    pthread_detach(pthread_self());
    MEDIA_BUFFER mb = NULL;

    VENC_PROC_PARAM venc_arg = *(VENC_PROC_PARAM *)args;
    free(args);

    printf("low_video_venc_thread...\n");

    while (1)
    {
        // 从指定通道中获取VENC数据
        //mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, venc_arg.vencId, -1);
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, 1, -1);
        if (!mb)
        {
            printf("low_venc break....\n");
            break;
        }

        // int naluType = RK_MPI_MB_GetFlag(mb);
        // 分配video_data_packet_t结构体
        video_data_packet_t *video_data_packet = (video_data_packet_t *)malloc(sizeof(video_data_packet_t));
        // 把VENC视频缓冲区数据传输到video_data_packet的buffer中
        memcpy(video_data_packet->buffer, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb));
        // 把VENC的长度赋值给video_data_packet的video_frame_size中
        video_data_packet->video_frame_size = RK_MPI_MB_GetSize(mb);
        // video_data_packet->frame_flag = naluType;
        // 入到视频压缩队列
        low_video_queue->putVideoPacketQueue(video_data_packet);
        // printf("#naluType = %d \n", naluType);
        // 释放VENC资源
        RK_MPI_MB_ReleaseBuffer(mb);
    }

    return NULL;
}

// 音视频合成推流线程
void *high_video_push_thread(void *args)
{
    pthread_detach(pthread_self());
    RKMEDIA_FFMPEG_CONFIG ffmpeg_config = *(RKMEDIA_FFMPEG_CONFIG *)args;
    free(args);
    AVOutputFormat *fmt = NULL;
    int ret;

    while (1)
    {
        ret = deal_high_video_avpacket(ffmpeg_config.oc, &ffmpeg_config.video_stream); // 处理FFMPEG视频数据
        if (ret == -1)
        {
            printf("deal_video_avpacket error\n");
            break;
        }
    }

    av_write_trailer(ffmpeg_config.oc);                         // 写入AVFormatContext的尾巴
    free_stream(ffmpeg_config.oc, &ffmpeg_config.video_stream); // 释放VIDEO_STREAM的资源
    free_stream(ffmpeg_config.oc, &ffmpeg_config.audio_stream); // 释放AUDIO_STREAM的资源
    avio_closep(&ffmpeg_config.oc->pb);                         // 释放AVIO资源
    avformat_free_context(ffmpeg_config.oc);                    // 释放AVFormatContext资源
    return NULL;
}

void *low_video_push_thread(void *args)
{
    pthread_detach(pthread_self());
    RKMEDIA_FFMPEG_CONFIG ffmpeg_config = *(RKMEDIA_FFMPEG_CONFIG *)args;
    free(args);
    AVOutputFormat *fmt = NULL;
    int ret;

    while (1)
    {
        ret = deal_low_video_avpacket(ffmpeg_config.oc, &ffmpeg_config.video_stream); // 处理FFMPEG视频数据
        if (ret == -1)
        {
            printf("deal_video_avpacket error\n");
            break;
        }
    }

    av_write_trailer(ffmpeg_config.oc);                         // 写入AVFormatContext的尾巴
    free_stream(ffmpeg_config.oc, &ffmpeg_config.video_stream); // 释放VIDEO_STREAM的资源
    free_stream(ffmpeg_config.oc, &ffmpeg_config.audio_stream); // 释放AUDIO_STREAM的资源
    avio_closep(&ffmpeg_config.oc->pb);                         // 释放AVIO资源
    avformat_free_context(ffmpeg_config.oc);                    // 释放AVFormatContext资源
    return NULL;
}

rkmedia_ffmpeg_config.cpp

#include "rkmedia_ffmpeg_config.h"

RKMEDIA_FFMPEG_CONFIG rkmedia_ffmpeg_configs[NETWORK_NUM];
pthread_mutex_t rkmedia_ffmpeg_config_mutex;

int init_rkmedia_ffmpeg_function()
{
    pthread_mutex_init(&rkmedia_ffmpeg_config_mutex, NULL);
    memset(rkmedia_ffmpeg_configs, 0, sizeof(rkmedia_ffmpeg_configs));
    return 0;
}

int set_rkmedia_ffmpeg_config(unsigned int config_id, RKMEDIA_FFMPEG_CONFIG *ffmpeg_config)
{
    pthread_mutex_lock(&rkmedia_ffmpeg_config_mutex);
    rkmedia_ffmpeg_configs[config_id] = *ffmpeg_config;
    pthread_mutex_unlock(&rkmedia_ffmpeg_config_mutex);
    return 0;
}

unsigned int get_rkmedia_ffmpeg_config(unsigned int config_id, RKMEDIA_FFMPEG_CONFIG *ffmpeg_config)
{
    pthread_mutex_lock(&rkmedia_ffmpeg_config_mutex);
    *ffmpeg_config = rkmedia_ffmpeg_configs[config_id];
    pthread_mutex_unlock(&rkmedia_ffmpeg_config_mutex);
    return 0;
}


int add_stream(OutputStream *ost, AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id, int width, int height)
{
    AVCodecContext *c = NULL;

    //创建输出码流的AVStream, AVStream是存储每一个视频/音频流信息的结构体
    ost->stream = avformat_new_stream(oc, NULL);
    if (!ost->stream)
    {
        printf("Can't not avformat_new_stream\n");
        return 0;
    }
    else
    {
        printf("Success avformat_new_stream\n");
    }

    //通过codecid找到CODEC
    *codec = avcodec_find_encoder(codec_id);
    if (!(*codec))
    {
        printf("Can't not find any encoder");
        return 0;
    }
    else
    {
        printf("Success find encoder");
    }

    //nb_streams 输入视频的AVStream 个数 就是当前有几种Stream,比如视频流、音频流、字幕,这样就算三种了,
    // s->nb_streams - 1其实对应的应是AVStream 中的 index
    ost->stream->id = oc->nb_streams - 1;
    //通过CODEC分配编码器上下文
    c = avcodec_alloc_context3(*codec);
    if (!c)
    {
        printf("Can't not allocate context3\n");
        return 0;
    }
    else
    {
        printf("Success allocate context3");
    }

    ost->enc = c;

    switch ((*codec)->type)
    {
    case AVMEDIA_TYPE_AUDIO:

        c->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; //FFMPEG采样格式
        c->bit_rate = 153600;  //FFMPEG音频码率
        c->sample_rate = 48000; //FFMPEG采样率
        c->channel_layout = AV_CH_LAYOUT_STEREO;//FFMPEG声道数2
        c->channels = av_get_channel_layout_nb_channels(c->channel_layout); //FFMPEG采样通道
        ost->stream->time_base = (AVRational){1, c->sample_rate};//FFMPEG音频时间基
        break;

    case AVMEDIA_TYPE_VIDEO:

        //c->codec_id = codec_id;
        c->bit_rate = width * height * 3; //FFMPEG视频码率
        //分辨率必须是2的倍数
        c->width = width; //FFMPEG视频宽度
        c->height = height;//FFMPEG视频高度

        ost->stream->r_frame_rate.den = 1; //FFMPEG帧率,分母
        ost->stream->r_frame_rate.num = 25;//FFMPEG帧率,分子
        ost->stream->time_base = (AVRational){1, 25};//Stream视频时间基,默认情况下等于帧率

        c->time_base = ost->stream->time_base; //编码器时间基
        c->gop_size = GOPSIZE; //GOPSIZE
        c->pix_fmt = AV_PIX_FMT_NV12;//图像格式

        break;

    default:
        break;
    }

    //在h264头部添加SPS,PPS
    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
    {
        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    return 0;
}

//使能video编码器
int open_video(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
{
    AVCodecContext *c = ost->enc;

    //打开编码器
    avcodec_open2(c, codec, NULL);

    //分配video avpacket包
    ost->packet = av_packet_alloc();

    /* 将AVCodecContext参数复制AVCodecParameters复用器 */
    avcodec_parameters_from_context(ost->stream->codecpar, c);
    return 0;
}

//使能audio编码器
int open_audio(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
{
    AVCodecContext *c = ost->enc;

    //打开编码器
    avcodec_open2(c, codec, NULL);

    //分配 audio avpacket包
    ost->packet = av_packet_alloc();

    /* 将AVCodecContext参数复制AVCodecParameters复用器 */
    avcodec_parameters_from_context(ost->stream->codecpar, c); 
    return 0;
}

void free_stream(AVFormatContext *oc, OutputStream *ost)
{
    avcodec_close(ost->enc); //就是需要关闭的编码器的AVCodecContext
    avcodec_free_context(&ost->enc); //释放AVCodecContext上下文

    av_buffer_unref(&(ost->packet->buf));//释放AVBufferRef

    av_packet_unref(ost->packet); //释放AVPacket
    av_packet_free(&ost->packet); //释放AVPacket
}


int init_rkmedia_ffmpeg_context(RKMEDIA_FFMPEG_CONFIG *ffmpeg_config)
{
    AVOutputFormat *fmt = NULL;
    AVCodec *audio_codec = NULL;
    AVCodec *video_codec = NULL;
    int ret = 0;

    //FLV_PROTOCOL is RTMP TCP
    if (ffmpeg_config->protocol_type == FLV_PROTOCOL)
    {
        //初始化一个FLV的AVFormatContext
        ret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "flv", ffmpeg_config->network_addr); 
        if (ret < 0)
        {
            return -1;
        }
    }
    //TS_PROTOCOL is SRT UDP RTSP
    else if (ffmpeg_config->protocol_type == TS_PROTOCOL)
    {
        //初始化一个TS的AVFormatContext
        ret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "mpegts", ffmpeg_config->network_addr);
        if (ret < 0)
        {
            return -1;
        }
    }
  
    fmt = ffmpeg_config->oc->oformat;
    /*指定编码器*/
    fmt->video_codec = ffmpeg_config->video_codec;
    fmt->audio_codec = ffmpeg_config->audio_codec;

    if (fmt->video_codec != AV_CODEC_ID_NONE)
    {
        ret = add_stream(&ffmpeg_config->video_stream, ffmpeg_config->oc, &video_codec, fmt->video_codec,ffmpeg_config->width,ffmpeg_config->height);
        if (ret < 0)
        {
            avcodec_free_context(&ffmpeg_config->video_stream.enc);
            free_stream(ffmpeg_config->oc, &ffmpeg_config->video_stream);
            avformat_free_context(ffmpeg_config->oc);
            return -1;
        }

        ret = open_video(ffmpeg_config->oc, video_codec, &ffmpeg_config->video_stream, NULL);
        if (ret < 0)
        {
            avformat_free_context(ffmpeg_config->oc);
        }
    }

#if 0
    if (fmt->audio_codec != AV_CODEC_ID_NONE)
    {
        ret = add_stream(&ffmpeg_config->audio_stream, ffmpeg_config->oc, &audio_codec, fmt->audio_codec);
        if (ret < 0)
        {
            avcodec_free_context(&ffmpeg_config->audio_stream.enc);
            free_stream(ffmpeg_config->oc, &ffmpeg_config->audio_stream);
            avformat_free_context(ffmpeg_config->oc);
            return -1;
        }

        ret = open_audio(ffmpeg_config->oc, audio_codec, &ffmpeg_config->audio_stream, NULL);
        if (ret < 0)
        {
            avformat_free_context(ffmpeg_config->oc);
        }
    }
#endif

    av_dump_format(ffmpeg_config->oc, 0, ffmpeg_config->network_addr, 1);

    if (!(fmt->flags & AVFMT_NOFILE))
    {
        //打开输出文件
        ret = avio_open(&ffmpeg_config->oc->pb, ffmpeg_config->network_addr, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            free_stream(ffmpeg_config->oc, &ffmpeg_config->video_stream);
            free_stream(ffmpeg_config->oc, &ffmpeg_config->audio_stream);
            avformat_free_context(ffmpeg_config->oc);
            return -1;
        }
    }

    avformat_write_header(ffmpeg_config->oc, NULL);
    return 0;
}


rkmedia_module.cpp

#include "rkmedia_module.h"

int rkmedia_function_init()
{
    RK_MPI_SYS_Init();
    return 0;
}

int rkmedia_vi_init(RV1126_VI_CONFIG *rv1126_vi_config)
{
    int ret;
    VI_CHN_ATTR_S vi_attr = rv1126_vi_config->attr;
    unsigned int id = rv1126_vi_config->id;
    //vi_attr.pcVideoNode = CMOS_DEVICE_NAME;//
    //初始化VI模块
    ret = RK_MPI_VI_SetChnAttr(CAMERA_ID, id, &vi_attr);
    //使能VI模块
    ret |= RK_MPI_VI_EnableChn(CAMERA_ID, id);
    if (ret != 0)
    {
        printf("create vi failed.....\n", ret);
        return -1;
    }
    return 0;
}

int rkmedia_ai_init(RV1126_AI_CONFIG *rv1126_ai_config)
{
    int ret;
    AI_CHN_ATTR_S ai_attr = rv1126_ai_config->attr;
    unsigned int id = rv1126_ai_config->id;
    //ai_attr.pcAudioNode = AUDIO_PATH;
    ret = RK_MPI_AI_SetChnAttr(id, &ai_attr); //设置AI属性
    ret = RK_MPI_AI_EnableChn(id); //使能AI模块
    if (ret != 0) 
    {
        printf("create ai failed...\n");
        return -1;
    }

    return 0;
}

//VENC的初始化
int rkmedia_venc_init(RV1126_VENC_CONFIG *rv1126_venc_config)
{
    int ret;
    VENC_CHN_ATTR_S venc_chn_attr = rv1126_venc_config->attr;
    unsigned int venc_id = rv1126_venc_config->id;
    ret = RK_MPI_VENC_CreateChn(rv1126_venc_config->id, &venc_chn_attr);
    if (ret != 0)
    {
        printf("create rv1126_venc_module failed\n");
        return -1;
    }
    else
    {
        printf("create rv1126_venc_module success\n");
    }

    return 0;
}

//初始化AENC参数
int rkmedia_aenc_init(RV1126_AENC_CONFIG *rv1126_aenc_config)
{
    int ret;
    AENC_CHN_ATTR_S aenc_chn_attr = rv1126_aenc_config->attr;
    unsigned int aenc_id = rv1126_aenc_config->id;

    //创建AENC层
    ret = RK_MPI_AENC_CreateChn(aenc_id, &aenc_chn_attr);
    if (ret != 0)
    {
        printf("create aenc failed: %d\n");
        return -1;
    }

    return 0;
}

rkmedia_module_function.cpp

#include "rkmedia_module_function.h"
#include "rkmedia_assignment_manage.h"
#include "rkmedia_config_public.h"
#include "rkmedia_module.h"
#include "rkmedia_container.h"
#include "SDL.h"
#include "SDL_ttf.h"
#include <sys/time.h>

#define FILE_IMAGE_LENGTH (64 * 1024)

static int get_align16_value(int input_value, int align)
{
    int handle_value = 0;
    if (align && (input_value % align))
        handle_value = (input_value / align + 1) * align;
    return handle_value;
}

int read_image(char *filename, char *buffer)
{
    if (filename == NULL || buffer == NULL)
        return -1;
    FILE *fp = fopen(filename, "rb"); // 以二进制模式读取该文件
    if (fp == NULL)
    {
        printf("fopen failed\n");
        return -2;
    }

    // 检测文件大小file
    fseek(fp, 0, SEEK_END);
    int length = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    int size = fread(buffer, 1, length, fp);
    if (size != length)
    {
        printf("fread failed:%d\n", size);
        return -3;
    }

    fclose(fp);
    return size;
}

static int get_cur_time_ms(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);                       // 使用gettimeofday获取当前系统时间
    return (tv.tv_sec * 1000 + tv.tv_usec / 1000); // 利用struct timeval结构体将时间转换为ms
}

int init_rkmedia_module_function()
{
    rkmedia_function_init();

    RV1126_VI_CONFIG rkmedia_vi_config;
    memset(&rkmedia_vi_config, 0, sizeof(rkmedia_vi_config));
    rkmedia_vi_config.id = 0;
    rkmedia_vi_config.attr.pcVideoNode = CMOS_DEVICE_NAME;   // VIDEO视频节点路径,
    rkmedia_vi_config.attr.u32BufCnt = 3;                    // VI捕获视频缓冲区计数,默认是3
    rkmedia_vi_config.attr.u32Width = 1920;                  // 视频输入的宽度,一般和CMOS摄像头或者外设的宽度一致
    rkmedia_vi_config.attr.u32Height = 1080;                 // 视频输入的高度,一般和CMOS摄像头或者外设的高度一致
    rkmedia_vi_config.attr.enPixFmt = IMAGE_TYPE_NV12;       // 视频输入的图像格式,默认是NV12(IMAGE_TYPE_NV12)
    rkmedia_vi_config.attr.enBufType = VI_CHN_BUF_TYPE_MMAP; // VI捕捉视频的类型
    rkmedia_vi_config.attr.enWorkMode = VI_WORK_MODE_NORMAL; // VI的工作模式,默认是NORMAL(VI_WORK_MODE_NORMAL)
    int ret = rkmedia_vi_init(&rkmedia_vi_config);           // 初始化VI工作
    if (ret != 0)
    {
        printf("vi init error\n");
    }
    else
    {
        printf("vi init success\n");
        RV1126_VI_CONTAINTER vi_container;
        vi_container.id = 0;
        vi_container.vi_id = rkmedia_vi_config.id;
        set_vi_container(0, &vi_container); // 设置VI容器
    }

    RV1126_VENC_CONFIG rkmedia_venc_config = {0};
    memset(&rkmedia_venc_config, 0, sizeof(rkmedia_venc_config));
    rkmedia_venc_config.id = 0;
    rkmedia_venc_config.attr.stVencAttr.enType = RK_CODEC_TYPE_H264;          // 编码器协议类型
    rkmedia_venc_config.attr.stVencAttr.imageType = IMAGE_TYPE_NV12;          // 输入图像类型
    rkmedia_venc_config.attr.stVencAttr.u32PicWidth = 1920;                   // 编码图像宽度
    rkmedia_venc_config.attr.stVencAttr.u32PicHeight = 1080;                  // 编码图像高度
    rkmedia_venc_config.attr.stVencAttr.u32VirWidth = 1920;                   // 编码图像虚宽度,一般来说u32VirWidth和u32PicWidth是一致的
    rkmedia_venc_config.attr.stVencAttr.u32VirHeight = 1080;                  // 编码图像虚高度,一般来说u32VirHeight和u32PicHeight是一致的
    rkmedia_venc_config.attr.stVencAttr.u32Profile = 66;                      // 编码等级H.264: 66: Baseline; 77:Main Profile; 100:High Profile; H.265: default:Main; Jpege/MJpege: default:Baseline(编码等级的作用主要是改变画面质量,66的画面质量最差利于网络传输,100的质量最好)

    rkmedia_venc_config.attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;        // 编码器码率控制模式
    rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32Gop = 25;                  // GOPSIZE:关键帧间隔
    rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32BitRate = 1920 * 1080 * 3; // 码率
    rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;      // 目的帧率分子:填的是1固定
    rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;     // 目的帧率分母:填的是25固定
    rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;       // 源头帧率分子:填的是1固定
    rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;      // 源头帧率分母:填的是25固定

    ret = rkmedia_venc_init(&rkmedia_venc_config);                            // VENC模块的初始化
    if (ret != 0)
    {
        printf("venc init error\n");
    }
    else
    {
        RV1126_VENC_CONTAINER venc_container;
        venc_container.id = 0;
        venc_container.venc_id = rkmedia_venc_config.id;
        set_venc_container(0, &venc_container);
        printf("venc init success\n");
    }

    // RGA
    RGA_ATTR_S rga_info;
    /**Image Input ..............*/
    rga_info.stImgIn.u32Width = 1920;           // 设置RGA输入分辨率宽度
    rga_info.stImgIn.u32Height = 1080;          // 设置RGA输入分辨率高度
    rga_info.stImgIn.u32HorStride = 1920;       // 设置RGA输入分辨率虚宽
    rga_info.stImgIn.u32VirStride = 1080;       // 设置RGA输入分辨率虚高
    rga_info.stImgIn.imgType = IMAGE_TYPE_NV12; // 设置ImageType图像类型
    rga_info.stImgIn.u32X = 0;                  // 设置X坐标
    rga_info.stImgIn.u32Y = 0;                  // 设置Y坐标

    /**Image Output......................*/
    rga_info.stImgOut.u32Width = 1280;           // 设置RGA输出分辨率宽度
    rga_info.stImgOut.u32Height = 720;           // 设置RGA输出分辨率高度
    rga_info.stImgOut.u32HorStride = 1280;       // 设置RGA输出分辨率虚宽
    rga_info.stImgOut.u32VirStride = 720;        // 设置RGA输出分辨率虚高
    rga_info.stImgOut.imgType = IMAGE_TYPE_NV12; // 设置输出ImageType图像类型
    rga_info.stImgOut.u32X = 0;                  // 设置X坐标
    rga_info.stImgOut.u32Y = 0;                  // 设置Y坐标

    // RGA Public Parameter
    rga_info.u16BufPoolCnt = 3; // 缓冲池计数
    rga_info.u16Rotaion = 0;    //
    rga_info.enFlip = RGA_FLIP_H;
    rga_info.bEnBufPool = RK_TRUE;
    ret = RK_MPI_RGA_CreateChn(0, &rga_info);
    if (ret)
    {
        printf("RGA Set Failed.....\n");
    }
    else
    {
        printf("RGA Set Success.....\n");
    }

    RV1126_VENC_CONFIG low_rkmedia_venc_config = {0};
    memset(&low_rkmedia_venc_config, 0, sizeof(low_rkmedia_venc_config));
    low_rkmedia_venc_config.id = 1;
    low_rkmedia_venc_config.attr.stVencAttr.enType = RK_CODEC_TYPE_H264;         // 编码器协议类型
    low_rkmedia_venc_config.attr.stVencAttr.imageType = IMAGE_TYPE_NV12;         // 输入图像类型
    low_rkmedia_venc_config.attr.stVencAttr.u32PicWidth = 1280;                  // 编码图像宽度
    low_rkmedia_venc_config.attr.stVencAttr.u32PicHeight = 720;                  // 编码图像高度
    low_rkmedia_venc_config.attr.stVencAttr.u32VirWidth = 1280;                  // 编码图像虚宽度,一般来说u32VirWidth和u32PicWidth是一致的
    low_rkmedia_venc_config.attr.stVencAttr.u32VirHeight = 720;                  // 编码图像虚高度,一般来说u32VirHeight和u32PicHeight是一致的
    low_rkmedia_venc_config.attr.stVencAttr.u32Profile = 66;                     // 编码等级H.264: 66: Baseline; 77:Main Profile; 100:High Profile; H.265: default:Main; Jpege/MJpege: default:Baseline(编码等级的作用主要是改变画面质量,66的画面质量最差利于网络传输,100的质量最好)

    low_rkmedia_venc_config.attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;       // 编码器码率控制模式
    low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32Gop = 30;                 // GOPSIZE:关键帧间隔
    low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32BitRate = 1280 * 720 * 3; // 码率
    low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;     // 目的帧率分子:填的是1固定
    low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;    // 目的帧率分母:填的是25固定
    low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;      // 源头帧率分子:填的是1固定
    low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;     // 源头帧率分母:填的是25固定    
    ret = rkmedia_venc_init(&low_rkmedia_venc_config);                           // VENC模块的初始化
    if (ret != 0)
    {
        printf("venc init error\n");
    }
    else
    {
        RV1126_VENC_CONTAINER low_venc_container;
        low_venc_container.id = 1;
        low_venc_container.venc_id = low_rkmedia_venc_config.id;
        set_venc_container(low_venc_container.id, &low_venc_container);
        printf("low_venc init success\n");
    }

    /*ret = RK_MPI_VENC_RGN_Init(0, NULL); 
    if (ret)
    {
        printf("Create HIGH_VENC_RGN Failed .....\n");
        return 0;
    }
    else
    {
        printf("Create HIGI_VENC_RGN Success .....\n");
    }

    int t1;
    t1 = get_cur_time_ms();
    // 获取当前时间
    time_t now = time(NULL);
    struct tm *tm_now = localtime(&now);

    char time_str[30];
    sprintf(time_str, "%04d-%02d-%02d %02d:%02d:%02d",
            tm_now->tm_year + 1900, tm_now->tm_mon + 1, tm_now->tm_mday,
            tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);
    printf("sizeof_time_str = %d\n", strlen(time_str));

    char * time_string = "2019-11-21 15:40:29";

    TTF_Font *ttf_font;
    char pstr[128] = {0};
    snprintf(pstr, 128, "%s", "HelloWorld");

    SDL_Surface *text_surface;
    SDL_Surface *convert_text_surface;
    SDL_PixelFormat *pixel_format;*/

#if 0
    // TTF模块的初始化
#if 0
    ret = TTF_Init();
    if (ret < 0)
    {
        printf("TTF_Init Failed...\n");
    }

    // 打开TTF的字库
    ttf_font = TTF_OpenFont("./fzlth.ttf", 48);
    if (ttf_font == NULL)
    {
        printf("TTF_OpenFont Failed...\n");
    }

    // SDL_COLOR黑色,RGB(0,0,0)
    SDL_Color sdl_color;
    sdl_color.r = 0;
    sdl_color.g = 0;
    sdl_color.b = 0;
    text_surface = TTF_RenderUTF8_Solid(ttf_font, time_string, sdl_color); ////渲染文字

    // ARGB_8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
    pixel_format->BitsPerPixel = 32;  // 每个像素所占的比特位数
    pixel_format->BytesPerPixel = 4;  // 每个像素所占的字节数
    pixel_format->Amask = 0XFF000000; // ARGB的A掩码,A位0xff
    pixel_format->Rmask = 0X00FF0000; // ARGB的R掩码,R位0xff
    pixel_format->Gmask = 0X0000FF00; // ARGB的G掩码,G位0xff
    pixel_format->Bmask = 0X000000FF; // ARGB的B掩码,B位0xff
    pixel_format->colorkey = 0x000000ff;
    pixel_format->alpha = 0xff;

    convert_text_surface = SDL_ConvertSurface(text_surface, pixel_format, 0);
    if (convert_text_surface == NULL)
    {
        printf("convert_text_surface failed...\n");
    }

    BITMAP_S bitmap;
    bitmap.u32Width = get_align16_value(convert_text_surface->w, 16);  // Bitmap的宽度
    bitmap.u32Height = get_align16_value(convert_text_surface->h, 16); // Bitmap的高度
    bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;
    bitmap.pData = malloc((bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel);                                             ////bitmap的data的分配大小
    memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) * pixel_format->BytesPerPixel); ////bitmap的data赋值
#endif



#if 0
    printf("ssssss\n");
    SDL_Surface* load_picture = SDL_LoadBMP("./0.bmp");    
    printf("gggggggg\n");                                                                                                         BITMAP_S bitmap;
    bitmap.u32Width = get_align16_value(load_picture->w, 16);                                                                        // Bitmap的宽度
    bitmap.u32Height = get_align16_value(load_picture->h, 16);                                                                    // Bitmap的高度
    bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;                                                                                           
    bitmap.pData = malloc((bitmap.u32Width) * (bitmap.u32Height) *4);                                             
    memcpy(bitmap.pData, load_picture->pixels, (load_picture->w) * (load_picture->h) * 4);
    printf("dddddddd\n");
#endif

    OSD_REGION_INFO_S rgn_info;            // OSD_RGN_INFO结构体
    rgn_info.enRegionId = REGION_ID_0;     // rgn的区域ID
    rgn_info.u32Width = bitmap.u32Width;   // osd的长度
    rgn_info.u32Height = bitmap.u32Height; // osd的高度

    //rgn_info.u32Width = 560;   // osd的长度
    //rgn_info.u32Height = 560; // osd的高度

    rgn_info.u32PosX = 128; // Osd的X轴方向
    rgn_info.u32PosY = 128; // Osd的Y轴方向
    rgn_info.u8Enable = 1;  ////使能OSD模块,1是使能,0为禁止。
    rgn_info.u8Inverse = 0; // 禁止翻转

    ret = RK_MPI_VENC_RGN_SetBitMap(0, &rgn_info, &bitmap); // 设置OSD位图
    if (ret)
    {
        printf("HIGI_RK_MPI_VENC_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("HIGI_RK_MPI_VENC_RGN_SetBitMap Success...\n");
    }

    ret = RK_MPI_RGA_RGN_SetBitMap(0, &rgn_info, &bitmap);
    if (ret)
    {
        printf("RK_MPI_RGA_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("RK_MPI_RGA_RGN_SetBitMap Success...\n");
    }
#endif

    return 0;
}

rv1126_ffmpeg_main.cpp

#include "rkmedia_ffmpeg_config.h"
#include "rkmedia_container.h"
#include "ffmpeg_audio_queue.h"
#include "ffmpeg_video_queue.h"
#include "rkmedia_module_function.h"
#include "rkmedia_assignment_manage.h"

VIDEO_QUEUE * high_video_queue = NULL;
VIDEO_QUEUE * low_video_queue = NULL;

int main(int argc, char *argv[])
{
    if(argc < 5)
    {
        printf("Please Input ./rv1126_ffmpeg_main high_stream_type high_url_address low_stream_type low_url_address. Notice URL_TYPE: 0-->FLV  1-->TS\n");
        return -1;
    }

    int high_protocol_type = atoi(argv[1]);
    char * high_network_address = argv[2];

    int low_protocol_type = atoi(argv[3]);
    char * low_network_address = argv[4];

    high_video_queue = new VIDEO_QUEUE(); //初始化所有VIDEO队列
    low_video_queue = new VIDEO_QUEUE();
    
    //init_rkmedia_ffmpeg_function();  //没用到
    init_rkmedia_module_function();  //初始化所有rkmedia的模块
    init_rv1126_first_assignment(high_protocol_type, high_network_address, low_protocol_type, low_network_address);  //开启推流任务
    
    while (1)
    {
       sleep(20);
    }
    
    return 0;
}

rv1126_isp_function.cpp

#include "rv1126_isp_function.h"

int init_all_isp_function()
{
    rk_aiq_working_mode_t hdr_mode = RK_AIQ_WORKING_MODE_NORMAL;
    SAMPLE_COMM_ISP_Init(hdr_mode, RK_FALSE);
    SAMPLE_COMM_ISP_Run();
    SAMPLE_COMM_ISP_SetFrameRate(30);

    return 0;
}

sample_common_isp.c

// Copyright 2020 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#if 1

#include "sample_common.h"
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

static rk_aiq_sys_ctx_t *g_aiq_ctx;

RK_S32 SAMPLE_COMM_ISP_Init(rk_aiq_working_mode_t WDRMode, RK_BOOL bFECEnable) {
  char *iq_file_dir = "/etc/iqfiles/";
  setlinebuf(stdout);

  rk_aiq_sys_ctx_t *aiq_ctx;
  rk_aiq_static_info_t aiq_static_info;
  rk_aiq_uapi_sysctl_enumStaticMetas(0, &aiq_static_info);

  printf("sensor_name is %s, iqfiles is %s\n",
         aiq_static_info.sensor_info.sensor_name, iq_file_dir);

  aiq_ctx = rk_aiq_uapi_sysctl_init(aiq_static_info.sensor_info.sensor_name,
                                    iq_file_dir, NULL, NULL);

  printf("rk_aiq_uapi_sysctl_init bFECEnable %d\n", bFECEnable);

  rk_aiq_uapi_setFecEn(aiq_ctx, bFECEnable);

  printf("rk_aiq_uapi_setFecEn\n");
  if (rk_aiq_uapi_sysctl_prepare(aiq_ctx, 0, 0, WDRMode)) {
    printf("rkaiq engine prepare failed !\n");
    return -1;
  }
  printf("rk_aiq_uapi_sysctl_init/prepare succeed\n");
  g_aiq_ctx = aiq_ctx;
  return 0;
}

RK_VOID SAMPLE_COMM_ISP_Stop(void) {
  if (!g_aiq_ctx)
    return;

  printf("rk_aiq_uapi_sysctl_stop enter\n");
  rk_aiq_uapi_sysctl_stop(g_aiq_ctx, false);
  printf("rk_aiq_uapi_sysctl_deinit enter\n");
  rk_aiq_uapi_sysctl_deinit(g_aiq_ctx);
  printf("rk_aiq_uapi_sysctl_deinit exit\n");
  g_aiq_ctx = NULL;
}

RK_S32 SAMPLE_COMM_ISP_Run(void) {
  if (!g_aiq_ctx)
    return -1;

  if (rk_aiq_uapi_sysctl_start(g_aiq_ctx)) {
    printf("rk_aiq_uapi_sysctl_start  failed\n");
    return -1;
  }
  printf("rk_aiq_uapi_sysctl_start succeed\n");
  return 0;
}

RK_VOID SAMPLE_COMM_ISP_DumpExpInfo(rk_aiq_working_mode_t WDRMode) {
  char aStr[128] = {'\0'};
  Uapi_ExpQueryInfo_t stExpInfo;
  rk_aiq_wb_cct_t stCCT;

  rk_aiq_user_api_ae_queryExpResInfo(g_aiq_ctx, &stExpInfo);
  rk_aiq_user_api_awb_GetCCT(g_aiq_ctx, &stCCT);

  if (WDRMode == RK_AIQ_WORKING_MODE_NORMAL) {
    sprintf(aStr, "M:%.0f-%.1f LM:%.1f CT:%.1f",
            stExpInfo.CurExpInfo.LinearExp.exp_real_params.integration_time *
                1000 * 1000,
            stExpInfo.CurExpInfo.LinearExp.exp_real_params.analog_gain,
            stExpInfo.MeanLuma, stCCT.CCT);
  } else {
    sprintf(aStr, "S:%.0f-%.1f M:%.0f-%.1f L:%.0f-%.1f SLM:%.1f MLM:%.1f "
                  "LLM:%.1f CT:%.1f",
            stExpInfo.CurExpInfo.HdrExp[0].exp_real_params.integration_time *
                1000 * 1000,
            stExpInfo.CurExpInfo.HdrExp[0].exp_real_params.analog_gain,
            stExpInfo.CurExpInfo.HdrExp[1].exp_real_params.integration_time *
                1000 * 1000,
            stExpInfo.CurExpInfo.HdrExp[1].exp_real_params.analog_gain,
            stExpInfo.CurExpInfo.HdrExp[2].exp_real_params.integration_time *
                1000 * 1000,
            stExpInfo.CurExpInfo.HdrExp[2].exp_real_params.analog_gain,
            stExpInfo.HdrMeanLuma[0], stExpInfo.HdrMeanLuma[1],
            stExpInfo.HdrMeanLuma[2], stCCT.CCT);
  }
  printf("isp exp dump: %s\n", aStr);
}

RK_VOID SAMPLE_COMM_ISP_SetFrameRate(RK_U32 uFps) {
  if (!g_aiq_ctx)
    return;

  printf("SAMPLE_COMM_ISP_SetFrameRate start %d\n", uFps);

  frameRateInfo_t info;
  info.mode = OP_MANUAL;
  info.fps = uFps;
  rk_aiq_uapi_setFrameRate(g_aiq_ctx, info);

  printf("SAMPLE_COMM_ISP_SetFrameRate %d\n", uFps);
}

RK_VOID SAMPLE_COMM_ISP_SetLDCHLevel(RK_U32 level) {
  if (!g_aiq_ctx)
    return;
  rk_aiq_uapi_setLdchEn(g_aiq_ctx, level > 0);
  if (level > 0 && level <= 255)
    rk_aiq_uapi_setLdchCorrectLevel(g_aiq_ctx, level);
}

/*only support switch between HDR and normal*/
RK_VOID SAMPLE_COMM_ISP_SetWDRModeDyn(rk_aiq_working_mode_t WDRMode) {
  rk_aiq_uapi_sysctl_swWorkingModeDyn(g_aiq_ctx, WDRMode);
}

#endif

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐