基本概念
给视频添加滤镜的方法,是在视频帧被编码前对其进行处理。有时我们还需要在视频帧被编码后对其进行处理,插入一些自定义数据,比如插入 H.264 定义的 SEI(Supplemental Enhancement Information,补充增强信息)。
此时便可以使用 WebRTC Insertable Streams API,这组 API 在 WebRTC M83 (branch-heads/4103) 版本被引入,会分别在视频帧被编码后且发送前、或者被接收后且解码前被调用。由于笔者是 Native 开发,因此这里直接给出 C++ 代码;前端开发者则可以直接参照 W3C 对应文档。
源代码解析
首先我们需要继承并实现 webrtc::FrameTransformerInterface
,一个典型模版如下:
ExampleTransformer.cpp
namespace example {
// 在头文件中定义
// class ExampleTransformer : public webrtc::FrameTransformerInterface {
// public:
// virtual void RegisterTransformedFrameSinkCallback(
// rtc::scoped_refptr<webrtc::TransformedFrameCallback>, uint32_t ssrc) override;
// virtual void UnregisterTransformedFrameSinkCallback(uint32_t ssrc) override;
// virtual void Transform(std::unique_ptr<webrtc::TransformableFrameInterface> frame) override;
// private:
// mutable webrtc::Mutex mutex_;
// rtc::scoped_refptr<webrtc::TransformedFrameCallback> sink_callback_;
// }
// WebRTC 只会在 webrtc::RTPSenderVideo 的构造方法中注册一个 callback,
// 具体可以参见 rtp_sender_video.cc frame_transformer_delegate_->Init()
void ExampleTransformer::RegisterTransformedFrameSinkCallback(
rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback, uint32_t ssrc) {
webrtc::MutexLock lock(&mutex_);
sink_callback_ = callback;
}
void ExampleTransformer::UnregisterTransformedFrameSinkCallback(uint32_t ssrc) {
webrtc::MutexLock lock(&mutex_);
sink_callback_ = nullptr;
}
void ExampleTransformer::Transform(std::unique_ptr<webrtc::TransformableFrameInterface> frame) {
webrtc::MutexLock lock(&mutex_);
if (sink_callback_ == nullptr) return;
// 在这里处理 frame 的二进制数据...
// 处理完毕后务必调用 sink_callback_ 将帧数据传递给上层
sink_callback_->OnTransformedFrame(std::move(frame));
}
}
如果你想要在视频帧被编码后且发送前被调用,可以通过 RtpSender 进行设置:
rtp_sender_interface.h
class RTC_EXPORT RtpSenderInterface : public rtc::RefCountInterface {
public:
// other definitions...
// 设置的 frame_transformer 会在视频帧被编码后且发送前调用
virtual void SetEncoderToPacketizerFrameTransformer(
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer);
// other definitions...
}
如果你想要在视频帧被接收后且解码前被调用,可以通过 RtpReceiver 进行设置:
rtp_receiver_interface.h
class RTC_EXPORT RtpReceiverInterface : public rtc::RefCountInterface {
public:
// other definitions...
// 设置的 frame_transformer 会在视频帧被接收后且解码前被调用
virtual void SetDepacketizerToDecoderFrameTransformer(
rtc::scoped_refptr<FrameTransformerInterface> frame_transformer);
// other definitions...
}
最后,这组 API 也同样适用于音频,因此读者需要注意 transform 过程中 frame 的具体类型。如果是视频帧数据,实际类型为 webrtc::TransformableVideoFrameInterface
;如果是音频帧数据,则实际类型为 webrtc::TransformableAudioFrameInterface
。
在我们的流式中真实的源代码如下:
这是使用的入口,在Addtrack之后
//设置可插入流处理类
auto rtcSender = result_or_error.value();
if (rtcSender)
{
rtc::scoped_refptr<StreamDataTransformer> myTransformer(new rtc::RefCountedObject<StreamDataTransformer>("TestName"));
rtcSender->SetEncoderToPacketizerFrameTransformer(myTransformer);
}
else
{
QL_ERROR("Invalid rtcSender");
}
真实处理逻辑的类封装中的头文件
#pragma once
#include "api/frame_transformer_interface.h"
namespace QL
{
class StreamDataTransformer : public webrtc::FrameTransformerInterface
{
public:
StreamDataTransformer(std::string trackId);
// Transforms |frame| using the implementing class' processing logic.
virtual void Transform(
std::unique_ptr<webrtc::TransformableFrameInterface> transformable_frame);
virtual void RegisterTransformedFrameCallback(rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback);
virtual void RegisterTransformedFrameSinkCallback(rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback, uint32_t ssrc);
virtual void UnregisterTransformedFrameCallback();
virtual void UnregisterTransformedFrameSinkCallback(uint32_t ssrc);
//virtual void AddRef() const override;
//virtual rtc::RefCountReleaseStatus Release() const override;
private:
std::string mCellName;
rtc::scoped_refptr<webrtc::TransformedFrameCallback> sink_callback_;
};
}
Cpp文件
#include "StreamDataTransformer.h"
namespace QL
{
StreamDataTransformer::StreamDataTransformer(std::string cellName)
{
this->mCellName = cellName;
}
/// <summary>
///
/// </summary>
/// <param name="transformable_frame"></param>
void StreamDataTransformer::Transform(std::unique_ptr<webrtc::TransformableFrameInterface> transformable_frame)
{
const int ourAdditionalDataLength = 32;
auto frameData = transformable_frame->GetData();
size_t dataSize = frameData.size();
//新的帧内容长度=原来的长+新增数据的长度
size_t sizeEx = dataSize + ourAdditionalDataLength;
uint8_t* finalFrameData = new uint8_t[sizeEx];
//复制原有数据
memcpy(finalFrameData, frameData.data(), dataSize);
// TODO End. Note the length of the frameid.
rtc::ArrayView<const uint8_t> frameDataEx(finalFrameData, sizeEx);
transformable_frame->SetData(frameDataEx);
if (this->sink_callback_) {
this->sink_callback_->OnTransformedFrame(std::move(transformable_frame));
}
delete[] finalFrameData;
finalFrameData = nullptr;
}
void StreamDataTransformer::RegisterTransformedFrameCallback(rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback)
{
this->sink_callback_ = callback;
}
void StreamDataTransformer::RegisterTransformedFrameSinkCallback(rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback, uint32_t ssrc)
{
this->sink_callback_ = callback;
}
void StreamDataTransformer::UnregisterTransformedFrameCallback()
{
this->sink_callback_ = nullptr;
}
void StreamDataTransformer::UnregisterTransformedFrameSinkCallback(uint32_t ssrc)
{
this->sink_callback_ = nullptr;
}
}
要注意的
- 可插入帧是在帧的数据都处理完后扔出去的时候最终处理的,所以这里你可以做类似加密,压缩等行为。
- 但是此时,已经没有办法找到对应之前数据的内容了,因此你不可以在这个阶段做对齐的操作。
- 可插入帧的这个能力,并没有在所有的web浏览器下支持,请注意检查兼容性问题。
其余的可插入帧
“可插入帧”(插入帧或插入式编码)是一种增强视频编码和传输的技术,允许开发者在视频流中插入特定类型的帧,以实现各种目的,如提升视频质量、控制视频流的传输特性等。以下是对可插入帧能力的详细介绍以及其作用。
可插入帧的能力
- 插入关键帧(I帧)
- 定义:关键帧(I帧)是完整的图像帧,不依赖于其他帧。插入关键帧可以使视频流在某个时间点能够从该点完整解码。
- 作用:
- 恢复点:在网络条件不稳定或丢包的情况下,插入关键帧可以提供一个解码的恢复点。丢失数据包之后,如果接收到新的关键帧,解码器可以重新同步视频流。
- 快速跳转:在视频播放器中,可以快速跳转到关键帧,帮助用户实现快速的随机访问。
- 错误恢复:在发生传输错误时,通过插入关键帧,可以帮助恢复视频流的正常播放。
- 应用:例如,在直播流中,为了减少延迟和提高流的稳定性,可能会定期插入关键帧。或者,在需要保证视频质量的场景,如重要会议或高质量的视频会议中,也可以根据需要插入关键帧。
- 插入自适应帧
- 定义:自适应帧是根据网络状况或应用需求动态调整的帧。例如,根据带宽的变化调整帧率或质量。
- 作用:
- 适应网络条件:在带宽受限或网络条件不稳定时,插入自适应帧可以帮助调整视频流的质量,防止视频播放卡顿。
- 动态调整:根据网络条件或应用场景的变化动态调整帧类型和质量,以实现最佳的用户体验。
- 插入修正帧
- 定义:修正帧用于对视频流中的错误进行修正。例如,在检测到数据丢失或解码错误时,插入修正帧可以帮助纠正错误。
- 作用:
- 增强鲁棒性:提高视频流的鲁棒性,确保在发生数据丢失或网络波动时,视频流能够平滑播放。
- 错误修正:在视频流传输过程中,能够修正传输错误,减少视觉上的瑕疵。
插入帧的实现
在WebRTC中,可以通过以下方式实现插入帧的能力:
- 使用
RTCPeerConnection
API:可以控制视频流的编码器设置,比如设置关键帧的间隔等。例如:
const peerConnection = new RTCPeerConnection();
// 修改编码器设置以插入关键帧
const senders = peerConnection.getSenders();
for (const sender of senders) {
if (sender.track.kind === 'video') {
const parameters = sender.getParameters();
parameters.encodings.forEach(encoding => {
encoding.keyFrameRequestDelay = 1000; // 请求关键帧的延迟
});
sender.setParameters(parameters);
}
}
- 通过媒体流处理:使用Canvas API和WebRTC的
MediaStream
结合,可以实现帧的插入和处理。具体步骤包括:- 从视频流中获取帧。
- 在Canvas上处理帧并生成新的帧。
- 将处理后的帧重新插入到视频流中。
- 示例代码(前文已提供)演示了如何使用Canvas API对视频流应用滤镜。
- 自定义视频编码器:在某些情况下,可能需要使用自定义的视频编码器来实现特定的帧插入策略。这通常涉及到更底层的操作,可能需要对视频编码库进行定制或使用专业的编码工具。
总结
可插入帧的能力在流式系统中具有重要作用,能够提升视频流的稳定性、质量和适应性。通过插入关键帧、自适应帧和修正帧等技术,可以实现更好的视频体验。流式系统提供了灵活的API和工具,使开发者能够根据需求控制视频流的行为和质量。
RA/SD 衍生者AI训练营。发布者:chris,转载请注明出处:https://www.shxcj.com/archives/5823