OLD | NEW |
1 /* | 1 /* |
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. | 2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 * | 9 * |
10 */ | 10 */ |
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
148 webrtc::VideoRotation rotation; | 148 webrtc::VideoRotation rotation; |
149 }; | 149 }; |
150 | 150 |
151 // We receive I420Frames as input, but we need to feed CVPixelBuffers into the | 151 // We receive I420Frames as input, but we need to feed CVPixelBuffers into the |
152 // encoder. This performs the copy and format conversion. | 152 // encoder. This performs the copy and format conversion. |
153 // TODO(tkchin): See if encoder will accept i420 frames and compare performance. | 153 // TODO(tkchin): See if encoder will accept i420 frames and compare performance. |
154 bool CopyVideoFrameToPixelBuffer( | 154 bool CopyVideoFrameToPixelBuffer( |
155 const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& frame, | 155 const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& frame, |
156 CVPixelBufferRef pixel_buffer) { | 156 CVPixelBufferRef pixel_buffer) { |
157 RTC_DCHECK(pixel_buffer); | 157 RTC_DCHECK(pixel_buffer); |
158 RTC_DCHECK(CVPixelBufferGetPixelFormatType(pixel_buffer) == | 158 RTC_DCHECK_EQ(CVPixelBufferGetPixelFormatType(pixel_buffer), |
159 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange); | 159 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange); |
160 RTC_DCHECK(CVPixelBufferGetHeightOfPlane(pixel_buffer, 0) == | 160 RTC_DCHECK_EQ(CVPixelBufferGetHeightOfPlane(pixel_buffer, 0), |
161 static_cast<size_t>(frame->height())); | 161 static_cast<size_t>(frame->height())); |
162 RTC_DCHECK(CVPixelBufferGetWidthOfPlane(pixel_buffer, 0) == | 162 RTC_DCHECK_EQ(CVPixelBufferGetWidthOfPlane(pixel_buffer, 0), |
163 static_cast<size_t>(frame->width())); | 163 static_cast<size_t>(frame->width())); |
164 | 164 |
165 CVReturn cvRet = CVPixelBufferLockBaseAddress(pixel_buffer, 0); | 165 CVReturn cvRet = CVPixelBufferLockBaseAddress(pixel_buffer, 0); |
166 if (cvRet != kCVReturnSuccess) { | 166 if (cvRet != kCVReturnSuccess) { |
167 LOG(LS_ERROR) << "Failed to lock base address: " << cvRet; | 167 LOG(LS_ERROR) << "Failed to lock base address: " << cvRet; |
168 return false; | 168 return false; |
169 } | 169 } |
170 uint8_t* dst_y = reinterpret_cast<uint8_t*>( | 170 uint8_t* dst_y = reinterpret_cast<uint8_t*>( |
171 CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 0)); | 171 CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 0)); |
172 int dst_stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 0); | 172 int dst_stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 0); |
173 uint8_t* dst_uv = reinterpret_cast<uint8_t*>( | 173 uint8_t* dst_uv = reinterpret_cast<uint8_t*>( |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
211 // .5 is set as a mininum to prevent overcompensating for large temporary | 211 // .5 is set as a mininum to prevent overcompensating for large temporary |
212 // overshoots. We don't want to degrade video quality too badly. | 212 // overshoots. We don't want to degrade video quality too badly. |
213 // .95 is set to prevent oscillations. When a lower bitrate is set on the | 213 // .95 is set to prevent oscillations. When a lower bitrate is set on the |
214 // encoder than previously set, its output seems to have a brief period of | 214 // encoder than previously set, its output seems to have a brief period of |
215 // drastically reduced bitrate, so we want to avoid that. In steady state | 215 // drastically reduced bitrate, so we want to avoid that. In steady state |
216 // conditions, 0.95 seems to give us better overall bitrate over long periods | 216 // conditions, 0.95 seems to give us better overall bitrate over long periods |
217 // of time. | 217 // of time. |
218 H264VideoToolboxEncoder::H264VideoToolboxEncoder() | 218 H264VideoToolboxEncoder::H264VideoToolboxEncoder() |
219 : callback_(nullptr), | 219 : callback_(nullptr), |
220 compression_session_(nullptr), | 220 compression_session_(nullptr), |
221 bitrate_adjuster_(Clock::GetRealTimeClock(), .5, .95), | 221 bitrate_adjuster_(Clock::GetRealTimeClock(), .5, .95) { |
222 enable_scaling_(true) { | |
223 #if defined(WEBRTC_IOS) | |
224 if ([UIDevice deviceType] == RTCDeviceTypeIPhone4S) { | |
225 enable_scaling_ = false; | |
226 } | |
227 #endif | |
228 } | 222 } |
229 | 223 |
230 H264VideoToolboxEncoder::~H264VideoToolboxEncoder() { | 224 H264VideoToolboxEncoder::~H264VideoToolboxEncoder() { |
231 DestroyCompressionSession(); | 225 DestroyCompressionSession(); |
232 } | 226 } |
233 | 227 |
234 int H264VideoToolboxEncoder::InitEncode(const VideoCodec* codec_settings, | 228 int H264VideoToolboxEncoder::InitEncode(const VideoCodec* codec_settings, |
235 int number_of_cores, | 229 int number_of_cores, |
236 size_t max_payload_size) { | 230 size_t max_payload_size) { |
237 RTC_DCHECK(codec_settings); | 231 RTC_DCHECK(codec_settings); |
238 RTC_DCHECK_EQ(codec_settings->codecType, kVideoCodecH264); | 232 RTC_DCHECK_EQ(codec_settings->codecType, kVideoCodecH264); |
239 width_ = codec_settings->width; | |
240 height_ = codec_settings->height; | |
241 { | 233 { |
242 rtc::CritScope lock(&quality_scaler_crit_); | 234 rtc::CritScope lock(&quality_scaler_crit_); |
243 quality_scaler_.Init(QualityScaler::kLowH264QpThreshold, | 235 quality_scaler_.Init(QualityScaler::kLowH264QpThreshold, |
244 QualityScaler::kBadH264QpThreshold, | 236 QualityScaler::kBadH264QpThreshold, |
245 codec_settings->startBitrate, codec_settings->width, | 237 codec_settings->startBitrate, codec_settings->width, |
246 codec_settings->height, codec_settings->maxFramerate); | 238 codec_settings->height, codec_settings->maxFramerate); |
247 QualityScaler::Resolution res = quality_scaler_.GetScaledResolution(); | 239 QualityScaler::Resolution res = quality_scaler_.GetScaledResolution(); |
248 // TODO(tkchin): We may need to enforce width/height dimension restrictions | 240 // TODO(tkchin): We may need to enforce width/height dimension restrictions |
249 // to match what the encoder supports. | 241 // to match what the encoder supports. |
250 if (enable_scaling_) { | 242 width_ = res.width; |
251 width_ = res.width; | 243 height_ = res.height; |
252 height_ = res.height; | |
253 } | |
254 } | 244 } |
255 // We can only set average bitrate on the HW encoder. | 245 // We can only set average bitrate on the HW encoder. |
256 target_bitrate_bps_ = codec_settings->startBitrate; | 246 target_bitrate_bps_ = codec_settings->startBitrate; |
257 bitrate_adjuster_.SetTargetBitrateBps(target_bitrate_bps_); | 247 bitrate_adjuster_.SetTargetBitrateBps(target_bitrate_bps_); |
258 | 248 |
259 // TODO(tkchin): Try setting payload size via | 249 // TODO(tkchin): Try setting payload size via |
260 // kVTCompressionPropertyKey_MaxH264SliceBytes. | 250 // kVTCompressionPropertyKey_MaxH264SliceBytes. |
261 | 251 |
262 return ResetCompressionSession(); | 252 return ResetCompressionSession(); |
263 } | 253 } |
264 | 254 |
265 rtc::scoped_refptr<VideoFrameBuffer> | |
266 H264VideoToolboxEncoder::GetScaledBufferOnEncode( | |
267 const rtc::scoped_refptr<VideoFrameBuffer>& frame) { | |
268 rtc::CritScope lock(&quality_scaler_crit_); | |
269 quality_scaler_.OnEncodeFrame(frame->width(), frame->height()); | |
270 if (!frame->native_handle()) | |
271 return quality_scaler_.GetScaledBuffer(frame); | |
272 | |
273 // Handle native (CVImageRef) scaling. | |
274 const QualityScaler::Resolution res = quality_scaler_.GetScaledResolution(); | |
275 if (!enable_scaling_ || | |
276 (res.width == frame->width() && res.height == frame->height())) | |
277 return frame; | |
278 // TODO(magjed): Implement efficient CVImageRef -> CVImageRef scaling instead | |
279 // of doing it via I420. | |
280 return quality_scaler_.GetScaledBuffer(frame->NativeToI420Buffer()); | |
281 } | |
282 | |
283 int H264VideoToolboxEncoder::Encode( | 255 int H264VideoToolboxEncoder::Encode( |
284 const VideoFrame& frame, | 256 const VideoFrame& frame, |
285 const CodecSpecificInfo* codec_specific_info, | 257 const CodecSpecificInfo* codec_specific_info, |
286 const std::vector<FrameType>* frame_types) { | 258 const std::vector<FrameType>* frame_types) { |
287 RTC_DCHECK(!frame.IsZeroSize()); | 259 RTC_DCHECK(!frame.IsZeroSize()); |
288 if (!callback_ || !compression_session_) { | 260 if (!callback_ || !compression_session_) { |
289 return WEBRTC_VIDEO_CODEC_UNINITIALIZED; | 261 return WEBRTC_VIDEO_CODEC_UNINITIALIZED; |
290 } | 262 } |
291 #if defined(WEBRTC_IOS) | 263 #if defined(WEBRTC_IOS) |
292 if (!RTCIsUIApplicationActive()) { | 264 if (!RTCIsUIApplicationActive()) { |
293 // Ignore all encode requests when app isn't active. In this state, the | 265 // Ignore all encode requests when app isn't active. In this state, the |
294 // hardware encoder has been invalidated by the OS. | 266 // hardware encoder has been invalidated by the OS. |
295 return WEBRTC_VIDEO_CODEC_OK; | 267 return WEBRTC_VIDEO_CODEC_OK; |
296 } | 268 } |
297 #endif | 269 #endif |
298 bool is_keyframe_required = false; | 270 bool is_keyframe_required = false; |
299 rtc::scoped_refptr<VideoFrameBuffer> input_image( | |
300 GetScaledBufferOnEncode(frame.video_frame_buffer())); | |
301 | 271 |
302 if (input_image->width() != width_ || input_image->height() != height_) { | 272 quality_scaler_.OnEncodeFrame(frame.width(), frame.height()); |
303 width_ = input_image->width(); | 273 const QualityScaler::Resolution scaled_res = |
304 height_ = input_image->height(); | 274 quality_scaler_.GetScaledResolution(); |
| 275 |
| 276 if (scaled_res.width != width_ || scaled_res.height != height_) { |
| 277 width_ = scaled_res.width; |
| 278 height_ = scaled_res.height; |
305 int ret = ResetCompressionSession(); | 279 int ret = ResetCompressionSession(); |
306 if (ret < 0) | 280 if (ret < 0) |
307 return ret; | 281 return ret; |
308 } | 282 } |
309 | 283 |
310 // Get a pixel buffer from the pool and copy frame data over. | 284 // Get a pixel buffer from the pool and copy frame data over. |
311 CVPixelBufferPoolRef pixel_buffer_pool = | 285 CVPixelBufferPoolRef pixel_buffer_pool = |
312 VTCompressionSessionGetPixelBufferPool(compression_session_); | 286 VTCompressionSessionGetPixelBufferPool(compression_session_); |
313 #if defined(WEBRTC_IOS) | 287 #if defined(WEBRTC_IOS) |
314 if (!pixel_buffer_pool) { | 288 if (!pixel_buffer_pool) { |
315 // Kind of a hack. On backgrounding, the compression session seems to get | 289 // Kind of a hack. On backgrounding, the compression session seems to get |
316 // invalidated, which causes this pool call to fail when the application | 290 // invalidated, which causes this pool call to fail when the application |
317 // is foregrounded and frames are being sent for encoding again. | 291 // is foregrounded and frames are being sent for encoding again. |
318 // Resetting the session when this happens fixes the issue. | 292 // Resetting the session when this happens fixes the issue. |
319 // In addition we request a keyframe so video can recover quickly. | 293 // In addition we request a keyframe so video can recover quickly. |
320 ResetCompressionSession(); | 294 ResetCompressionSession(); |
321 pixel_buffer_pool = | 295 pixel_buffer_pool = |
322 VTCompressionSessionGetPixelBufferPool(compression_session_); | 296 VTCompressionSessionGetPixelBufferPool(compression_session_); |
323 is_keyframe_required = true; | 297 is_keyframe_required = true; |
324 LOG(LS_INFO) << "Resetting compression session due to invalid pool."; | 298 LOG(LS_INFO) << "Resetting compression session due to invalid pool."; |
325 } | 299 } |
326 #endif | 300 #endif |
327 | 301 |
328 CVPixelBufferRef pixel_buffer = | 302 CVPixelBufferRef pixel_buffer = static_cast<CVPixelBufferRef>( |
329 static_cast<CVPixelBufferRef>(input_image->native_handle()); | 303 frame.video_frame_buffer()->native_handle()); |
330 if (pixel_buffer) { | 304 if (pixel_buffer) { |
| 305 // This pixel buffer might have a higher resolution than what the |
| 306 // compression session is configured to. The compression session can handle |
| 307 // that and will output encoded frames in the configured resolution |
| 308 // regardless of the input pixel buffer resolution. |
331 CVBufferRetain(pixel_buffer); | 309 CVBufferRetain(pixel_buffer); |
332 pixel_buffer_pool = nullptr; | 310 pixel_buffer_pool = nullptr; |
333 } else { | 311 } else { |
334 if (!pixel_buffer_pool) { | 312 if (!pixel_buffer_pool) { |
335 LOG(LS_ERROR) << "Failed to get pixel buffer pool."; | 313 LOG(LS_ERROR) << "Failed to get pixel buffer pool."; |
336 return WEBRTC_VIDEO_CODEC_ERROR; | 314 return WEBRTC_VIDEO_CODEC_ERROR; |
337 } | 315 } |
338 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer( | 316 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer( |
339 nullptr, pixel_buffer_pool, &pixel_buffer); | 317 nullptr, pixel_buffer_pool, &pixel_buffer); |
340 if (ret != kCVReturnSuccess) { | 318 if (ret != kCVReturnSuccess) { |
341 LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret; | 319 LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret; |
342 // We probably want to drop frames here, since failure probably means | 320 // We probably want to drop frames here, since failure probably means |
343 // that the pool is empty. | 321 // that the pool is empty. |
344 return WEBRTC_VIDEO_CODEC_ERROR; | 322 return WEBRTC_VIDEO_CODEC_ERROR; |
345 } | 323 } |
346 RTC_DCHECK(pixel_buffer); | 324 RTC_DCHECK(pixel_buffer); |
347 if (!internal::CopyVideoFrameToPixelBuffer(input_image, pixel_buffer)) { | 325 // TODO(magjed): Optimize by merging scaling and NV12 pixel buffer |
| 326 // conversion once libyuv::MergeUVPlanes is available. |
| 327 rtc::scoped_refptr<VideoFrameBuffer> scaled_i420_buffer = |
| 328 quality_scaler_.GetScaledBuffer(frame.video_frame_buffer()); |
| 329 if (!internal::CopyVideoFrameToPixelBuffer(scaled_i420_buffer, |
| 330 pixel_buffer)) { |
348 LOG(LS_ERROR) << "Failed to copy frame data."; | 331 LOG(LS_ERROR) << "Failed to copy frame data."; |
349 CVBufferRelease(pixel_buffer); | 332 CVBufferRelease(pixel_buffer); |
350 return WEBRTC_VIDEO_CODEC_ERROR; | 333 return WEBRTC_VIDEO_CODEC_ERROR; |
351 } | 334 } |
352 } | 335 } |
353 | 336 |
354 // Check if we need a keyframe. | 337 // Check if we need a keyframe. |
355 if (!is_keyframe_required && frame_types) { | 338 if (!is_keyframe_required && frame_types) { |
356 for (auto frame_type : *frame_types) { | 339 for (auto frame_type : *frame_types) { |
357 if (frame_type == kVideoFrameKey) { | 340 if (frame_type == kVideoFrameKey) { |
(...skipping 286 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
644 if (result != 0) { | 627 if (result != 0) { |
645 LOG(LS_ERROR) << "Encode callback failed: " << result; | 628 LOG(LS_ERROR) << "Encode callback failed: " << result; |
646 return; | 629 return; |
647 } | 630 } |
648 bitrate_adjuster_.Update(frame._size); | 631 bitrate_adjuster_.Update(frame._size); |
649 } | 632 } |
650 | 633 |
651 } // namespace webrtc | 634 } // namespace webrtc |
652 | 635 |
653 #endif // defined(WEBRTC_VIDEO_TOOLBOX_SUPPORTED) | 636 #endif // defined(WEBRTC_VIDEO_TOOLBOX_SUPPORTED) |
OLD | NEW |