代码主体思路:
一.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 结构体指针
功能:线程等待并挂起,若被唤醒了,则直接跳出挂起状态。