基于RV1126的多路码流编码视频推流项目

发布时间:2026/5/16 6:27:48

基于RV1126的多路码流编码视频推流项目 注此项目只用来分享和学习不能用来商业级的用途完整项目可以在评论区留言后面我会发布Nginx服务器的搭建方法和/FFmpeg客户端的使用方法。项目简述此项目硬件上是使用的瑞芯微RV1126开发板搭载COMS摄像头结合软件FFmpeg 和 Nginx服务器实现用硬件对原始双路1.1920*1080 2.1280*720视频流编码H.264\H.265通过FFmpeg使用RTMP协议来进行推送编码后的数据到中间件Nginx服务器上再通过中间件Nginx服务器将编码后的数据发送给客户端FFplay播放器来进行编码视频的解码和播放。项目技术音视频编解码(VI,VENC,RGA)多线程数据结构队列RTMP协议FFmpeg......项目知识介绍一.硬件编码部分在前几期的文章中其实已经提到了硬件编码部分的重要结构体和api想要进一步了解可以去笔者前几期的文章中看看有详细的代码使用和解释在这里就不过多赘述了。二.FFmpeg推流部分1.总体流程FFmpeg网络初始化-创造全局上下文结构体AVformatcontext *-设置编码器参数虽然这里不用FFmpeg来进行编码但是在推送码流的时候要确定编码类型的参数-创建并添加新的视频流-设置新的视频流的相关参数-打开网络IO文件-写码流文件头-**写文件内容-写文件尾-关闭和注销一些结构体和文件。2.结构体AVformatcontext * oc这是在FFmpeg中的最重要的结构体这个结构体是全局指挥官统领一切。在推流中它管理着输出格式、网络连接和所有的流视频、音频是调用绝大多数API时都需要传入的“总入口”。通过avformat_alloc_output_context2()来创建具体的用法笔者在这里不多赘述。AVOutputFormat它是封装格式说明书描述了你要使用的封装格式如 FLV、MP4的“规范”和特性例如支持的编解码器类型和文件扩展名。AVIOContext这是数据通道管理者负责实际的数据读写无论是写入本地文件还是推送到网络如 RTMP 服务器都由它通过avio_open()打开的通道来完成。AVStream这是视频/音频流的描述者代表输出中的一路流例如视频流。它最重要的成员之一是codecpar(AVCodecParameters)用于存储流的编码参数。通过函数avformat_new_stream(),来创建。AVCodecParameters这是编码参数说明书它告诉封装器你的数据是如何编码的如codec_idAV_CODEC_ID_H264、分辨率是多少等等。封装器需要这些信息才能正确打包。AVPacket这是数据打包员用于存放一帧压缩后的数据如 H.264 帧。在推流时你需要做的就是将硬件编码好的数据填充到AVPacket中然后交给av_interleaved_write_frame()发送出去。它们的嵌套关系AVFormatContextAVOutputFormat *oformat指向一个AVOutputFormat结构体决定封装格式。AVIOContext *pb指向 I/O 上下文负责网络或文件的读写。AVStream **streams指向一个AVStream的指针数组。例如如果有视频和音频两路流这里就有两个AVStream。AVStreamAVCodecParameters *codecpar指向编码参数描述该流的具体编码信息。AVPacketstream_index这个字段指向AVStream在AVFormatContext中streams数组的索引以表明这个数据包属于哪一路流。3.函数准备就绪: 调用avformat_alloc_output_context2()创建AVFormatContext结构体并确定好AVOutputFormat。创建通道: 为视频流新建AVStream(avformat_new_stream())并填充它的codecpar成员。打开大门: 通过avio_open()创建并初始化AVIOContext建立通往流媒体服务器的网络连接。写入头信息: 调用avformat_write_header()FFmpeg 会跟 据AVOutputFormat和AVStream的codecpar信息生成并发送文件头。数据流转: 进入主循环。你从硬件获取编码好的 H.264 数据然后通过av_packet_alloc()分配一个AVPacket。将数据拷贝到AVPacket的缓冲区中并设置好pts、dts、flags等元数据。调用av_interleaved_write_frame()这个函数会通过AVIOContext将AVPacket发送出去。完美收尾: 推流完成后调用av_write_trailer()写入文件尾并调用avio_closep()关闭网络连接最后释放所有上下文。***4.时间戳和时间基时间基时间基是一个有理数分数表示时间戳的单位。例如{1, 25}表示每个时间戳单位对应1/25秒即 0.04 秒时间戳DTS:告诉解码器什么时候解码这一帧。PTS:告诉播放器什么时候显示这一帧。对于没有 B 帧的视频流PTS DTS存在 B 帧时由于 B 帧需要参考后面的帧解码顺序和显示顺序不同PTS 和 DTS 会不同。为什么需要时间基的转换在编码端和推流器的封装端的时间基不一样因为要求不一致编码端口需要更精细的时间基来更好的编码FFmpeg 提供了av_packet_rescale_ts来自动完成这个转换av_packet_rescale_ts(pkt, tb_enc, tb_mux);项目内容一.编码因为是两路码流为了方便管理在这个项目总用了RV1126_VI_CONFIG rkmedia_vi_config这个结构体来管理Vi的变量。typedef struct { unsigned int id;//vi的id号方便读取 VI_CHN_ATTR_S attr;//vi的初始化信息 } RV1126_VI_CONFIG;对Vi模块进行初始化rkmedia_vi_config.id 0; rkmedia_vi_config.attr.pcVideoNode CMOS_DEVICE_NAME; // VIDEO视频节点路径, rkmedia_vi_config.attr.u32BufCnt 3; // VI捕获视频缓冲区计数默认是3 rkmedia_vi_config.attr.u32Width 1920; // 视频输入的宽度一般和CMOS摄像头或者外设的宽度一致 rkmedia_vi_config.attr.u32Height 1080; // 视频输入的高度一般和CMOS摄像头或者外设的高度一致 rkmedia_vi_config.attr.enPixFmt IMAGE_TYPE_NV12; // 视频输入的图像格式默认是NV12(IMAGE_TYPE_NV12) rkmedia_vi_config.attr.enBufType VI_CHN_BUF_TYPE_MMAP; // VI捕捉视频的类型 rkmedia_vi_config.attr.enWorkMode VI_WORK_MODE_NORMAL;为了可读行对vi模块的设置和使能笔者都放在了int rkmedia_vi_init(RV1126_VI_CONFIG *rv1126_vi_config){...} 的函数中进行int rkmedia_vi_init(RV1126_VI_CONFIG *rv1126_vi_config) { int ret; VI_CHN_ATTR_S vi_attr rv1126_vi_config-attr; unsigned int id rv1126_vi_config-id; //vi_attr.pcVideoNode CMOS_DEVICE_NAME;// //初始化VI模块 ret RK_MPI_VI_SetChnAttr(CAMERA_ID, id, vi_attr); //使能VI模块 ret | RK_MPI_VI_EnableChn(CAMERA_ID, id); if (ret ! 0) { printf(create vi failed.....\n, ret); return -1; } return 0; }对VENC模块也是同样的操作但是VENC模块分为高分辨率模块1920*1080和低分辨率码流1280*720高分辨率typedef struct { unsigned int id; VENC_CHN_ATTR_S attr; } RV1126_VENC_CONFIG; int rkmedia_venc_init(RV1126_VENC_CONFIG *rv1126_venc_config) { int ret; VENC_CHN_ATTR_S venc_chn_attr rv1126_venc_config-attr; unsigned int venc_id rv1126_venc_config-id; ret RK_MPI_VENC_CreateChn(rv1126_venc_config-id, venc_chn_attr); if (ret ! 0) { printf(create rv1126_venc_module failed\n); return -1; } else { printf(create rv1126_venc_module success\n); } return 0; } int main() { ...... RV1126_VENC_CONFIG rkmedia_venc_config {0}; memset(rkmedia_venc_config, 0, sizeof(rkmedia_venc_config)); rkmedia_venc_config.id 0; rkmedia_venc_config.attr.stVencAttr.enType RK_CODEC_TYPE_H264; // 编码器协议类型 rkmedia_venc_config.attr.stVencAttr.imageType IMAGE_TYPE_NV12; // 输入图像类型 rkmedia_venc_config.attr.stVencAttr.u32PicWidth 1920; // 编码图像宽度 rkmedia_venc_config.attr.stVencAttr.u32PicHeight 1080; // 编码图像高度 rkmedia_venc_config.attr.stVencAttr.u32VirWidth 1920; // 编码图像虚宽度一般来说u32VirWidth和u32PicWidth是一致的 rkmedia_venc_config.attr.stVencAttr.u32VirHeight 1080; // 编码图像虚高度一般来说u32VirHeight和u32PicHeight是一致的 rkmedia_venc_config.attr.stVencAttr.u32Profile 66; // 编码等级H.264: 66: Baseline; 77:Main Profile; 100:High Profile; H.265: default:Main; Jpege/MJpege: default:Baseline(编码等级的作用主要是改变画面质量66的画面质量最差利于网络传输100的质量最好) rkmedia_venc_config.attr.stRcAttr.enRcMode VENC_RC_MODE_H264CBR; // 编码器码率控制模式 rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32Gop 25; // GOPSIZE:关键帧间隔 rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32BitRate 1920 * 1080 * 3; // 码率 rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen 1; // 目的帧率分子:填的是1固定 rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum 25; // 目的帧率分母:填的是25固定 rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen 1; // 源头帧率分子:填的是1固定 rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum 25; // 源头帧率分母:填的是25固定 ret rkmedia_venc_init(rkmedia_venc_config); // VENC模块的初始化 ..... }在设置低分辨率之前要设置RGA模块的初始化这样才能将原始Vi模块的1920*1080的分辨率视频信息转成1280*720的分辨率信息。RGA:// RGA RGA_ATTR_S rga_info; /**Image Input ..............*/ rga_info.stImgIn.u32Width 1920; // 设置RGA输入分辨率宽度 rga_info.stImgIn.u32Height 1080; // 设置RGA输入分辨率高度 rga_info.stImgIn.u32HorStride 1920; // 设置RGA输入分辨率虚宽 rga_info.stImgIn.u32VirStride 1080; // 设置RGA输入分辨率虚高 rga_info.stImgIn.imgType IMAGE_TYPE_NV12; // 设置ImageType图像类型 rga_info.stImgIn.u32X 0; // 设置X坐标 rga_info.stImgIn.u32Y 0; // 设置Y坐标 /**Image Output......................*/ rga_info.stImgOut.u32Width 1280; // 设置RGA输出分辨率宽度 rga_info.stImgOut.u32Height 720; // 设置RGA输出分辨率高度 rga_info.stImgOut.u32HorStride 1280; // 设置RGA输出分辨率虚宽 rga_info.stImgOut.u32VirStride 720; // 设置RGA输出分辨率虚高 rga_info.stImgOut.imgType IMAGE_TYPE_NV12; // 设置输出ImageType图像类型 rga_info.stImgOut.u32X 0; // 设置X坐标 rga_info.stImgOut.u32Y 0; // 设置Y坐标 // RGA Public Parameter rga_info.u16BufPoolCnt 3; // 缓冲池计数 rga_info.u16Rotaion 0; // rga_info.enFlip RGA_FLIP_H; rga_info.bEnBufPool RK_TRUE; ret RK_MPI_RGA_CreateChn(0, rga_info); if (ret) { printf(RGA Set Failed.....\n); } else { printf(RGA Set Success.....\n); }低分辨率VENC模块RV1126_VENC_CONFIG low_rkmedia_venc_config {0}; memset(low_rkmedia_venc_config, 0, sizeof(low_rkmedia_venc_config)); low_rkmedia_venc_config.id 1; low_rkmedia_venc_config.attr.stVencAttr.enType RK_CODEC_TYPE_H264; // 编码器协议类型 low_rkmedia_venc_config.attr.stVencAttr.imageType IMAGE_TYPE_NV12; // 输入图像类型 low_rkmedia_venc_config.attr.stVencAttr.u32PicWidth 1280; // 编码图像宽度 low_rkmedia_venc_config.attr.stVencAttr.u32PicHeight 720; // 编码图像高度 low_rkmedia_venc_config.attr.stVencAttr.u32VirWidth 1280; // 编码图像虚宽度一般来说u32VirWidth和u32PicWidth是一致的 low_rkmedia_venc_config.attr.stVencAttr.u32VirHeight 720; // 编码图像虚高度一般来说u32VirHeight和u32PicHeight是一致的 low_rkmedia_venc_config.attr.stVencAttr.u32Profile 66; // 编码等级H.264: 66: Baseline; 77:Main Profile; 100:High Profile; H.265: default:Main; Jpege/MJpege: default:Baseline(编码等级的作用主要是改变画面质量66的画面质量最差利于网络传输100的质量最好) low_rkmedia_venc_config.attr.stRcAttr.enRcMode VENC_RC_MODE_H264CBR; // 编码器码率控制模式 low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32Gop 30; // GOPSIZE:关键帧间隔 low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32BitRate 1280 * 720 * 3; // 码率 low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen 1; // 目的帧率分子:填的是1固定 low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum 25; // 目的帧率分母:填的是25固定 low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen 1; // 源头帧率分子:填的是1固定 low_rkmedia_venc_config.attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum 25; // 源头帧率分母:填的是25固定 ret rkmedia_venc_init(low_rkmedia_venc_config); // VENC模块的初始化上面就是所有项目中要用到的RKmedia模块的初始化和使能创建为了项目的可读性可以将上述的函数和结构体写在同一个函数中在主函数中调用它就行了。二.FFmpeg初始化实列代码只有高分辨率的码流的FFmpeg初始化实列实际还有低分辨率码流的初始化FFmpeg的初始化也同上面Vi模块和VENC模块一样使用结构体RKMEDIA_FFMPEG_CONFIG *来进行管理typedef struct { int width; int height; unsigned int config_id; int protocol_type; //流媒体TYPE char network_addr[NETWORK_ADDR_LENGTH];//流媒体地址 enum AVCodecID video_codec; //视频编码器ID enum AVCodecID audio_codec; //音频编码器ID OutputStream video_stream; //VIDEO的STREAM配置 OutputStream audio_stream; //AUDIO的STREAM配置 AVFormatContext *oc; //是存储音视频封装格式中包含的信息的结构体也是FFmpeg中统领全局的结构体对文件的封装、编码操作从这里开始。 } RKMEDIA_FFMPEG_CONFIG; //FFMPEG配置将上面结构体信息复制完毕之后就要对FFmpeg模块来进行初始化了主要流程包括创建全局上下文结构体oc -设置编码器参数 -新建并添加视频流 -设置视频流参数 -打开io网络文件 -写视频头1.先判断是不是以RTMP协议来发送的注此项目只使用了FLV格式来分装视频编码资源用RTMP协议来进行发送然后创建一个FFmpeg的全局上下文变量//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); 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; } }2.设置oc中的AVoutputformat 结构体 中的音频和视频的编码器参数fmt ffmpeg_config-oc-oformat; /*指定编码器*/ fmt-video_codec ffmpeg_config-video_codec; fmt-audio_codec ffmpeg_config-audio_codec;3.创建并添加一个视频流ost-stream avformat_new_stream(oc, NULL); if (!ost-stream) { printf(Cant not avformat_new_stream\n); return 0; } else { printf(Success avformat_new_stream\n); }4.对不同的编码参数音频和视频来设置不同的视频流编码参数这里采用先对Avcodeccontext *结构体赋值再用函数将设置好的参数复制到视频流中的参数中去。*codec avcodec_find_encoder(codec_id); if (!(*codec)) { printf(Cant not find any encoder); return 0; } else { printf(Success find encoder); } //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(Cant not allocate context3\n); return 0; } else { printf(Success allocate context3); } 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音频时间基 avcodec_parameters_from_context(ost-stream-codecpar, c); 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;//图像格式 avcodec_parameters_from_context(ost-stream-codecpar, c); break; default: break; }5.因为这里并不用FFmpeg的编码功能所以并不用对其编码器进行初始化和使能。直接打开io网络文件准备进行发送编码数据。要先判断他是不是自动分配网络io文件不是的话要主动为他分配网络文件。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; } }6.写文件编码头avformat_write_header(ffmpeg_config-oc, NULL);三.绑定1.vi和venc模块绑定以便输出高分辨码流printf(Bind Before...\n); MPP_CHN_S vi_channel; MPP_CHN_S venc_channel; //绑定VI和VENC节点 ret RK_MPI_SYS_Bind(vi_channel, venc_channel); if (ret ! 0) { printf(bind venc error\n); return -1; } else { printf(bind venc success\n); }2.vi和rga模块绑定以便将高分辨像素信息转换成低分辨像素信息MPP_CHN_S vi_channel; MPP_CHN_S rga_channel; MPP_CHN_S low_venc_channel; ret RK_MPI_SYS_Bind(vi_channel, rga_channel); if (ret ! 0) { printf(vi bind rga error\n); return -1; } else { printf(vi bind rga success\n); }四.多线程发送编码视频数据包此项目要创建五个线程来完成此部分的内容。1.采集VENC模块的高分辨率H.264码流并且将码流存储到队列中等待发送。2.采集RGA模块将高分辨率数据信息转换成低分辨率的数据信息并且将码流发送到对应的VENC管道中进行编码。3.采集VENC模块的低分辨率H.264码流并且将码流存储到队列中等待发送。4.将队列中的高分辨码流信息传送到Nginx服务器上去。5.将队列中的低分辨码流信息传送到Nginx服务器上去。注意这里利用不同数据包结构体avpacket中的streamdex来区分属于不同的数据流的。将读取到的硬件编码码流数据存储到video_data_packet_t这个自定义结构体中去typedef struct _video_data_packet_t { unsigned char buffer[MAX_VIDEO_BUFFER_SIZE]; int video_frame_size; int frame_flag; }video_data_packet_t;再将自定义结构体插入VIDEO_QUEUE队列当中线程之间的管理使用的标准的生产者-消费者模型#include ffmpeg_video_queue.h //VIDEO队列的构造器包含mutex的初始化和条件变量初始化 VIDEO_QUEUE::VIDEO_QUEUE() { pthread_mutex_init(videoMutex, NULL);//mutex的初始化 pthread_cond_init(videoCond, NULL);//条件变量初始化 } //VIDEO队列的析构函数锁的销毁和条件变量的销毁 VIDEO_QUEUE ::~VIDEO_QUEUE() { pthread_mutex_destroy(videoMutex);//锁的销毁 pthread_cond_destroy(videoCond);//条件变量的销毁 } //VIDEO_QUEUE的插入视频队列操作 int VIDEO_QUEUE::putVideoPacketQueue(video_data_packet_t *video_packet) { pthread_mutex_lock(videoMutex); //上视频锁 video_packet_queue.push(video_packet);//向视频队列插入video_data_packet_t包 pthread_cond_broadcast(videoCond);//唤醒视频队列 pthread_mutex_unlock(videoMutex);//解视频锁 return 0; } //VIDEO_QUEUE取出视频包 video_data_packet_t *VIDEO_QUEUE::getVideoPacketQueue() { pthread_mutex_lock(videoMutex);//上视频锁 while (video_packet_queue.size() 0) { pthread_cond_wait(videoCond, videoMutex); //当视频队列没有数据的时候等待被唤醒 } video_data_packet_t *item video_packet_queue.front();//把视频数据包移到最前面 video_packet_queue.pop();//pop取出视频数据并删除 pthread_mutex_unlock(videoMutex);//解视频锁 return item; }代码实现1.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); }2.RGA转换后的数据通过RK_MPI_SYS_SendMediaBuffer函数发送到VENC对应的分辨率编码管道中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; }3.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, 1, -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); }4.在使用FFmpeg推流的时候要再次用av_buffer_realloc这个函数来根据数据包的大小个AVpacket中的buf来分配空间并且要将码流字节拷贝到buf中去在将AVpacket结构体中的data指针指向AVpacket中buf中的data指针这个是FFmpeg规定的。并且一般来说要将AVpacket结构体中的Flags赋值为关键帧。还要对packet中的时间基使用av_packet_rescale_ts这个函数来进行时间基转换。***对于视频数据推流来说时间戳是根据帧率来不断加一的FFmpeg 要求所有AVPacket承载的数据内存都必须通过AVBufferRef来管理而不能由外部随意分配并直接赋值给data。这样做的好处是自动内存释放当最后一个引用被释放时例如av_packet_unrefAVBufferRef会自动释放内存避免泄漏。共享零拷贝多个AVPacket可以引用同一个缓冲区通过av_packet_ref增加引用计数而不复制数据。线程安全缓冲区有引用计数可以在多线程间安全传递。AVPacket *get_high_ffmpeg_video_avpacket(AVPacket *pkt) { video_data_packet_t *video_data_packet high_video_queue-getVideoPacketQueue(); // 从视频队列获取数据 if (video_data_packet ! NULL) { /* 重新分配给定的缓冲区 1. 如果入参的 AVBufferRef 为空直接调用 av_realloc 分配一个新的缓存区并调用 av_buffer_create 返回一个新的 AVBufferRef 结构 2. 如果入参的缓存区长度和入参 size 相等直接返回 0 3. 如果对应的 AVBuffer 设置了 BUFFER_FLAG_REALLOCATABLE 标志或者不可写再或者 AVBufferRef data 字段指向的数据地址和 AVBuffer 的 data 地址不同递归调用 av_buffer_realloc 分配一个新 的 buffer并将 data 拷贝过去 4. 不满足上面的条件直接调用 av_realloc 重新分配缓存区。 */ int ret av_buffer_realloc(pkt-buf, video_data_packet-video_frame_size 70); if (ret 0) { return NULL; } pkt-size video_data_packet-video_frame_size; // rv1126的视频长度赋值到AVPacket Size memcpy(pkt-buf-data, video_data_packet-buffer, video_data_packet-video_frame_size); // rv1126的视频数据赋值到AVPacket data pkt-data pkt-buf-data; // 把pkt-buf-data赋值到pkt-data pkt-flags | AV_PKT_FLAG_KEY; // 默认flags是AV_PKT_FLAG_KEY if (video_data_packet ! NULL) { free(video_data_packet); video_data_packet NULL; } return pkt; } else { return NULL; } }int write_ffmpeg_avpacket(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt) { /*将输出数据包时间戳值从编解码器重新调整为流时基 */ av_packet_rescale_ts(pkt, *time_base, st-time_base); pkt-stream_index st-index; return av_interleaved_write_frame(fmt_ctx, pkt); }int deal_high_video_avpacket(AVFormatContext *oc, OutputStream *ost) { int ret; AVCodecContext *c ost-enc; AVPacket *video_packet get_high_ffmpeg_video_avpacket(ost-packet); // 从RV1126视频编码数据赋值到FFMPEG的Video AVPacket中 if (video_packet ! NULL) { video_packet-pts ost-next_timestamp; // VIDEO_PTS按照帧率进行累加 } ret write_ffmpeg_avpacket(oc, c-time_base, ost-stream, video_packet); // 向复合流写入视频数据 if (ret ! 0) { printf(write video avpacket error); return -1; } return 0; }5.与上述的代码基本一致AVPacket *get_low_ffmpeg_video_avpacket(AVPacket *pkt) { video_data_packet_t *video_data_packet low_video_queue-getVideoPacketQueue(); // 从视频队列获取数据 if (video_data_packet ! NULL) { /* 重新分配给定的缓冲区 1. 如果入参的 AVBufferRef 为空直接调用 av_realloc 分配一个新的缓存区并调用 av_buffer_create 返回一个新的 AVBufferRef 结构 2. 如果入参的缓存区长度和入参 size 相等直接返回 0 3. 如果对应的 AVBuffer 设置了 BUFFER_FLAG_REALLOCATABLE 标志或者不可写再或者 AVBufferRef data 字段指向的数据地址和 AVBuffer 的 data 地址不同递归调用 av_buffer_realloc 分配一个新 的 buffer并将 data 拷贝过去 4. 不满足上面的条件直接调用 av_realloc 重新分配缓存区。 */ int ret av_buffer_realloc(pkt-buf, video_data_packet-video_frame_size 70); if (ret 0) { return NULL; } pkt-size video_data_packet-video_frame_size; // rv1126的视频长度赋值到AVPacket Size memcpy(pkt-buf-data, video_data_packet-buffer, video_data_packet-video_frame_size); // rv1126的视频数据赋值到AVPacket data pkt-data pkt-buf-data; // 把pkt-buf-data赋值到pkt-data pkt-flags | AV_PKT_FLAG_KEY; // 默认flags是AV_PKT_FLAG_KEY if (video_data_packet ! NULL) { free(video_data_packet); video_data_packet NULL; } return pkt; } else { return NULL; } }int deal_low_video_avpacket(AVFormatContext *oc, OutputStream *ost) { int ret; AVCodecContext *c ost-enc; AVPacket *video_packet get_low_ffmpeg_video_avpacket(ost-packet); // 从RV1126视频编码数据赋值到FFMPEG的Video AVPacket中 if (video_packet ! NULL) { video_packet-pts ost-next_timestamp; // VIDEO_PTS按照帧率进行累加 } ret write_ffmpeg_avpacket(oc, c-time_base, ost-stream, video_packet); // 向复合流写入视频数据 if (ret ! 0) { printf(write video avpacket error); return -1; } return 0; }五.收尾工作.发送视频流尾部释放资源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;

相关新闻