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

RV1126 + FFPEG多路码流项目
代码主体思路: 一.VI,VENC,RGA模块初始化 1.先创建一个自定义公共结构体,用于方便管理各个模块 rkmedia_config_public.h //文件名字#ifndef _RV1126_PUBLIC_H #define _RV1126_PUBLIC_H#include <assert.h> #include <fcntl.h> #include …...

NodeJS 基于 Koa, 开发一个读取文件,并返回给客户端文件下载,以及读取文件形成列表和文件删除的代码演示
前言 在上一篇文章 《Nodejs 实现 Mysql 数据库的全量备份的代码演示》 中,我们演示了如何将用户的 Mysql 数据库进行备份的代码。但是,这个备份,只是备份在了服务器上了。 而我们用户的真实需求,是需要将备份文件下载到本地进行…...

为什么在我的Flask里面有两个路由,但是在网页里有一个却不能正确访问到智能体
1. /zhoushibo 能访问,/chat 直接浏览器访问报 Method Not Allowed 原因: /zhoushibo 路由是你用 app.route(/zhoushibo) 定义的,返回的是一个HTML网页,浏览器访问没问题。 /chat 路由你用的是 app.route(/chat, methods[POST])…...

哈工大计算机系统2024大作业——Hello的程序人生
计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 人工智能 学 号 2022112040 班 级 2203601 学 生 郄东昕 指 导 教 师 吴锐 计算机科学与技术学院…...

2025年软件测试面试八股文(含答案+文档)
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 Part1 1、你的测试职业发展是什么? 测试经验越多,测试能力越高。所以我的职业发展是需要时间积累的,一步步向着高级测试工程师…...
【仿生系统】qwen的仿生机器人解决方案
收到完整需求后,我将从系统架构设计、算法实现路径、训练方法三个维度为您构建完整的仿生机器人解决方案。以下是分阶段实施方案: 一、核心系统架构设计(模块化可进化架构) 1. 多模态感知引擎 - 视觉子系统:YOLOv8SAM…...

Flutter3.22适配运行鸿蒙系统问题记录
Flutter3.22适配运行鸿蒙系统问题记录 一:适配条件适配过程问题记录(1)环境配置问题(2)Concurrent modification during iteration: Instance(length:2) of_GrowableList 报错(3)三方插件寻找替…...

秋招Day10 - JVM - 内存管理
JVM组织架构主要有三个部分:类加载器、运行时数据区和字节码执行引擎 类加载器:负责从文件系统、网络或其他来源加载class文件,将class文件中的二进制数据加载到内存中运行时数据区:运行时的数据存放的区域,分为方法区…...

Spring Boot 3.5.0中文文档上线
Spring Boot 3.5.0 中文文档翻译完成,需要的可收藏 传送门:Spring Boot 3.5.0 中文文档...

Redisson学习专栏(一):快速入门及核心API实践
文章目录 前言一、Redisson简介1.1 什么是Redisson?1.2 解决了什么问题? 二、快速入门2.1 环境准备 2.2 基础配置三、核心API解析3.1 分布式锁(RLock)3.2 分布式集合3.2.1 RMap(分布式Map)3.2.2 RList&…...

Pandas学习入门一
1.什么是Pandas? Pandas是一个强大的分析结构化数据的工具集,基于NumPy构建,提供了高级数据结构和数据操作工具,它是使Python成为强大而高效的数据分析环境的重要因素之一。 一个强大的分析和操作大型结构化数据集所需的工具集基础是NumPy…...

基于Piecewise Jerk Speed Optimizer的速度规划算法(附ROS C++/Python仿真)
目录 1 时空解耦运动规划2 PJSO速度规划原理2.1 优化变量2.2 代价函数2.3 约束条件2.4 二次规划形式 3 算法仿真3.1 ROS C仿真3.2 Python仿真 1 时空解耦运动规划 在自主移动系统的运动规划体系中,时空解耦的递进式架构因其高效性与工程可实现性被广泛采用。这一架…...
关于 JavaScript 版本、TypeScript、Vue 的区别说明, PHP 开发者入门 Vue 的具体方案
以下是关于 JavaScript 版本、TypeScript、Vue 的区别说明,以及 PHP 开发者入门 Vue 的具体方案: 一、JavaScript 版本演进 JavaScript 的核心版本以 ECMAScript 规范(ES) 命名: 版本发布时间关键特性ES52009严格模式…...
中断和信号详解
三种中断 中断分为三种:硬件中断、异常中断、软中断 硬件中断 设备向中断控制器发送中断请求,中断控制器生成对应中断号,然后通过中断引脚向cpu发送高电平,cpu收到请求后不会立即处理,cpu会处理完当前指令ÿ…...
STM32八股【10】-----stm32启动流程
启动流程 1.上电复位 2.系统初始化 3.跳转到 main 函数 启动入口: cpu被清空,程序从0x00000000开始运行0x00000000存放的是reset_handler的入口地址0x00000000的实际位置会变,根据不同的启动模式决定启动模式分为: flash启动&a…...

游戏引擎学习第312天:跨实体手动排序
运行游戏并评估当前状况 目前排序功能基本已经正常,能够实现特定的排序要求,针对单一区域、单个房间的场景,效果基本符合预期。 不过还有一些细节需要调试。现在有些对象的缩放比例不对,导致它们看起来有些怪异,需要…...

智警杯备赛--数据库管理与优化及数据库对象创建与管理
sql操作 插入数据 如果要操作数据表中的数据,首先应该确保表中存在数据。没有插入数据之前的表只是一张空表,需要使用insert语句向表中插入数据。插入数据有4种不同的方式:为所有字段插入数据、为指定字段插入数据、同时插入多条数据以及插…...

MySQL 在 CentOS 7 环境下的安装教程
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习Mysql的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...

K8S集群主机网络端口不通问题排查
一、环境: k8s: v1.23.6 docker: 20.10.14 问题和故障现象:devops主机集群主机节点到端口8082不通(网络策略已经申请,并且网络策略已经实施完毕),而且网络实施人员再次确认,网络策…...
【Elasticsearch】retry_on_conflict
在 Elasticsearch 中,retry_on_conflict 是 _update 和 _update_by_query API 的一个参数,用于处理并发冲突。当多个客户端同时尝试更新同一个文档时,可能会发生版本冲突(version conflict)。retry_on_conflict 参数允…...
Android Cameara2 + MediaRecorder 完成录像功能
一、打开相机、预览 打开相机预览流程是Camera2的默认流程 可参考:https://blog.csdn.net/kk3087961/article/details/135616576 二、开启录像功能 开启录像主要包括以下3步: private void startRecording() {// 1. 停止预览并关闭会话if (mCameraSes…...

python打卡day39
知识点回顾 图像数据的格式:灰度和彩色数据模型的定义显存占用的4种地方 模型参数梯度参数优化器参数数据批量所占显存神经元输出中间状态 batchisize和训练的关系 课程代码: # 先继续之前的代码 import torch import torch.nn as nn import torch.opti…...

3.8.5 利用RDD统计网站每月访问量
本项目旨在利用Spark RDD统计网站每月访问量。首先,创建名为“SparkRDDWebsiteTraffic”的Maven项目,并添加Spark和Scala的依赖。接着,编写Scala代码,通过SparkContext读取存储在HDFS上的原始数据文件,使用map和reduce…...

尚硅谷redis7 49-51 redis管道之理论简介
前提redis事务和redis管道有点像,但本质上截然不同 49 redis管道之理论简介 面试题 如何优化频繁命令往返造成的性能瓶颈? redis每秒可以承受8万的写操作和接近10万次以上的读操作。每条命令都发送、处理、返回,能不能批处理一次性搞定呢…...
Spring Boot + MyBatis-Plus实现操作日志记录
创建数据库表 CREATE TABLE sys_operation_log (log_id bigint NOT NULL AUTO_INCREMENT COMMENT 日志ID,operation_type varchar(20) NOT NULL COMMENT 操作类型,operation_module varchar(50) NOT NULL COMMENT 操作模块,operation_desc varchar(200) DEFAULT NULL COMMENT …...
JavaScript入门基础篇-day03
一、为什么需要数组? 在我们正式学习数组之前,先思考一个场景:假设我们要记录一个班级50位同学的期末成绩。如果不用数组,代码会是这样的: let score1 85; let score2 92; let score3 78; // ... 要写50个变量&am…...
Leetcode-5 好数对的数目
Leetcode-5 好数对的数目(简单) 题目描述思路分析通过代码(python) 题目描述 给你一个整数数组 nums 。 如果一组数字 (i,j) 满足 nums[i] nums[j] 且 i < j ,就可以认为这是一组 好数对 。 返回好数对的数目。 示…...

openEuler安装MySql8(tar包模式)
操作系统版本: openEuler release 22.03 (LTS-SP4) MySql版本: 下载地址: https://dev.mysql.com/downloads/mysql/ 准备安装: 关闭防火墙: 停止防火墙 #systemctl stop firewalld.service 关闭防火墙 #systemc…...
Opencv实用操作6 开运算 闭运算 梯度运算 礼帽 黑帽
1.相关函数 开运算 img_open cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)#(图片,算法,核) 闭运算 img_close cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)#(图片,算法,核) 梯度…...

基于python,html,flask,echart,ids/ips,VMware,mysql,在线sdn防御ddos系统
详细视频:【基于python,html,flask,echart,ids/ips,VMware,mysql,在线sdn防御ddos系统-哔哩哔哩】 https://b23.tv/azUqQXe...