RV1126 + FFPEG多路码流项目

article/2025/8/23 23:24:48

代码主体思路:

一.VI,VENC,RGA模块初始化

1.先创建一个自定义公共结构体,用于方便管理各个模块

rkmedia_config_public.h //文件名字#ifndef _RV1126_PUBLIC_H
#define _RV1126_PUBLIC_H#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>#include "sample_common.h"
#include "rkmedia_api.h"
#include "rkmedia_venc.h"#define CAMERA_PIPE 0 //摄像头管道号
#define CMOS_DEV "rkispp_scale0" //CMOS设备名//VI模块通道号和属性结构体集合的结构体
typedef struct {int id ;VI_CHN_ATTR_S vi_attr;
}RV1126_VI_CONFIG;//AI模块通道号和属性结构体集合的结构体
typedef struct {int id ;AI_CHN_ATTR_S ai_attr;
}RV1126_AI_CONFIG;//VENC模块通道号和属性结构体集合的结构体
typedef struct {int id ;VENC_CHN_ATTR_S venc_attr;
} RV1126_VENC_CONFIG;//AENC模块通道号和属性结构体集合的结构体
typedef struct {int id ;AENC_CHN_ATTR_S aenc_attr;
}RV1126_AENC_CONFIG;#endif
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_container.h //文件名#ifndef _RKMEDIA_CONTAINER_H
#define _RKMEDIA_CONTAINER_H#define ALL_CONTAINER_NUM 20#include <stdbool.h>
#include "rkmedia_config_public.h"typedef struct
{unsigned int id;unsigned int vi_id;}RV1126_VI_CONTAINTER;typedef struct
{unsigned int id;unsigned int ai_id;}RV1126_AI_CONTAINTER;typedef struct
{unsigned int id;unsigned int venc_id;}RV1126_VENC_CONTAINER;typedef struct
{unsigned int id;unsigned int aenc_id;}RV1126_AENC_CONTAINER;typedef struct
{unsigned int container_id;RV1126_VI_CONTAINTER vi_containers[ALL_CONTAINER_NUM];RV1126_AI_CONTAINTER ai_containers[ALL_CONTAINER_NUM];RV1126_VENC_CONTAINER venc_containers[ALL_CONTAINER_NUM];RV1126_AENC_CONTAINER aenc_containers[ALL_CONTAINER_NUM];}RV1126_ALL_CONTAINER;int init_all_container_function();int set_vi_container(unsigned int index, RV1126_VI_CONTAINTER *vi_container);
int get_vi_container(unsigned int index, RV1126_VI_CONTAINTER *vi_container);int set_ai_container(unsigned int index, RV1126_AI_CONTAINTER *ai_container);
int get_ai_container(unsigned int index, RV1126_AI_CONTAINTER *ai_container);int set_venc_container(unsigned int index, RV1126_VENC_CONTAINER *venc_container);
int get_venc_container(unsigned int index, RV1126_VENC_CONTAINER *venc_container);int set_aenc_container(unsigned int index, RV1126_AENC_CONTAINER *aenc_container);
int get_aenc_container(unsigned int index, RV1126_AENC_CONTAINER *aenc_container);#endif

2.为了使代码简洁,易懂,创建一个使能各个模块文件

rkmedia_module.cpp //文件名#include "rkmedia_module.h"//系统初始化函数
int SYS_init(){RK_MPI_SYS_Init();return 0;
}//VI模块初始化函数
int RK1126_vi_init(RV1126_VI_CONFIG *vi_config){int ret;int id = vi_config->id;VI_CHN_ATTR_S vi_attr = vi_config->vi_attr;ret = RK_MPI_VI_SetChnAttr(CAMERA_PIPE,id, &vi_attr);if (ret) {printf("RK_MPI_VI_SetChnAttr failed!\n");return -1;}else{printf("RK_MPI_VI_SetChnAttr success!\n");}ret = RK_MPI_VI_EnableChn(CAMERA_PIPE,id);if (ret) {printf("RK_MPI_VI_EnableChn failed!\n");return -1;}else{printf("RK_MPI_VI_EnableChn success!\n");}return 0;
}//VENC模块初始化函数
int RV1126_venc_init(RV1126_VENC_CONFIG *venc_config){int ret;int id = venc_config->id;VENC_CHN_ATTR_S venc_attr = venc_config->venc_attr;ret = RK_MPI_VENC_CreateChn(id, &venc_attr);if (ret) {printf("RK_MPI_VENC_CreateChn failed!\n");return -1;}else{printf("RK_MPI_VENC_CreateChn success!\n");}return 0;
}//AI模块初始化函数
int RV1126_ai_init(RV1126_AI_CONFIG *ai_config){int ret;int id = ai_config->id;AI_CHN_ATTR_S ai_attr = ai_config->ai_attr;ret = RK_MPI_AI_SetChnAttr(id, &ai_attr);if (ret) {printf("RK_MPI_AI_SetChnAttr failed!\n");return -1;}else{printf("RK_MPI_AI_SetChnAttr success!\n");}ret = RK_MPI_AI_EnableChn(id);if (ret) {printf("RK_MPI_AI_EnableChn failed!\n");return -1;}return 0;
}//AENC模块初始化函数
int RV1126_aenc_init(RV1126_AENC_CONFIG *aenc_config){int ret;int id = aenc_config->id;AENC_CHN_ATTR_S aenc_attr = aenc_config->aenc_attr;ret = RK_MPI_AENC_CreateChn(id, &aenc_attr);if (ret) {printf("RK_MPI_AENC_CreateChn failed!\n");return -1;}else{printf("RK_MPI_AENC_CreateChn success!\n");}return 0;
}
rkmedia_module.h //文件名#ifndef _RV1126_TASK_FUNCTION_H 
#define _RV1126_TASK_FUNCTION_H#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>#include "rkmedia_config_public.h"int SYS_init();int RK1126_vi_init(RV1126_VI_CONFIG *vi_config);int RV1126_ai_init(RV1126_AI_CONFIG *ai_config);int RV1126_venc_init(RV1126_VENC_CONFIG *venc_config);int RV1126_aenc_init(RV1126_AENC_CONFIG *venc_config);#endif

3.各个模块设置

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) //64KBstatic 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;}// 检测文件大小filefseek(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()
{//系统初始化SYS_init();//******************VI模块初始化*********************************//RV1126_VI_CONFIG vi_chn_attr;memset(&vi_chn_attr, 0, sizeof(vi_chn_attr));vi_chn_attr.id = 0; //VI通道号vi_chn_attr.vi_attr.pcVideoNode = CMOS_DEV;vi_chn_attr.vi_attr.u32BufCnt = 3;vi_chn_attr.vi_attr.u32Width = 1920;vi_chn_attr.vi_attr.u32Height = 1080;vi_chn_attr.vi_attr.enPixFmt = IMAGE_TYPE_NV12;vi_chn_attr.vi_attr.enWorkMode = VI_WORK_MODE_NORMAL;vi_chn_attr.vi_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;int ret = RK1126_vi_init(&vi_chn_attr);if (ret){printf("RK1126_vi_init failed:%d\n", ret);}else{//VI模块初始化成功就把VI模块的信息放到容器里,方便后续使用printf("RK1126_vi_init success\n");RV1126_VI_CONTAINTER vi_container;vi_container.id = 0;                 //vi模块在容器里的索引vi_container.vi_id = vi_chn_attr.id;//VI模块的通道号set_vi_container(0,&vi_container);printf("set_vi_container success\n");}//******************高分辨率的VENC模块初始化*********************************////高分辨率的VENC模块基础属性RV1126_VENC_CONFIG high_venc_attr;memset(&high_venc_attr, 0, sizeof(high_venc_attr));high_venc_attr.id = 0;high_venc_attr.venc_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;high_venc_attr.venc_attr.stVencAttr.u32Profile = 66;high_venc_attr.venc_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;high_venc_attr.venc_attr.stVencAttr.u32PicWidth = 1920;high_venc_attr.venc_attr.stVencAttr.u32PicHeight = 1080;high_venc_attr.venc_attr.stVencAttr.u32VirWidth = 1920;high_venc_attr.venc_attr.stVencAttr.u32VirHeight = 1080;high_venc_attr.venc_attr.stVencAttr.enRotation = VENC_ROTATION_0;//高分辨率的VENC模块编码属性high_venc_attr.venc_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;high_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32Gop = 25;high_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32BitRate = 1920 * 1080 * 3 ;high_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;high_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;high_venc_attr.venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;high_venc_attr.venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;int ret = RV1126_venc_init(&high_venc_attr);if (ret){printf("Create HIGH_VENC Failed .....\n");}else   {printf("Create HIGH_VENC Success .....\n");RV1126_VENC_CONTAINER   high_venc_container;high_venc_container.id = 0; //高VENC模块在VENC容器里的索引号high_venc_container.venc_id = high_venc_attr.id;//高VENC模块的通道号set_venc_container(0,&high_venc_container);printf("set_venc_container success\n");}//******************RGA模块初始化*********************************////RGA图像输入设置RGA_ATTR_S rga_attr;rga_attr.stImgIn.imgType = IMAGE_TYPE_NV12;rga_attr.stImgIn.u32Width = 1920;rga_attr.stImgIn.u32Height = 1080;rga_attr.stImgIn.u32HorStride = 1920;rga_attr.stImgIn.u32VirStride = 1080;rga_attr.stImgIn.u32X = 0;rga_attr.stImgIn.u32Y = 0;//RGA图像输出设置rga_attr.stImgOut.imgType = IMAGE_TYPE_NV12;rga_attr.stImgOut.u32Width = 1280;rga_attr.stImgOut.u32Height = 720;rga_attr.stImgOut.u32HorStride = 1280;rga_attr.stImgOut.u32VirStride = 720;rga_attr.stImgOut.u32X = 0;rga_attr.stImgOut.u32Y = 0;//RGA模块公共设置rga_attr.u16BufPoolCnt = 3;rga_attr.u16Rotaion = 0;rga_attr.bEnBufPool = RK_TRUE;rga_attr.enFlip =RGA_FLIP_H;int ret = RK_MPI_RGA_CreateChn(0, &rga_attr);if (ret){printf("Create RGA Failed .....\n");}else   {printf("Create RGA Success .....\n");}//******************低VENC模块初始化*********************************////低分辨率的VENC模块基础属性RV1126_VENC_CONFIG low_venc_attr;memset(&low_venc_attr, 0, sizeof(low_venc_attr));low_venc_attr.id = 1;low_venc_attr.venc_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;low_venc_attr.venc_attr.stVencAttr.u32Profile = 66;low_venc_attr.venc_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;low_venc_attr.venc_attr.stVencAttr.u32PicWidth = 1280;low_venc_attr.venc_attr.stVencAttr.u32PicHeight = 720;low_venc_attr.venc_attr.stVencAttr.u32VirWidth = 1280;low_venc_attr.venc_attr.stVencAttr.u32VirHeight = 720;low_venc_attr.venc_attr.stVencAttr.enRotation = VENC_ROTATION_0;//低分辨率的VENC模块编码属性low_venc_attr.venc_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;low_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32Gop = 25;low_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32BitRate = 1280 * 720 * 3 ;low_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;low_venc_attr.venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;low_venc_attr.venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;low_venc_attr.venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;int ret = RV1126_venc_init(&low_venc_attr);if (ret){printf("Create HIGH_VENC Failed .....\n");}else   {printf("Create HIGH_VENC Success .....\n");RV1126_VENC_CONTAINER   low_venc_container;low_venc_container.id = 1; //低VENC模块在VENC容器里的索引号low_venc_container.venc_id = low_venc_attr.id;//低VENC模块的通道号set_venc_container(1,&low_venc_container);printf("set_venc_container success\n");}return 0;
}
rkmedia_module_function.h //文件名#ifndef _RKMEDIA_MODULE_FUNCTION_H
#define _RKMEDIA_MODULE_FUNCTION_H#include "rkmedia_assignment_manage.h"
#include "rkmedia_data_process.h"
#include "sample_common.h"
#include "rkmedia_ffmpeg_config.h"
//#include "ffmpeg_group.h"int init_rkmedia_module_function();#endif

4.绑定VI和高VENC,VI和RGA模块

rkmedia_assignment_manage.cpp //文件名//******************************绑定VI和高VENC模块**********************//MPP_CHN_S vi_attr;MPP_CHN_S high_venc_attr;RV1126_VI_CONTAINTER vi_container;//从VI容器里获取信息get_vi_container(0,&vi_container);RV1126_VENC_CONTAINER high_venc_container;//从高VENC容器里获取信息get_venc_container(0,&high_venc_container);vi_attr.enModId = RK_ID_VI;vi_attr.s32ChnId = vi_container.vi_id;//VI的通道号high_venc_attr.enModId = RK_ID_VENC;high_venc_attr.s32ChnId = high_venc_container.venc_id;//VENC的通道号ret = RK_MPI_SYS_Bind(&vi_attr, &high_venc_attr);if (ret){printf("VI bind VENC failed!\n");}else{printf("VI bind VENC success!\n");}//******************************绑定VI和RGA模块**********************//MPP_CHN_S rga_attr;rga_attr.enModId = RK_ID_RGA;rga_attr.s32ChnId = 0;ret = RK_MPI_SYS_Bind(&vi_attr, &rga_attr);if (ret){printf("VI bind RGA failed!\n");}else{printf("VI bind RGA success!\n");}//******************************从低VENC容器里面获取VENC数据(绑定这里没有用到,后面线程用到)**********************//RV1126_VENC_CONTAINER low_venc_container;//从低VENC容器里获取信息get_venc_container(1,&low_venc_container);MPP_CHN_S low_venc_attr;low_venc_attr.enModId = RK_ID_VENC;low_venc_attr.s32ChnId = low_venc_container.venc_id;

5.创建线程获取数据,实现线程

rkmedia_assignment_manage.cpp //文件名//******************************创建线程,实现线程获取高VENC数据**********************//pthread_t pid;//为VENC_PROC_PARAM结构体分配内存,VENC_PROC_PARAM结构体里面包含VENC的IDVENC_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的通道给VENC_PROC_PARAM结构体的ID,后面线程会用到venc_arg_params->vencId = high_venc_attr.s32ChnId;//创建高VENC线程,获取摄像头编码数据ret = pthread_create(&pid, NULL, camera_venc_thread, (void *)venc_arg_params);//(void *)venc_arg_params是传给camera_venc_thread的参数if (ret != 0){printf("create camera_venc_thread failed\n");}//创建RGA线程,用于获取摄像头低编码数据,然后传给低VENCret = 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);}//把低VENC的通道给VENC_PROC_PARAM结构体的ID,后面线程会用到low_venc_arg_params->vencId = low_venc_attr.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");}
rkmedia_data_process.cpp//文件名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 viret = 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, venc_arg.vencId, -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);}//***************************释放空间************************//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(1);if (ret){printf("Destroy Venc error! ret=%d\n", ret);return 0;}// destroy viret = RK_MPI_VI_DisableChn(0, 0);if (ret){printf("Disable Chn Venc error! ret=%d\n", ret);return 0;}return NULL;
}

二.FFMPEG设置

  • 分配 FFMPEG AVFormatContext 输出的上下文结构体指针(设置用什么格式输出,FLV还是UDP)
rkmedia_ffmpeg_config.cpp //文件名//FLV_PROTOCOL is RTMP TCPif (ffmpeg_config->protocol_type == FLV_PROTOCOL){//初始化一个FLV的AVFormatContextret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "flv", ffmpeg_config->network_addr); //设置用FLV作为输出模式if (ret < 0){return -1;}}//TS_PROTOCOL is SRT UDP RTSPelse if (ffmpeg_config->protocol_type == TS_PROTOCOL){//初始化一个TS的AVFormatContextret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "mpegts", ffmpeg_config->network_addr);if (ret < 0){return -1;}}

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename)
第一个传输参数:AVFormatContext 结构体指针的指针,是存储音视频封装格式中包含的信息的结构体,所有对文件的封装、编码都是从这个结构体开始。
第二个传输参数:AVOutputFormat 的结构体指针,它主要存储复合流信息的常规配置,默认为设置 NULL。
第三个传输参数:format_name 指的是复合流的格式,比方说:flv、ts、mp4 等等
第四个传输参数:filename 是输出地址,输出地址可以是本地文件(如:xxx.mp4、xxx.ts 等等)。也可以是网络流地址(如:rtmp://xxx.xxx.xxx.xxx:1935/live/01) 

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

  •  配置推流器编码参数和 AVStream 结构体(主要是存储流信息结构体,这个流信息包含音频流和视频流)
//创建输出码流的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");}

AVStream * avformat_new_stream(AVFormatContext *s, AVDictionary **options);
第一个传输参数:AVFormatContext 的结构体指针
第二个传输参数:AVDictionary 结构体指针的指针
返回值:AVStream 结构体指针

  • 设置对应的推流器编码器参数 
//通过codecid找到CODEC*codec = avcodec_find_encoder(codec_id);if (!(*codec)){printf("Can't not find any encoder");return 0;}else{printf("Success find encoder");}

 AVCodec *avcodec_find_encoder(enum AVCodecID id); //
第一个传输参数:传递参数 AVCodecID

  • 根据编码器 ID 分配 AVCodecContext 结构体
//nb_streams 输入视频的AVStream 个数 就是当前有几种Stream,比如视频流、音频流、字幕,这样就算三种了,// s->nb_streams - 1其实对应的应是AVStream 中的 indexost->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");}

 AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
第一个参数:传递 AVCodec 结构体指针
avcodec_find_encoder 的主要作用是通过 codec_id(编码器 id )找到对应的 AVCodec 结构体。在 RV1126 推流项目中 codec_id我们使用两种,分别是 AV_CODEC_ID_H264、AV_CODEC_ID_H265。并利用 avcodec_alloc_context3 去创建 AVCodecContext 上下
文。

  • 初始化完 AVStream 和编码上下文结构体之后,我们就需要对这些参数进行配置。重点:推流编码器参数和 RV1126 编码器的参数要完全一样,否则可能会出问题
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声道数2c->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; //GOPSIZEc->pix_fmt = AV_PIX_FMT_NV12;//图像格式break;default:break;}
  •  在h264头部添加SPS,PPS
//在h264头部添加SPS,PPSif (oc->oformat->flags & AVFMT_GLOBALHEADER){c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}

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

  •   设置完上述参数之后,拷贝参数到 AVStream 编解码器(要 先 调 用 avcodec_open2 打 开 编 码 器 ,然 后 再 调 用avcodec_parameters_from_context 把编码器参数传输到AVStream 里面)
//使能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;
}

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
这个函数的具体作用是,打开编解码器
第一个参数:AVCodecContext 结构体指针
第二个参数:AVCodec 结构体指针
第三个参数:AVDictionary 二级指针


int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);这个函数的具体作用是,把 AVCodecContext 的参数拷贝到 AVCodecParameters 里面。
第一个参数:AVCodecParameters 结构体指针
第二个参数:AVCodecContext 结构体指针
 

  • 打开 IO 文件操作
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;}}

 使用 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 */

  •  avformat_write_header 对头部进行初始化,输出模块头部进行初始化
avformat_write_header(ffmpeg_config->oc, NULL);

int avformat_write_header(AVFormatContext *s, AVDictionary **options);
第一个参数:传递 AVFormatContext 结构体指针
第二个参数:传递 AVDictionary 结构体指针的指针

三.线程推流 

rkmedia_assignment_manage.cpp //文件名//*******************************推流线程**************************//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");}
rkmedia_data_process.cpp //文件名 ,线程的实现// 音视频合成推流线程
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;

四.队列

1.队列概念

        队列就是先进先出,假如1,2,3进队列里,那么先出来就是1,2,3.(和排队买东西一样,你先排,买完你就先走)

2.队列作用

  • 队列用于两线程间通信,(线程一把数据按照顺序把数据包存储到 Queue 上、线程二、三也按照顺序从队列拿到数据)。

  • 队列用于数据量缓存方面,(当编码端直接发给解码端时,如果网络断了,那之前数据都没有了;但你把数据放队列里面,我就可以慢慢拿数据,不会出现数据丢失)

3.队列用法

  • 初始化队列

#include <queue>
std::queue<object> object_queue;

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

  •  队列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() 来完成同样的操作。

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

 4.多线程队列使用

  •   入队线程主要是通过 push 的 api 向 Queue 的队尾插入数据,插入数据的同时通过 pthread_cond_broadcast 通知出队线程取出数据。此时出队线程正在等待入队线程的唤醒(pthread_cond_wait),若收到唤醒通知则让队列数据出队。

  •  多线程API

pthread_mutex_lock:(上锁,别的线程就不能拿数据)
int pthread_mutex_lock(pthread_mutex_t *mutex);
第一个传入参数:pthread_mutex_t 结构体指针
功能:这个是互斥锁加锁功能,就是每次线程调用的时候都会把锁加上,使其保证访问数据的原子性,直到解锁为止。


pthread_mutex_unlock:      (解锁,别的线程就可以拿数据,和上锁API配合使用)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
第一个传入参数:pthread_mutex_t 结构体指针
功能:这个是互斥锁解锁功能,就是每次线程访问完资源的时候都会把锁解锁。

pthread_cond_broadcast:(唤醒等待线程)
int pthread_cond_broadcast(pthread_cond_t *cond)
传入参数:pthread_cond_t 的结构体指针
功能:唤醒所有正在 pthread_cond_wait(线程等待)的线程


pthread_cond_wait:        (等待线程)
int pthread_cond_wait (pthread_cond_t *__restrict __cond , pthread_mutex_t *__restrict __mutex)
第一个参数:pthread_cond_t 的结构体指针
第二个参数:pthread_mutex_t 结构体指针
功能:线程等待并挂起,若被唤醒了,则直接跳出挂起状态。 


http://www.hkcw.cn/article/yIUXZHkydY.shtml

相关文章

Mybatis中的懒加载

目录 基本概念 懒加载的应用场景 如何配置懒加载 全局配置 局部配置 懒加载的工作原理 示例代码 一对一懒加载 一对多懒加载 懒加载的触发条件 懒加载的优缺点 优点&#xff1a; 缺点&#xff1a; 解决N 1查询问题的方法 注意事项 示例 对应sql语句 当只需输…

无人机桥梁3D建模的拍摄频率

无人机桥梁3D建模的拍摄频率 无人机桥梁3D建模的拍摄频率&#xff08;每秒拍摄照片数&#xff09;需根据建模精度、飞行速度、相机性能等因素综合确定。以下是专业级作业的详细参数分析&#xff1a; 1. 核心计算公式 拍摄频率&#xff08;fps&#xff09; \frac{飞行速度&…

AI安全挑战与全球应对:从ComfyUI漏洞谈起

目录 引言&#xff1a;ComfyUI漏洞的警示 一、ComfyUI漏洞 1.1 漏洞类型与影响 1.2 官方处置建议 二、更广泛的AI安全挑战 2.1 快速迭代与安全滞后 2.2 数据隐私与保护 2.3 攻击手段的智能化与规模化 2.4 人才缺口与攻防不均衡 三、全球应对AI安全的努力 3.1 政府与…

天洑软件响应“链主“集结令,亮相“宁工品推“助力南京市产业链协同发展

5月27日&#xff0c;南京市召开“宁工品推”市场拓展供需对接活动暨江宁经济技术开发区专场大会&#xff0c;天洑软件响应"链主"集结令&#xff0c;亮相"宁工品推"大会现场。 大会聚集了行业内多领域杰出代表&#xff0c;通过交流讨论&#xff0c;深度剖析…

重塑企业:迈向人类、智能体与下一代组织模式

“未来的工厂只需要两名员工&#xff1a;一个人和一只狗。人的工作是喂狗&#xff0c;狗的工作是防止人碰机器。” 人工智能不再只是后台工具&#xff0c;它正逐步成为前线的协作者。当自主智能体&#xff08;智能体&#xff09;越来越能分析、优化&#xff0c;甚至代表我们做出…

时间序列噪声模型分析软件推荐与使用经验

最近在论文大修2024年投稿的一篇文章&#xff0c;大修了2轮&#xff0c;最后一次还是重新投稿&#xff0c;其中有一个问题一直被审稿人怼&#xff0c;他认为我计算时间序列的趋势的时候&#xff0c;没有考虑时间的相关性&#xff0c;即对噪声模型的估计不合理&#xff0c;会影响…

并行智算云:打破时空边界的云计算平台,助力 AI 与科研的极速前行!

一、引言 在数字化浪潮中&#xff0c;算力已然成为推动科技创新与产业变革的核心驱动力。随着人工智能&#xff08;AI&#xff09;技术的迅猛发展以及科研领域对计算需求的指数级增长&#xff0c;传统计算模式逐渐难以满足复杂任务的高效处理要求。并行智算云应运而生&#xf…

实验设计与分析(第6版,Montgomery)第5章析因设计引导5.7节思考题5.5 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第5章析因设计引导5.7节思考题5.5 R语言解题。主要涉及方差分析&#xff0c;正态假设检验&#xff0c;残差分析&#xff0c;交互作用图。 dataframe <-data.frame( wrapc(17,20,12,9,…

野火教程/SDIO工作流程/SDIO笔记

本流程是根据野火官方提供的F407源码绘制而来,可将照片另存为到自己电脑上进行观看 SDIO&#xff08;Secure Digital Input Output&#xff09;是在SD存储卡标准基础上扩展出来的一种接口标准&#xff0c;主要用于连接除了存储卡以外的输入/输出设备&#xff08;如Wi-Fi卡、蓝牙…

Vert.x学习笔记-什么是Handler

Vert.x学习笔记 在Vert.x中&#xff0c;Handler是一个核心概念&#xff0c;用于处理异步事件和回调。它是Vert.x响应式编程模型的核心组件之一&#xff0c;通过函数式接口的方式简化了异步编程的复杂性。 1. Handler的定义 Handler是一个函数式接口&#xff0c;定义如下&#…

什么是系统调用

系统调用是一种编程方式&#xff0c;计算机程序通过这种方式向执行它的操作系统内核请求服务。系统调用是程序与操作系统交互的一种方式。计算机程序在请求操作系统内核时进行系统调用。系统调用通过应用程序接口&#xff08;API&#xff09;向用户程序提供操作系统的服务。系统…

解决各个系统报错TDengine:no taos in java.library.path问题

windows 系统解决办法 在本地上安装一个TD的Windows客户端&#xff0c;注意安装的客户端版本一定要和服务端TD版本完全一致。&#xff08;或者将 C:\TDengine\driver\taos.dll 拷贝到 C:\Windows\System32\ 目录下&#xff09; 客户端各个历史版本下载链接&#xff1a;TDengin…

《100天精通Python——基础篇 2025 第22天:Python 多进程编程入门与实战详解》

目录 一、进程相关概念回顾二、多进程初体验2.1 使用multiprocessing模块创建进程2.2 使用Process子类创建进程2.3 使用进程池Pool创建进程2.4 concurrent.futures包 三、进程通信3.1 Pipe类3.2 进程队列3.2.1 队列简介3.2.2 多进程队列的使用 四、多进程优化图片下载器各个模块…

Spring boot集成milvus(spring ai)

服务器部署Milvus Run Milvus with Docker Compose (Linux) milvus版本可在docker-compose.yml中进行image修改 启动后&#xff0c;docker查看启动成功 spring boot集成milvus 参考了这篇文章 Spring AI开发RAG示例&#xff0c;理解RAG执行原理 但集成过程中遇到了一系列…

2人因经济拮据竟偷盗老房子金属门环!

2人因经济拮据竟偷盗老房子金属门环。近日,广东揭阳周田派出所连续接报多起住宅门环被盗案件,民警初步判断很可能是同一批人所为。经侦,警方成功抓获犯罪嫌疑人陈某忠、陈某晓,并查获被盗门环一批。经查,两名嫌疑人因经济拮据,专挑无人老房子盗窃。目前,案件进一步办理中…

[Dify] 如何应对明道云API数据过长带来的Token超限问题

在集成明道云与大型语言模型(LLM)如ChatGPT或本地部署的Dify时,开发者经常会面临一个核心问题:API获取的数据太长,超出LLM支持的Token数限制,导致无法直接处理。本文将深入探讨这个问题的成因,并提供几种可行的解决方案,包括分段处理、外部知识库构建等策略。 明道云AP…

周奇:藏海是庄之行生命中的光!

周奇:藏海是庄之行生命中的光。周奇在《藏海传》中饰演的庄之行与藏海关系复杂,藏海对其成长影响深远。庄之行从无忧无虑的公子到后期经历家庭变故、练武从军,角色跨度大。在这个熙熙攘攘的娱乐圈中,多少年轻的生命如繁星般闪烁,却也让人分不清哪个是珍珠,哪个是泥沙。然…

3D拟合测量水杯半径

1&#xff0c;目的。 测量水杯的半径 如图所示&#xff1a; 2&#xff0c;原理。 对 3D 点云对象 进行圆柱体拟合&#xff0c;获取拟合后的半径。 3&#xff0c;注意事项。 在Halcon中使用fit_primitives_object_model_3d进行圆柱体拟合时&#xff0c;输出的primitive_para…

数据可视化(第4、5、6次课)

Matplotlib 折线图 import numpy as np import matplotlib.pyplot as plt import matplotlib # 配置中文格式——保证图中出现中文的时候不会乱码 matplotlib.rcParams[font.sans-serif][SimHei] matplotlib.rcParams[axes.unicode_minus]False # 绘图 x np.linspace(0,2*np…

SpringBoot(五)--- 异常处理、JWT令牌、拦截技术

目录 一、异常处理 1. 问题分析 2. 全局异常处理器 二、登录校验技术 1.会话技术 1.1 Cookie 1.2 Session 2. JWT令牌 2.1 介绍 2.2 生成与校验 2.3 登录时下发令牌 3. 过滤器Filter 3.1 概述 3.2 登录校验过滤器 3.3 Filter详解 4. 拦截器interceptor 4.1 令…