RV1126 + FFPEG多路码流项目

发布于:2025-05-30 ⋅ 阅读:(15) ⋅ 点赞:(0)

代码主体思路:

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


网站公告

今日签到

点亮在社区的每一天
去签到