Skip to content

Commit 105a2f0

Browse files
committed
add support for raspberry pi 4 v4l2 video encoder
1 parent 1560ef0 commit 105a2f0

File tree

8 files changed

+1219
-0
lines changed

8 files changed

+1219
-0
lines changed

webrtc-sys/build.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,17 @@ fn main() {
204204
);
205205
}
206206

207+
if arm {
208+
// V4L2 M2M H.264 encoder support (RPi 4, Rockchip, etc.)
209+
// Uses only kernel headers (linux/videodev2.h) and POSIX APIs,
210+
// no external library dependencies needed.
211+
builder
212+
.file("src/v4l2/v4l2_h264_encoder_wrapper.cpp")
213+
.file("src/v4l2/v4l2_encoder_factory.cpp")
214+
.file("src/v4l2/h264_encoder_impl.cpp")
215+
.flag("-DUSE_V4L2_VIDEO_CODEC=1");
216+
}
217+
207218
if x86 || arm {
208219
let cuda_home = PathBuf::from(match env::var("CUDA_HOME") {
209220
Ok(p) => p,
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/*
2+
* Copyright 2025 LiveKit, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "h264_encoder_impl.h"
18+
19+
#include <algorithm>
20+
#include <limits>
21+
#include <string>
22+
23+
#include "api/video/video_codec_constants.h"
24+
#include "common_video/libyuv/include/webrtc_libyuv.h"
25+
#include "modules/video_coding/include/video_codec_interface.h"
26+
#include "modules/video_coding/include/video_error_codes.h"
27+
#include "modules/video_coding/utility/simulcast_rate_allocator.h"
28+
#include "modules/video_coding/utility/simulcast_utility.h"
29+
#include "rtc_base/checks.h"
30+
#include "rtc_base/logging.h"
31+
#include "rtc_base/time_utils.h"
32+
#include "system_wrappers/include/metrics.h"
33+
34+
namespace webrtc {
35+
36+
// Used by histograms. Values of entries should not be changed.
37+
enum V4L2H264EncoderImplEvent {
38+
kV4L2H264EncoderEventInit = 0,
39+
kV4L2H264EncoderEventError = 1,
40+
kV4L2H264EncoderEventMax = 16,
41+
};
42+
43+
V4L2H264EncoderImpl::V4L2H264EncoderImpl(const webrtc::Environment& env,
44+
const SdpVideoFormat& format)
45+
: env_(env),
46+
encoder_(std::make_unique<livekit_ffi::V4l2H264EncoderWrapper>()),
47+
packetization_mode_(H264EncoderSettings::Parse(format).packetization_mode),
48+
format_(format) {}
49+
50+
V4L2H264EncoderImpl::~V4L2H264EncoderImpl() {
51+
Release();
52+
}
53+
54+
void V4L2H264EncoderImpl::ReportInit() {
55+
if (has_reported_init_)
56+
return;
57+
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.V4L2H264EncoderImpl.Event",
58+
kV4L2H264EncoderEventInit,
59+
kV4L2H264EncoderEventMax);
60+
has_reported_init_ = true;
61+
}
62+
63+
void V4L2H264EncoderImpl::ReportError() {
64+
if (has_reported_error_)
65+
return;
66+
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.V4L2H264EncoderImpl.Event",
67+
kV4L2H264EncoderEventError,
68+
kV4L2H264EncoderEventMax);
69+
has_reported_error_ = true;
70+
}
71+
72+
int32_t V4L2H264EncoderImpl::InitEncode(const VideoCodec* inst,
73+
const VideoEncoder::Settings& settings) {
74+
if (!inst || inst->codecType != kVideoCodecH264) {
75+
ReportError();
76+
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
77+
}
78+
if (inst->maxFramerate == 0) {
79+
ReportError();
80+
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
81+
}
82+
if (inst->width < 1 || inst->height < 1) {
83+
ReportError();
84+
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
85+
}
86+
87+
int32_t release_ret = Release();
88+
if (release_ret != WEBRTC_VIDEO_CODEC_OK) {
89+
ReportError();
90+
return release_ret;
91+
}
92+
93+
codec_ = *inst;
94+
95+
// Code expects simulcastStream resolutions to be correct, make sure they are
96+
// filled even when there are no simulcast layers.
97+
if (codec_.numberOfSimulcastStreams == 0) {
98+
codec_.simulcastStream[0].width = codec_.width;
99+
codec_.simulcastStream[0].height = codec_.height;
100+
}
101+
102+
// Initialize encoded image. Default buffer size: size of unencoded data.
103+
const size_t new_capacity =
104+
CalcBufferSize(VideoType::kI420, codec_.width, codec_.height);
105+
encoded_image_.SetEncodedData(EncodedImageBuffer::Create(new_capacity));
106+
encoded_image_._encodedWidth = codec_.width;
107+
encoded_image_._encodedHeight = codec_.height;
108+
encoded_image_.set_size(0);
109+
110+
configuration_.sending = false;
111+
configuration_.frame_dropping_on = codec_.GetFrameDropEnabled();
112+
configuration_.key_frame_interval = codec_.H264()->keyFrameInterval;
113+
114+
configuration_.width = codec_.width;
115+
configuration_.height = codec_.height;
116+
117+
configuration_.max_frame_rate = codec_.maxFramerate;
118+
configuration_.target_bps = codec_.startBitrate * 1000;
119+
configuration_.max_bps = codec_.maxBitrate * 1000;
120+
121+
if (!encoder_->IsInitialized()) {
122+
int keyFrameInterval = 60;
123+
if (codec_.maxFramerate > 0) {
124+
keyFrameInterval = codec_.maxFramerate * 5;
125+
}
126+
127+
if (!encoder_->Initialize(codec_.width, codec_.height,
128+
codec_.startBitrate * 1000, keyFrameInterval,
129+
codec_.maxFramerate)) {
130+
RTC_LOG(LS_ERROR) << "V4L2: Failed to initialize H.264 encoder";
131+
ReportError();
132+
return WEBRTC_VIDEO_CODEC_ERROR;
133+
}
134+
}
135+
136+
SimulcastRateAllocator init_allocator(env_, codec_);
137+
VideoBitrateAllocation allocation =
138+
init_allocator.Allocate(VideoBitrateAllocationParameters(
139+
DataRate::KilobitsPerSec(codec_.startBitrate), codec_.maxFramerate));
140+
SetRates(RateControlParameters(allocation, codec_.maxFramerate));
141+
return WEBRTC_VIDEO_CODEC_OK;
142+
}
143+
144+
int32_t V4L2H264EncoderImpl::RegisterEncodeCompleteCallback(
145+
EncodedImageCallback* callback) {
146+
encoded_image_callback_ = callback;
147+
return WEBRTC_VIDEO_CODEC_OK;
148+
}
149+
150+
int32_t V4L2H264EncoderImpl::Release() {
151+
if (encoder_->IsInitialized()) {
152+
encoder_->Destroy();
153+
}
154+
return WEBRTC_VIDEO_CODEC_OK;
155+
}
156+
157+
int32_t V4L2H264EncoderImpl::Encode(
158+
const VideoFrame& input_frame,
159+
const std::vector<VideoFrameType>* frame_types) {
160+
if (!encoder_ || !encoder_->IsInitialized()) {
161+
ReportError();
162+
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
163+
}
164+
if (!encoded_image_callback_) {
165+
RTC_LOG(LS_WARNING)
166+
<< "InitEncode() has been called, but a callback function "
167+
"has not been set with RegisterEncodeCompleteCallback()";
168+
ReportError();
169+
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
170+
}
171+
172+
scoped_refptr<I420BufferInterface> frame_buffer =
173+
input_frame.video_frame_buffer()->ToI420();
174+
if (!frame_buffer) {
175+
RTC_LOG(LS_ERROR) << "Failed to convert "
176+
<< VideoFrameBufferTypeToString(
177+
input_frame.video_frame_buffer()->type())
178+
<< " image to I420. Can't encode frame.";
179+
return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE;
180+
}
181+
RTC_CHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420 ||
182+
frame_buffer->type() == VideoFrameBuffer::Type::kI420A);
183+
184+
bool is_keyframe_needed = false;
185+
if (configuration_.key_frame_request && configuration_.sending) {
186+
is_keyframe_needed = true;
187+
}
188+
189+
bool send_key_frame =
190+
is_keyframe_needed ||
191+
(frame_types && (*frame_types)[0] == VideoFrameType::kVideoFrameKey);
192+
if (send_key_frame) {
193+
is_keyframe_needed = true;
194+
configuration_.key_frame_request = false;
195+
}
196+
197+
RTC_DCHECK_EQ(configuration_.width, frame_buffer->width());
198+
RTC_DCHECK_EQ(configuration_.height, frame_buffer->height());
199+
200+
if (!configuration_.sending) {
201+
return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
202+
}
203+
204+
if (frame_types != nullptr) {
205+
if ((*frame_types)[0] == VideoFrameType::kEmptyFrame) {
206+
return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
207+
}
208+
}
209+
210+
std::vector<uint8_t> output;
211+
bool encode_ok = encoder_->Encode(
212+
frame_buffer->DataY(), frame_buffer->DataU(), frame_buffer->DataV(),
213+
frame_buffer->StrideY(), frame_buffer->StrideU(), frame_buffer->StrideV(),
214+
send_key_frame, output);
215+
216+
if (!encode_ok || output.empty()) {
217+
RTC_LOG(LS_ERROR) << "V4L2: Failed to encode frame.";
218+
return WEBRTC_VIDEO_CODEC_ERROR;
219+
}
220+
221+
encoded_image_.SetEncodedData(
222+
EncodedImageBuffer::Create(output.data(), output.size()));
223+
224+
h264_bitstream_parser_.ParseBitstream(encoded_image_);
225+
226+
encoded_image_.qp_ = h264_bitstream_parser_.GetLastSliceQp().value_or(-1);
227+
228+
encoded_image_._encodedWidth = configuration_.width;
229+
encoded_image_._encodedHeight = configuration_.height;
230+
encoded_image_.SetRtpTimestamp(input_frame.rtp_timestamp());
231+
encoded_image_.SetColorSpace(input_frame.color_space());
232+
encoded_image_._frameType = send_key_frame ? VideoFrameType::kVideoFrameKey
233+
: VideoFrameType::kVideoFrameDelta;
234+
235+
CodecSpecificInfo codec_specific;
236+
codec_specific.codecType = kVideoCodecH264;
237+
codec_specific.codecSpecific.H264.packetization_mode = packetization_mode_;
238+
codec_specific.codecSpecific.H264.temporal_idx = kNoTemporalIdx;
239+
codec_specific.codecSpecific.H264.base_layer_sync = false;
240+
codec_specific.codecSpecific.H264.idr_frame = send_key_frame;
241+
242+
encoded_image_callback_->OnEncodedImage(encoded_image_, &codec_specific);
243+
244+
return WEBRTC_VIDEO_CODEC_OK;
245+
}
246+
247+
VideoEncoder::EncoderInfo V4L2H264EncoderImpl::GetEncoderInfo() const {
248+
EncoderInfo info;
249+
info.supports_native_handle = false;
250+
info.implementation_name = "V4L2 H264 Encoder";
251+
info.scaling_settings = VideoEncoder::ScalingSettings::kOff;
252+
info.is_hardware_accelerated = true;
253+
info.supports_simulcast = false;
254+
info.preferred_pixel_formats = {VideoFrameBuffer::Type::kI420};
255+
return info;
256+
}
257+
258+
void V4L2H264EncoderImpl::SetRates(
259+
const RateControlParameters& parameters) {
260+
if (!encoder_) {
261+
RTC_LOG(LS_WARNING) << "V4L2: SetRates() while uninitialized.";
262+
return;
263+
}
264+
265+
if (parameters.framerate_fps < 1.0) {
266+
RTC_LOG(LS_WARNING) << "V4L2: Invalid frame rate: "
267+
<< parameters.framerate_fps;
268+
return;
269+
}
270+
271+
if (parameters.bitrate.get_sum_bps() == 0) {
272+
configuration_.SetStreamState(false);
273+
return;
274+
}
275+
276+
codec_.maxFramerate = static_cast<uint32_t>(parameters.framerate_fps);
277+
278+
configuration_.target_bps = parameters.bitrate.GetSpatialLayerSum(0);
279+
configuration_.max_frame_rate = parameters.framerate_fps;
280+
281+
if (configuration_.target_bps) {
282+
configuration_.SetStreamState(true);
283+
encoder_->UpdateRates(configuration_.max_frame_rate,
284+
configuration_.target_bps);
285+
} else {
286+
configuration_.SetStreamState(false);
287+
}
288+
}
289+
290+
void V4L2H264EncoderImpl::LayerConfig::SetStreamState(bool send_stream) {
291+
if (send_stream && !sending) {
292+
key_frame_request = true;
293+
}
294+
sending = send_stream;
295+
}
296+
297+
} // namespace webrtc

0 commit comments

Comments
 (0)