代码主体思路:
一.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) //64KB
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()
{
//系统初始化
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的ID
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的通道给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线程,用于获取摄像头低编码数据,然后传给低VENC
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);
}
//把低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 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, 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 vi
ret = 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 TCP
if (ffmpeg_config->protocol_type == FLV_PROTOCOL)
{
//初始化一个FLV的AVFormatContext
ret = avformat_alloc_output_context2(&ffmpeg_config->oc, NULL, "flv", ffmpeg_config->network_addr); //设置用FLV作为输出模式
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;
}
}
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 中的 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");
}
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声道数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
//在h264头部添加SPS,PPS
if (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 结构体指针
功能:线程等待并挂起,若被唤醒了,则直接跳出挂起状态。