|
| 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