Webrtc新增FFmpeg视频编解码模块

1 整体描述

目前webrtc内置的视频编解码器包括:VP8、VP9、AV1和H264。一般情况下载pc端基本可以满足大部分的需求,但是有时候为了进行编解码器的扩展包括支持H265或者是支持硬件编解码以提升效率时需要新增编解码模块。
在这里插入图片描述

2 新增外部编码器

编码器实现的要点包括两个部分:
一是需要实现以VideoEncoder为基类的编码器对象,核心API实现如下:
(1)初始化编码器,将编码参数传入进行初始化。

virtual int InitEncode(const VideoCodec* codec_settings,
                         const VideoEncoder::Settings& settings);

(2)回调函数的注册,用于编码后数据的回传。

virtual int32_t RegisterEncodeCompleteCallback(
      EncodedImageCallback* callback) = 0;

(3)编码函数,将视频帧数据传入到解码器中,这个函数是实际编码接口;外部数据输入包括VideoFrame数据帧一般是kI420数据和帧类型(I帧、P帧…)

virtual int32_t Encode(const VideoFrame& frame,
                         const std::vector<VideoFrameType>* frame_types) = 0;

(4) QOS相关控制,设置码率和帧率等操作。

void SetRates(const RateControlParameters& parameters) override;

二是需要实现EncodedImageCallback回调基类来处理,编码后的数据回传到视频引擎。核心函数就是将编码后的数据转成EncodedImage结构回传到视频引擎。
在这里插入图片描述

2.1 编码器初始化

编码器的初始化主要是对编码器的基础参数包括分辨率,码率和荷载等基本数据,我们在完成编码器的初始化基本需要完成编码对象的创建和编码通道的创建等操作。这部分工作主要是在InitEncode完成,需要注意的是初始化函数可能被调用多次,因此这里需要保护防止多次创建造成,内存泄漏和系统资源冲突等问题。
以FFmpeg初始化编码器为例,基本需要包括四部分一是进行编码器的选择,根据codec类型, 寻找并创建编码器的上下文;第二是编码参数的初始化,根据上层传入的用户配置参数及自身对编码器的理解, 对所使用编码器参数进行的配置,可以进行参数的优化提高编码质量和用户体验;第三是编码帧的初始化,最后是开启编码器。
在这里插入图片描述

//编码器
AVCodec *avcodec_find_encoder(enum AVCodecID id);
//上下文获取
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
//初始化视频帧
AVFrame *avcodec_alloc_frame(void);
//开启编码器
int avcodec_open(AVCodecContext *avctx, AVCodec *codec);

2.2 编码实现

编码阶段需要理解两方面数据,一方面是输入数据VideoFrame为原始的图像帧,一般情况为I420,我们需要进行内部转换转成FFmpeg编码的输入参数。
以FFmpeg为例,解码过程分为三步:一是将输入数据转换为I420数据,然后利用类内部接口获取YUV数据并组装成编码器需要的数据;
二是编码数据,一般是调用FFmpeg编码函数编码,内部会进一步根据不同编码类型进行编码;
三是编码结束后,需要根据重新将编码后的数据进行组装得到一个EncodedImage结构体,并且调用回调函数将结果返回给视频引擎处理。
在这里插入图片描述

//将原始视频数据,转成I420格式
input_frame.video_frame_buffer()->ToI420()
//获取YUV数据
frame_buffer->DataY();
//图片填充
int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
                   enum AVPixelFormat pix_fmt, int width, int height);
//视频编码
int avcodec_encode_video(AVCodecContext *avctx, uint8_t *buf, int buf_size,
                         const AVFrame *pict);
//回调,数据返回
Encoded(EncodedImage& encodedImage,
            const CodecSpecificInfo* codecSpecificInfo = NULL,
            const RTPFragmentationHeader* fragmentation = NULL) = 0;

3 新增外部解码器

解码器实现与编码器类似,实现的要点包括两个部分:
一是需要实现以VideoDecoder为基类的解码器对象,核心API实现如下:
(1)初始化解码器,将解码参数传入进行初始化。

virtual bool Configure(const Settings& settings) = 0;

(2)回调函数的注册,用于解码后数据的回传。

virtual int32_t Decode(const EncodedImage& input_image,
                         bool missing_frames,
                         int64_t render_time_ms) = 0;

(3)解码函数,将编码视频帧数据传入到解码器中,这个函数是实际解码接口;外部数据输入包括EncodedImage数据帧,渲染时间和是否丢帧等。

virtual int32_t Decode(const EncodedImage& input_image,
                         bool missing_frames,
                         int64_t render_time_ms) = 0;

(4) 与视频编码不一样的配置函数,包括视频的解码缓冲区大小和渲染分辨率的设置。

//设置解码缓冲区大小
void set_buffer_pool_size(absl::optional<int> value);
//设置渲染分辨率
void set_max_render_resolution(RenderResolution value);

二是需要实现DecodedImageCallback回调基类来处理,解码后的数据回传到视频引擎。核心函数就是将编码后的数据转成原始视频帧VideoFrame结构回传到视频引擎。
在这里插入图片描述

3.1 解码器初始化

解码器的初始化主要是对解码器的基础参数包括分辨率,码率和荷载等基本数据,我们在完成解码器的初始化基本需要完成解码对象的创建和解码通道的创建等操作。这部分工作主要是在Configure完成,需要注意的是初始化函数可能被调用多次,因此这里需要保护防止多次创建造成,内存泄漏和系统资源冲突等问题。
以FFmpeg初始化解码器为例,基本需要包括四部分第一是解码参数的初始化,根据上层传入的用户配置参数及自身对解码器的理解, 对所使用解码器参数进行的配置,可以进行参数的优化提高解码质量和用户体验;二是进行解码器的选择,根据codec类型, 寻找并创建解码器的上下文;第三是开启解码器,传入上下文配置和解码器;第四部分是分配一个解码帧,用于后续解码使用。
在这里插入图片描述

//上下文获取
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
//解码器
AVCodec *avcodec_find_decoder(enum AVCodecID id);
//开启解码器
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
//初始化视频帧
AVFrame *avcodec_alloc_frame(void);

3.2 解码实现

解码阶段需要理解两方面数据,一方面是输入数据EncodedImage为编码后的数据,需要转换会解码器需要的帧结构,然后进行解码操作;第二是解码后数据解释为VideoFrame帧后回调给视频引擎。
以FFmpeg为例,解码过程分为四步:一是将EncodedImage结构视频帧转换为AVPacket结构;第二是将配置上下文和AVPacket传入解码器解码;第三是获取解码后视频帧存入AVFrame中;第四是需要根据重新将解码后的数据进行组装得到一个VideoFrame结构体,并且调用回调函数将结果返回给视频引擎处理。
在这里插入图片描述

//分配一个packet空间
AVPacket *av_packet_alloc(void);
//将编码数据加入到解码队列
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
//获取解码后数据帧
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
//释放packet
void av_packet_free(AVPacket **pkt);

4 总结

webrtc编解码模块设计有较强的对称性,其实在发送和接收,音频和视频,编码和解码等等都在设计上比较接近,因此需要在学习的时候相互借鉴和理解。整个编解码模块的添加相对难度较低,主要是理解输入和输出的数据结构,可以将webrtc的数据结构转换为编解码器内部能够理解的数据,达到将数据编解码的目的。基本的设计理念就是定义好标准的输入和输出,屏蔽内部的处理细节,达到模块之间的相互独立。

### RuoYi与RTMP集成实现 #### 背景介绍 RuoYi 是一款基于 Spring Boot 的开源后台管理系统框架,提供了丰富的功能模块和良好的扩展性。而 RTMP (Real-Time Messaging Protocol) 则是一种用于页面低延迟数据传输的应用层协议,主要用于音视频直播流媒体服务。 #### 集成需求分析 为了使 RuoYi 支持实时音视频处理能力,在项目中引入 RTMP 协议支持成为必要选项之一。这不仅能够增强系统的多媒体交互特性,还能满足诸如在线教育、远程会议等应用场景下的业务诉求[^1]。 #### 技术选型考量 针对上述目标,可以考虑采用 Nginx + FFmpeg 方案来完成 RTMP 流服务器搭建工作;与此同时,借助 WebRTC 实现浏览器端推拉流操作,从而形成完整的解决方案架构图如下所示: - **Nginx**: 提供高效的 HTTP 和 RTMP 请求转发机制; - **FFmpeg**: 处理视音频编解码转换任务; - **WebRTC**: 构建 P2P 连接模式下客户端之间的直接通信链路[^2]。 ```mermaid graph LR; A[Browser Client(WebRTC)] --> B{Nginx(RTMP)}; C[RuoYi Backend(Spring Boot)] -.->|API Calls.| D{Database}; E[Media Source(Video/Audio)] --> F{FFmpeg(Coding/Decoding)} --> G[Nginx]; ``` #### 关键组件配置说明 ##### 安装并启用 NGINX RTMP 模块 通过安装 `nginx-rtmp-module` 插件给标准版 Nginx 添加对 RTMP 协议的支持,具体命令如下: ```bash sudo apt-get install nginx libnginx-mod-rtmp ffmpeg ``` 编辑 `/etc/nginx/nginx.conf` 文件加入 rtmp 块定义推送地址等相关参数设置[^3]: ```nginx rtmp { server { listen 1935; # 监听默认 RTMP 端口 chunk_size 4096; application live { live on; record off; push rtmp://your-push-server/live; } } } ``` ##### 开发前端播放器界面 利用 HTML5 `<video>` 标签配合 JavaScript 库如 Plyr.js 或 Video.js 来创建简易的网页播放控件,允许用户观看由后端推送过来的直播内容[^4]: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Live Stream Player</title> <link rel="stylesheet" href="https://cdn.plyr.io/3.6.7/plyr.css"/> <script src="https://cdn.plyr.io/3.6.7/plyr.polyfilled.min.js"></script> <style>.plyr__poster {background-image:url('cover.jpg')}</style> </head> <body> <div id="player" data-plyr-provider="youtube" data-plyr-embed-id=""></div> <script type="text/javascript"> const player = new Plyr('#player', { type: 'rtmp', source: { src: 'rtmp://localhost/live/stream_key', provider: 'rtmp' }, }); </script> </body> </html> ``` #### 后端 API 设计要点 考虑到安全性因素以及便于管理维护的目的,建议在 RuoYi 中新增一套 RESTful 接口用来控制 RTMP 流的状态变更(启动/停止)、获取当前正在运行中的频道列表等功能,并确保这些接口仅限于授权后的合法调用者访问[^5].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值