Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/cast/sender/h264_vt_encoder.h" | |
| 6 | |
| 7 #include <algorithm> | |
|
miu
2014/11/20 23:16:55
Don't think you need this anymore.
...but you do
jfroy
2014/11/20 23:38:30
I don't need algo anymore indeed, but also don't t
| |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/big_endian.h" | |
| 11 #include "base/bind.h" | |
| 12 #include "base/bind_helpers.h" | |
| 13 #include "base/location.h" | |
| 14 #include "base/logging.h" | |
| 15 #include "media/base/mac/corevideo_glue.h" | |
| 16 #include "media/base/mac/video_frame_mac.h" | |
| 17 #include "media/cast/sender/video_frame_factory.h" | |
| 18 | |
| 19 namespace media { | |
| 20 namespace cast { | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 // Container for the associated data of a video frame being processed. | |
| 25 struct InProgressFrameEncode { | |
| 26 const RtpTimestamp rtp_timestamp; | |
| 27 const base::TimeTicks reference_time; | |
| 28 const VideoEncoder::FrameEncodedCallback frame_encoded_callback; | |
| 29 | |
| 30 InProgressFrameEncode(RtpTimestamp rtp, | |
| 31 base::TimeTicks r_time, | |
| 32 VideoEncoder::FrameEncodedCallback callback) | |
| 33 : rtp_timestamp(rtp), | |
| 34 reference_time(r_time), | |
| 35 frame_encoded_callback(callback) {} | |
| 36 }; | |
| 37 | |
| 38 base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue(CFTypeRef key, | |
| 39 CFTypeRef value) { | |
| 40 CFTypeRef keys[1] = {key}; | |
| 41 CFTypeRef values[1] = {value}; | |
| 42 return base::ScopedCFTypeRef<CFDictionaryRef>(CFDictionaryCreate( | |
| 43 kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, | |
| 44 &kCFTypeDictionaryValueCallBacks)); | |
| 45 } | |
| 46 | |
| 47 template <typename NalSizeType> | |
| 48 void CopyNalsToAnnexB(char* avcc_buffer, | |
| 49 const size_t avcc_size, | |
| 50 std::string* annexb_buffer) { | |
| 51 COMPILE_ASSERT(sizeof(NalSizeType) == 1 || sizeof(NalSizeType) == 2 || | |
| 52 sizeof(NalSizeType) == 4, | |
| 53 "NAL size type has unsupported size"); | |
| 54 static const char startcode_3[3] = {0, 0, 1}; | |
| 55 DCHECK(avcc_buffer); | |
| 56 DCHECK(annexb_buffer); | |
| 57 size_t bytes_left = avcc_size; | |
| 58 while (bytes_left > 0) { | |
| 59 DCHECK_GT(bytes_left, sizeof(NalSizeType)); | |
| 60 NalSizeType nal_size; | |
| 61 base::ReadBigEndian(avcc_buffer, &nal_size); | |
| 62 bytes_left -= sizeof(NalSizeType); | |
| 63 avcc_buffer += sizeof(NalSizeType); | |
| 64 | |
| 65 DCHECK_GE(bytes_left, nal_size); | |
| 66 annexb_buffer->append(startcode_3, sizeof(startcode_3)); | |
| 67 annexb_buffer->append(avcc_buffer, nal_size); | |
| 68 bytes_left -= nal_size; | |
| 69 avcc_buffer += nal_size; | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 // Copy a H.264 frame stored in a CM sample buffer to an Annex B buffer. Copies | |
| 74 // parameter sets for keyframes before the frame data as well. | |
| 75 void CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, | |
| 76 std::string* annexb_buffer, | |
| 77 bool keyframe) { | |
| 78 // Perform two pass, one to figure out the total output size, and another to | |
| 79 // copy the data after having performed a single output allocation. Note that | |
| 80 // we'll allocate a bit more because we'll count 4 bytes instead of 3 for | |
| 81 // video NALs. | |
| 82 | |
| 83 OSStatus status; | |
| 84 | |
| 85 // Get the sample buffer's block buffer and format description. | |
| 86 auto bb = CoreMediaGlue::CMSampleBufferGetDataBuffer(sbuf); | |
| 87 DCHECK(bb); | |
| 88 auto fdesc = CoreMediaGlue::CMSampleBufferGetFormatDescription(sbuf); | |
| 89 DCHECK(fdesc); | |
| 90 | |
| 91 size_t bb_size = CoreMediaGlue::CMBlockBufferGetDataLength(bb); | |
| 92 size_t total_bytes = bb_size; | |
| 93 | |
| 94 size_t pset_count; | |
| 95 int nal_size_field_bytes; | |
| 96 status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
| 97 fdesc, 0, nullptr, nullptr, &pset_count, &nal_size_field_bytes); | |
| 98 if (status == | |
| 99 CoreMediaGlue::kCMFormatDescriptionBridgeError_InvalidParameter) { | |
| 100 DLOG(WARNING) << " assuming 2 parameter sets and 4 bytes NAL length header"; | |
| 101 pset_count = 2; | |
| 102 nal_size_field_bytes = 4; | |
| 103 } else if (status != noErr) { | |
| 104 DLOG(ERROR) | |
| 105 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
| 106 << status; | |
| 107 return; | |
| 108 } | |
| 109 | |
| 110 if (keyframe) { | |
| 111 const uint8_t* pset; | |
| 112 size_t pset_size; | |
| 113 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { | |
| 114 status = | |
| 115 CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
| 116 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); | |
| 117 if (status != noErr) { | |
| 118 DLOG(ERROR) | |
| 119 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
| 120 << status; | |
| 121 return; | |
| 122 } | |
| 123 total_bytes += pset_size + nal_size_field_bytes; | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 annexb_buffer->reserve(total_bytes); | |
| 128 | |
| 129 // Copy all parameter sets before keyframes. | |
| 130 if (keyframe) { | |
| 131 const uint8_t* pset; | |
| 132 size_t pset_size; | |
| 133 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { | |
| 134 status = | |
| 135 CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
| 136 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); | |
| 137 if (status != noErr) { | |
| 138 DLOG(ERROR) | |
| 139 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
| 140 << status; | |
| 141 return; | |
| 142 } | |
| 143 static const char startcode_4[4] = {0, 0, 0, 1}; | |
| 144 annexb_buffer->append(startcode_4, sizeof(startcode_4)); | |
| 145 annexb_buffer->append(reinterpret_cast<const char*>(pset), pset_size); | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 // Block buffers can be composed of non-contiguous chunks. For the sake of | |
| 150 // keeping this code simple, flatten non-contiguous block buffers. | |
| 151 base::ScopedCFTypeRef<CoreMediaGlue::CMBlockBufferRef> contiguous_bb( | |
| 152 bb, base::scoped_policy::RETAIN); | |
| 153 if (!CoreMediaGlue::CMBlockBufferIsRangeContiguous(bb, 0, 0)) { | |
| 154 contiguous_bb.reset(); | |
| 155 status = CoreMediaGlue::CMBlockBufferCreateContiguous( | |
| 156 kCFAllocatorDefault, bb, kCFAllocatorDefault, nullptr, 0, 0, 0, | |
| 157 contiguous_bb.InitializeInto()); | |
| 158 if (status != noErr) { | |
| 159 DLOG(ERROR) << " CMBlockBufferCreateContiguous failed: " << status; | |
| 160 return; | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 // Copy all the NAL units. In the process convert them from AVCC format | |
| 165 // (length header) to AnnexB format (start code). | |
| 166 char* bb_data; | |
| 167 status = CoreMediaGlue::CMBlockBufferGetDataPointer(contiguous_bb, 0, nullptr, | |
| 168 nullptr, &bb_data); | |
| 169 if (status != noErr) { | |
| 170 DLOG(ERROR) << " CMBlockBufferGetDataPointer failed: " << status; | |
| 171 return; | |
| 172 } | |
| 173 | |
| 174 if (nal_size_field_bytes == 1) { | |
| 175 CopyNalsToAnnexB<uint8_t>(bb_data, bb_size, annexb_buffer); | |
| 176 } else if (nal_size_field_bytes == 2) { | |
| 177 CopyNalsToAnnexB<uint16_t>(bb_data, bb_size, annexb_buffer); | |
| 178 } else if (nal_size_field_bytes == 4) { | |
| 179 CopyNalsToAnnexB<uint32_t>(bb_data, bb_size, annexb_buffer); | |
| 180 } else { | |
| 181 NOTREACHED(); | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 // Implementation of the VideoFrameFactory interface using |CVPixelBufferPool|. | |
| 186 class VideoFrameFactoryCVPixelBufferPoolImpl : public VideoFrameFactory { | |
| 187 public: | |
| 188 VideoFrameFactoryCVPixelBufferPoolImpl( | |
| 189 base::ScopedCFTypeRef<CVPixelBufferPoolRef> pool) | |
|
miu
2014/11/20 23:16:55
const base::ScopedCFTypeRef<CVPixelBufferPoolRef>&
| |
| 190 : pool_(pool) {} | |
| 191 | |
| 192 ~VideoFrameFactoryCVPixelBufferPoolImpl() override {} | |
| 193 | |
| 194 scoped_refptr<VideoFrame> CreateFrame(base::TimeDelta timestamp) override { | |
| 195 base::ScopedCFTypeRef<CVPixelBufferRef> buffer; | |
| 196 CHECK_EQ(kCVReturnSuccess, | |
| 197 CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool_, | |
| 198 buffer.InitializeInto())); | |
| 199 return VideoFrame::WrapCVPixelBuffer(buffer, timestamp); | |
| 200 } | |
| 201 | |
| 202 private: | |
| 203 base::ScopedCFTypeRef<CVPixelBufferPoolRef> pool_; | |
| 204 | |
| 205 DISALLOW_COPY_AND_ASSIGN(VideoFrameFactoryCVPixelBufferPoolImpl); | |
| 206 }; | |
| 207 | |
| 208 } // namespace | |
| 209 | |
| 210 H264VideoToolboxEncoder::H264VideoToolboxEncoder( | |
| 211 scoped_refptr<CastEnvironment> cast_environment, | |
| 212 const VideoSenderConfig& video_config, | |
| 213 const CastInitializationCallback& initialization_cb) | |
| 214 : cast_environment_(cast_environment), | |
| 215 videotoolbox_glue_(VideoToolboxGlue::Get()), | |
| 216 frame_id_(kStartFrameId), | |
| 217 encode_next_frame_as_keyframe_(false) { | |
| 218 DCHECK(!initialization_cb.is_null()); | |
| 219 CastInitializationStatus initialization_status; | |
| 220 if (videotoolbox_glue_) { | |
| 221 initialization_status = (Initialize(video_config)) | |
| 222 ? STATUS_VIDEO_INITIALIZED | |
| 223 : STATUS_INVALID_VIDEO_CONFIGURATION; | |
| 224 } else { | |
| 225 LOG(ERROR) << " VideoToolbox is not available"; | |
| 226 initialization_status = STATUS_HW_VIDEO_ENCODER_NOT_SUPPORTED; | |
| 227 } | |
| 228 cast_environment_->PostTask( | |
| 229 CastEnvironment::MAIN, FROM_HERE, | |
| 230 base::Bind(initialization_cb, initialization_status)); | |
| 231 } | |
| 232 | |
| 233 H264VideoToolboxEncoder::~H264VideoToolboxEncoder() { | |
| 234 Teardown(); | |
| 235 } | |
| 236 | |
| 237 bool H264VideoToolboxEncoder::Initialize( | |
| 238 const VideoSenderConfig& video_config) { | |
| 239 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 240 DCHECK(!compression_session_); | |
| 241 | |
| 242 // Note that the encoder object is given to the compression session as the | |
| 243 // callback context using a raw pointer. The C API does not allow us to use | |
| 244 // a smart pointer, nor is this encoder ref counted. However, this is still | |
| 245 // safe, because we 1) we own the compression session and 2) we tear it down | |
| 246 // safely. When destructing the encoder, the compression session is flushed | |
| 247 // and invalidated. Internally, VideoToolbox will join all of its threads | |
| 248 // before returning to the client. Therefore, when control returns to us, we | |
| 249 // are guaranteed that the output callback will not execute again. | |
| 250 | |
| 251 // On OS X, allow the hardware encoder. Don't require it, it does not support | |
| 252 // all configurations (some of which are used for testing). | |
| 253 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec; | |
| 254 #if !defined(OS_IOS) | |
| 255 encoder_spec = DictionaryWithKeyValue( | |
| 256 videotoolbox_glue_ | |
| 257 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder() , | |
| 258 kCFBooleanTrue); | |
| 259 #endif | |
| 260 | |
| 261 VTCompressionSessionRef session; | |
| 262 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate( | |
| 263 kCFAllocatorDefault, video_config.width, video_config.height, | |
| 264 CoreMediaGlue::kCMVideoCodecType_H264, encoder_spec, | |
| 265 nullptr /* sourceImageBufferAttributes */, | |
| 266 nullptr /* compressedDataAllocator */, | |
| 267 &H264VideoToolboxEncoder::CompressionCallback, | |
| 268 reinterpret_cast<void*>(this), &session); | |
| 269 if (status != noErr) { | |
| 270 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status; | |
| 271 return false; | |
| 272 } | |
| 273 compression_session_.reset(session); | |
| 274 | |
| 275 ConfigureSession(video_config); | |
| 276 | |
| 277 return true; | |
| 278 } | |
| 279 | |
| 280 void H264VideoToolboxEncoder::ConfigureSession( | |
| 281 const VideoSenderConfig& video_config) { | |
| 282 SetSessionProperty( | |
| 283 videotoolbox_glue_->kVTCompressionPropertyKey_ProfileLevel(), | |
| 284 videotoolbox_glue_->kVTProfileLevel_H264_Main_AutoLevel()); | |
| 285 SetSessionProperty(videotoolbox_glue_->kVTCompressionPropertyKey_RealTime(), | |
| 286 true); | |
| 287 SetSessionProperty( | |
| 288 videotoolbox_glue_->kVTCompressionPropertyKey_AllowFrameReordering(), | |
| 289 false); | |
| 290 SetSessionProperty( | |
| 291 videotoolbox_glue_->kVTCompressionPropertyKey_MaxKeyFrameInterval(), 240); | |
| 292 SetSessionProperty( | |
| 293 videotoolbox_glue_ | |
| 294 ->kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration(), | |
| 295 240); | |
| 296 // TODO(jfroy): implement better bitrate control | |
| 297 // https://crbug.com/425352 | |
| 298 SetSessionProperty( | |
| 299 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(), | |
| 300 (video_config.min_bitrate + video_config.max_bitrate) / 2); | |
| 301 SetSessionProperty( | |
| 302 videotoolbox_glue_->kVTCompressionPropertyKey_ExpectedFrameRate(), | |
| 303 video_config.max_frame_rate); | |
| 304 SetSessionProperty( | |
| 305 videotoolbox_glue_->kVTCompressionPropertyKey_ColorPrimaries(), | |
| 306 kCVImageBufferColorPrimaries_ITU_R_709_2); | |
| 307 SetSessionProperty( | |
| 308 videotoolbox_glue_->kVTCompressionPropertyKey_TransferFunction(), | |
| 309 kCVImageBufferTransferFunction_ITU_R_709_2); | |
| 310 SetSessionProperty( | |
| 311 videotoolbox_glue_->kVTCompressionPropertyKey_YCbCrMatrix(), | |
| 312 kCVImageBufferYCbCrMatrix_ITU_R_709_2); | |
| 313 } | |
| 314 | |
| 315 void H264VideoToolboxEncoder::Teardown() { | |
| 316 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 317 | |
| 318 // If the compression session exists, invalidate it. This blocks until all | |
| 319 // pending output callbacks have returned and any internal threads have | |
| 320 // joined, ensuring no output callback ever sees a dangling encoder pointer. | |
| 321 if (compression_session_) { | |
| 322 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_); | |
| 323 compression_session_.reset(); | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 bool H264VideoToolboxEncoder::EncodeVideoFrame( | |
| 328 const scoped_refptr<media::VideoFrame>& video_frame, | |
| 329 const base::TimeTicks& reference_time, | |
| 330 const FrameEncodedCallback& frame_encoded_callback) { | |
| 331 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 332 DCHECK(!reference_time.is_null()); | |
| 333 | |
| 334 if (!compression_session_) { | |
| 335 DLOG(ERROR) << " compression session is null"; | |
| 336 return false; | |
| 337 } | |
| 338 | |
| 339 // Wrap the VideoFrame in a CVPixelBuffer. In all cases, no data will be | |
| 340 // copied. If the VideoFrame was created by this encoder's video frame | |
| 341 // factory, then the returned CVPixelBuffer will have been obtained from the | |
| 342 // compression session's pixel buffer pool. This will eliminate a copy of the | |
| 343 // frame into memory visible by the hardware encoder. The VideoFrame's | |
| 344 // lifetime is extended for the lifetime of the returned CVPixelBuffer. | |
| 345 auto pixel_buffer = media::WrapVideoFrameInCVPixelBuffer(*video_frame); | |
| 346 if (!pixel_buffer) { | |
| 347 return false; | |
| 348 } | |
| 349 | |
| 350 auto timestamp_cm = CoreMediaGlue::CMTimeMake( | |
| 351 (reference_time - base::TimeTicks()).InMicroseconds(), USEC_PER_SEC); | |
| 352 | |
| 353 scoped_ptr<InProgressFrameEncode> request(new InProgressFrameEncode( | |
| 354 TimeDeltaToRtpDelta(video_frame->timestamp(), kVideoFrequency), | |
| 355 reference_time, frame_encoded_callback)); | |
| 356 | |
| 357 base::ScopedCFTypeRef<CFDictionaryRef> frame_props; | |
| 358 if (encode_next_frame_as_keyframe_) { | |
| 359 frame_props = DictionaryWithKeyValue( | |
| 360 videotoolbox_glue_->kVTEncodeFrameOptionKey_ForceKeyFrame(), | |
| 361 kCFBooleanTrue); | |
| 362 encode_next_frame_as_keyframe_ = false; | |
| 363 } | |
| 364 | |
| 365 VTEncodeInfoFlags info; | |
| 366 OSStatus status = videotoolbox_glue_->VTCompressionSessionEncodeFrame( | |
| 367 compression_session_, pixel_buffer, timestamp_cm, | |
| 368 CoreMediaGlue::CMTime{0, 0, 0, 0}, frame_props, | |
| 369 reinterpret_cast<void*>(request.release()), &info); | |
| 370 if (status != noErr) { | |
| 371 DLOG(ERROR) << " VTCompressionSessionEncodeFrame failed: " << status; | |
| 372 return false; | |
| 373 } | |
| 374 if ((info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped)) { | |
| 375 DLOG(ERROR) << " frame dropped"; | |
| 376 return false; | |
| 377 } | |
| 378 | |
| 379 return true; | |
| 380 } | |
| 381 | |
| 382 void H264VideoToolboxEncoder::SetBitRate(int new_bit_rate) { | |
| 383 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 384 // VideoToolbox does not seem to support bitrate reconfiguration. | |
| 385 } | |
| 386 | |
| 387 void H264VideoToolboxEncoder::GenerateKeyFrame() { | |
| 388 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 389 DCHECK(compression_session_); | |
| 390 | |
| 391 encode_next_frame_as_keyframe_ = true; | |
| 392 } | |
| 393 | |
| 394 void H264VideoToolboxEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) { | |
| 395 // Not supported by VideoToolbox in any meaningful manner. | |
| 396 } | |
| 397 | |
| 398 scoped_ptr<VideoFrameFactory> | |
| 399 H264VideoToolboxEncoder::CreateVideoFrameFactory() { | |
| 400 base::ScopedCFTypeRef<CVPixelBufferPoolRef> pool( | |
| 401 videotoolbox_glue_->VTCompressionSessionGetPixelBufferPool( | |
| 402 compression_session_), | |
| 403 base::scoped_policy::RETAIN); | |
| 404 return scoped_ptr<VideoFrameFactory>( | |
| 405 new VideoFrameFactoryCVPixelBufferPoolImpl(pool)); | |
| 406 } | |
| 407 | |
| 408 bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key, | |
| 409 int32_t value) { | |
| 410 base::ScopedCFTypeRef<CFNumberRef> cfvalue( | |
| 411 CFNumberCreate(nullptr, kCFNumberSInt32Type, &value)); | |
| 412 return videotoolbox_glue_->VTSessionSetProperty(compression_session_, key, | |
| 413 cfvalue) == noErr; | |
| 414 } | |
| 415 | |
| 416 bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key, bool value) { | |
| 417 CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse; | |
| 418 return videotoolbox_glue_->VTSessionSetProperty(compression_session_, key, | |
| 419 cfvalue) == noErr; | |
| 420 } | |
| 421 | |
| 422 bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key, | |
| 423 CFStringRef value) { | |
| 424 return videotoolbox_glue_->VTSessionSetProperty(compression_session_, key, | |
| 425 value) == noErr; | |
| 426 } | |
| 427 | |
| 428 void H264VideoToolboxEncoder::CompressionCallback(void* encoder_opaque, | |
| 429 void* request_opaque, | |
| 430 OSStatus status, | |
| 431 VTEncodeInfoFlags info, | |
| 432 CMSampleBufferRef sbuf) { | |
| 433 if (status != noErr) { | |
| 434 DLOG(ERROR) << " encoding failed: " << status; | |
| 435 return; | |
| 436 } | |
| 437 if ((info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped)) { | |
| 438 DVLOG(2) << " frame dropped"; | |
| 439 return; | |
| 440 } | |
| 441 | |
| 442 auto encoder = reinterpret_cast<H264VideoToolboxEncoder*>(encoder_opaque); | |
| 443 const scoped_ptr<InProgressFrameEncode> request( | |
| 444 reinterpret_cast<InProgressFrameEncode*>(request_opaque)); | |
| 445 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex( | |
| 446 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0)); | |
| 447 | |
| 448 // If the NotSync key is not present, it implies Sync, which indicates a | |
| 449 // keyframe (at least I think, VT documentation is, erm, sparse). Could | |
| 450 // alternatively use kCMSampleAttachmentKey_DependsOnOthers == false. | |
| 451 bool keyframe = | |
| 452 !CFDictionaryContainsKey(sample_attachments, | |
| 453 CoreMediaGlue::kCMSampleAttachmentKey_NotSync()); | |
| 454 | |
| 455 // Increment the encoder-scoped frame id and assign the new value to this | |
| 456 // frame. VideoToolbox calls the output callback serially, so this is safe. | |
| 457 uint32 frame_id = ++encoder->frame_id_; | |
| 458 | |
| 459 scoped_ptr<EncodedFrame> encoded_frame(new EncodedFrame()); | |
| 460 encoded_frame->frame_id = frame_id; | |
| 461 encoded_frame->reference_time = request->reference_time; | |
| 462 encoded_frame->rtp_timestamp = request->rtp_timestamp; | |
| 463 if (keyframe) { | |
| 464 encoded_frame->dependency = EncodedFrame::KEY; | |
| 465 encoded_frame->referenced_frame_id = frame_id; | |
| 466 } else { | |
| 467 encoded_frame->dependency = EncodedFrame::DEPENDENT; | |
| 468 // H.264 supports complex frame reference schemes (multiple reference | |
| 469 // frames, slice references, backward and forward references, etc). Cast | |
| 470 // doesn't support the concept of forward-referencing frame dependencies or | |
| 471 // multiple frame dependencies; so pretend that all frames are only | |
| 472 // decodable after their immediately preceding frame is decoded. This will | |
| 473 // ensure a Cast receiver only attempts to decode the frames sequentially | |
| 474 // and in order. Furthermore, the encoder is configured to never use forward | |
| 475 // references (see |kVTCompressionPropertyKey_AllowFrameReordering|). There | |
| 476 // is no way to prevent multiple reference frames. | |
| 477 encoded_frame->referenced_frame_id = frame_id - 1; | |
| 478 } | |
| 479 | |
| 480 CopySampleBufferToAnnexBBuffer(sbuf, &encoded_frame->data, keyframe); | |
| 481 | |
| 482 encoder->cast_environment_->PostTask( | |
| 483 CastEnvironment::MAIN, FROM_HERE, | |
| 484 base::Bind(request->frame_encoded_callback, | |
| 485 base::Passed(&encoded_frame))); | |
| 486 } | |
| 487 | |
| 488 } // namespace cast | |
| 489 } // namespace media | |
| OLD | NEW |