Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <CoreVideo/CoreVideo.h> | 5 #include <CoreVideo/CoreVideo.h> |
| 6 #include <OpenGL/CGLIOSurface.h> | 6 #include <OpenGL/CGLIOSurface.h> |
| 7 #include <OpenGL/gl.h> | 7 #include <OpenGL/gl.h> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 11 #include "base/sys_byteorder.h" | 11 #include "base/sys_byteorder.h" |
| 12 #include "base/thread_task_runner_handle.h" | 12 #include "base/thread_task_runner_handle.h" |
| 13 #include "content/common/gpu/media/vt_video_decode_accelerator.h" | 13 #include "content/common/gpu/media/vt_video_decode_accelerator.h" |
| 14 #include "content/public/common/content_switches.h" | 14 #include "content/public/common/content_switches.h" |
| 15 #include "media/filters/h264_parser.h" | 15 #include "media/filters/h264_parser.h" |
| 16 #include "ui/gl/scoped_binders.h" | 16 #include "ui/gl/scoped_binders.h" |
| 17 #include "ui/gl/scoped_cgl.h" | 17 #include "ui/gl/scoped_cgl.h" |
| 18 | 18 |
| 19 using content_common_gpu_media::kModuleVt; | 19 using content_common_gpu_media::kModuleVt; |
| 20 using content_common_gpu_media::InitializeStubs; | 20 using content_common_gpu_media::InitializeStubs; |
| 21 using content_common_gpu_media::IsVtInitialized; | 21 using content_common_gpu_media::IsVtInitialized; |
| 22 using content_common_gpu_media::StubPathMap; | 22 using content_common_gpu_media::StubPathMap; |
| 23 | 23 |
| 24 #define NOTIFY_STATUS(name, status) \ | |
|
Pawel Osciak
2014/10/22 10:45:21
I'd personally prefer s/STATUS/ERROR/, but up to y
sandersd (OOO until July 31)
2014/10/22 22:15:45
While this is a wrapper for NotifyError(), it is s
| |
| 25 do { \ | |
| 26 LOG(ERROR) << name << " failed with status " << status; \ | |
| 27 NotifyError(PLATFORM_FAILURE); \ | |
| 28 } while (0) | |
| 29 | |
| 24 namespace content { | 30 namespace content { |
| 25 | 31 |
| 26 // Size of NALU length headers in AVCC/MPEG-4 format (can be 1, 2, or 4). | 32 // Size of NALU length headers in AVCC/MPEG-4 format (can be 1, 2, or 4). |
| 27 static const int kNALUHeaderLength = 4; | 33 static const int kNALUHeaderLength = 4; |
| 28 | 34 |
| 29 // We only request 5 picture buffers from the client which are used to hold the | 35 // We only request 5 picture buffers from the client which are used to hold the |
| 30 // decoded samples. These buffers are then reused when the client tells us that | 36 // decoded samples. These buffers are then reused when the client tells us that |
| 31 // it is done with the buffer. | 37 // it is done with the buffer. |
| 32 static const int kNumPictureBuffers = 5; | 38 static const int kNumPictureBuffers = 5; |
| 33 | 39 |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 62 : action(action), | 68 : action(action), |
| 63 bitstream_id(bitstream_id) { | 69 bitstream_id(bitstream_id) { |
| 64 } | 70 } |
| 65 | 71 |
| 66 VTVideoDecodeAccelerator::PendingAction::~PendingAction() { | 72 VTVideoDecodeAccelerator::PendingAction::~PendingAction() { |
| 67 } | 73 } |
| 68 | 74 |
| 69 VTVideoDecodeAccelerator::VTVideoDecodeAccelerator(CGLContextObj cgl_context) | 75 VTVideoDecodeAccelerator::VTVideoDecodeAccelerator(CGLContextObj cgl_context) |
| 70 : cgl_context_(cgl_context), | 76 : cgl_context_(cgl_context), |
| 71 client_(NULL), | 77 client_(NULL), |
| 78 has_error_(false), | |
| 72 format_(NULL), | 79 format_(NULL), |
| 73 session_(NULL), | 80 session_(NULL), |
| 74 gpu_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 81 gpu_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 75 weak_this_factory_(this), | 82 weak_this_factory_(this), |
| 76 decoder_thread_("VTDecoderThread") { | 83 decoder_thread_("VTDecoderThread") { |
| 77 callback_.decompressionOutputCallback = OutputThunk; | 84 callback_.decompressionOutputCallback = OutputThunk; |
| 78 callback_.decompressionOutputRefCon = this; | 85 callback_.decompressionOutputRefCon = this; |
| 79 } | 86 } |
| 80 | 87 |
| 81 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { | 88 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 108 return false; | 115 return false; |
| 109 } | 116 } |
| 110 | 117 |
| 111 // Spawn a thread to handle parsing and calling VideoToolbox. | 118 // Spawn a thread to handle parsing and calling VideoToolbox. |
| 112 if (!decoder_thread_.Start()) | 119 if (!decoder_thread_.Start()) |
| 113 return false; | 120 return false; |
| 114 | 121 |
| 115 return true; | 122 return true; |
| 116 } | 123 } |
| 117 | 124 |
| 118 // TODO(sandersd): Proper error reporting instead of CHECKs. | 125 bool VTVideoDecodeAccelerator::ConfigureDecoder( |
| 119 void VTVideoDecodeAccelerator::ConfigureDecoder( | |
| 120 const std::vector<const uint8_t*>& nalu_data_ptrs, | 126 const std::vector<const uint8_t*>& nalu_data_ptrs, |
| 121 const std::vector<size_t>& nalu_data_sizes) { | 127 const std::vector<size_t>& nalu_data_sizes) { |
| 122 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 128 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 129 | |
| 123 // Construct a new format description from the parameter sets. | 130 // Construct a new format description from the parameter sets. |
| 124 // TODO(sandersd): Replace this with custom code to support OS X < 10.9. | 131 // TODO(sandersd): Replace this with custom code to support OS X < 10.9. |
| 125 format_.reset(); | 132 format_.reset(); |
| 126 CHECK(!CMVideoFormatDescriptionCreateFromH264ParameterSets( | 133 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( |
| 127 kCFAllocatorDefault, | 134 kCFAllocatorDefault, |
| 128 nalu_data_ptrs.size(), // parameter_set_count | 135 nalu_data_ptrs.size(), // parameter_set_count |
| 129 &nalu_data_ptrs.front(), // ¶meter_set_pointers | 136 &nalu_data_ptrs.front(), // ¶meter_set_pointers |
| 130 &nalu_data_sizes.front(), // ¶meter_set_sizes | 137 &nalu_data_sizes.front(), // ¶meter_set_sizes |
| 131 kNALUHeaderLength, // nal_unit_header_length | 138 kNALUHeaderLength, // nal_unit_header_length |
| 132 format_.InitializeInto())); | 139 format_.InitializeInto()); |
| 140 if (status) { | |
| 141 NOTIFY_STATUS("CMVideoFormatDescriptionCreateFromH264ParameterSets()", | |
| 142 status); | |
| 143 return false; | |
| 144 } | |
| 133 CMVideoDimensions coded_dimensions = | 145 CMVideoDimensions coded_dimensions = |
| 134 CMVideoFormatDescriptionGetDimensions(format_); | 146 CMVideoFormatDescriptionGetDimensions(format_); |
| 135 | 147 |
| 136 // Prepare VideoToolbox configuration dictionaries. | 148 // Prepare VideoToolbox configuration dictionaries. |
| 137 base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( | 149 base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( |
| 138 CFDictionaryCreateMutable( | 150 CFDictionaryCreateMutable( |
| 139 kCFAllocatorDefault, | 151 kCFAllocatorDefault, |
| 140 1, // capacity | 152 1, // capacity |
| 141 &kCFTypeDictionaryKeyCallBacks, | 153 &kCFTypeDictionaryKeyCallBacks, |
| 142 &kCFTypeDictionaryValueCallBacks)); | 154 &kCFTypeDictionaryValueCallBacks)); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 163 #undef CFINT | 175 #undef CFINT |
| 164 CFDictionarySetValue( | 176 CFDictionarySetValue( |
| 165 image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); | 177 image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); |
| 166 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); | 178 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); |
| 167 CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); | 179 CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); |
| 168 CFDictionarySetValue( | 180 CFDictionarySetValue( |
| 169 image_config, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); | 181 image_config, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); |
| 170 | 182 |
| 171 // TODO(sandersd): Check if the session is already compatible. | 183 // TODO(sandersd): Check if the session is already compatible. |
| 172 session_.reset(); | 184 session_.reset(); |
| 173 CHECK(!VTDecompressionSessionCreate( | 185 status = VTDecompressionSessionCreate( |
| 174 kCFAllocatorDefault, | 186 kCFAllocatorDefault, |
| 175 format_, // video_format_description | 187 format_, // video_format_description |
| 176 decoder_config, // video_decoder_specification | 188 decoder_config, // video_decoder_specification |
| 177 image_config, // destination_image_buffer_attributes | 189 image_config, // destination_image_buffer_attributes |
| 178 &callback_, // output_callback | 190 &callback_, // output_callback |
| 179 session_.InitializeInto())); | 191 session_.InitializeInto()); |
| 192 if (status) { | |
| 193 NOTIFY_STATUS("VTDecompressionSessionCreate()", status); | |
| 194 return false; | |
| 195 } | |
| 180 | 196 |
| 181 // If the size has changed, trigger a request for new picture buffers. | 197 // If the size has changed, trigger a request for new picture buffers. |
| 182 // TODO(sandersd): Move to SendPictures(), and use this just as a hint for an | 198 // TODO(sandersd): Move to SendPictures(), and use this just as a hint for an |
| 183 // upcoming size change. | 199 // upcoming size change. |
| 184 gfx::Size new_coded_size(coded_dimensions.width, coded_dimensions.height); | 200 gfx::Size new_coded_size(coded_dimensions.width, coded_dimensions.height); |
| 185 if (coded_size_ != new_coded_size) { | 201 if (coded_size_ != new_coded_size) { |
| 186 coded_size_ = new_coded_size; | 202 coded_size_ = new_coded_size; |
| 187 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 203 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 188 &VTVideoDecodeAccelerator::SizeChangedTask, | 204 &VTVideoDecodeAccelerator::SizeChangedTask, |
| 189 weak_this_factory_.GetWeakPtr(), | 205 weak_this_factory_.GetWeakPtr(), |
| 190 coded_size_));; | 206 coded_size_));; |
| 191 } | 207 } |
| 208 | |
| 209 return true; | |
| 192 } | 210 } |
| 193 | 211 |
| 194 void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { | 212 void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { |
| 195 DCHECK(CalledOnValidThread()); | 213 DCHECK(CalledOnValidThread()); |
| 196 CHECK_GE(bitstream.id(), 0) << "Negative bitstream_id"; | 214 // Not actually a requirement of the VDA API, but we're lazy and use negative |
| 215 // values as flags internally. Revisit that if this actually happens. | |
| 216 if (bitstream.id() < 0) { | |
| 217 LOG(ERROR) << "Negative bitstream ID"; | |
| 218 NotifyError(INVALID_ARGUMENT); | |
| 219 client_->NotifyEndOfBitstreamBuffer(bitstream.id()); | |
| 220 return; | |
| 221 } | |
| 197 pending_bitstream_ids_.push(bitstream.id()); | 222 pending_bitstream_ids_.push(bitstream.id()); |
| 198 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( | 223 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
| 199 &VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), | 224 &VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), |
| 200 bitstream)); | 225 bitstream)); |
| 201 } | 226 } |
| 202 | 227 |
| 203 // TODO(sandersd): Proper error reporting instead of CHECKs. | |
| 204 void VTVideoDecodeAccelerator::DecodeTask( | 228 void VTVideoDecodeAccelerator::DecodeTask( |
| 205 const media::BitstreamBuffer bitstream) { | 229 const media::BitstreamBuffer& bitstream) { |
| 206 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 230 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 207 | 231 |
| 208 // Map the bitstream buffer. | 232 // Map the bitstream buffer. |
| 209 base::SharedMemory memory(bitstream.handle(), true); | 233 base::SharedMemory memory(bitstream.handle(), true); |
| 210 size_t size = bitstream.size(); | 234 size_t size = bitstream.size(); |
| 211 CHECK(memory.Map(size)); | 235 if (!memory.Map(size)) { |
| 236 LOG(ERROR) << "Failed to map bitstream buffer"; | |
| 237 NotifyError(PLATFORM_FAILURE); | |
| 238 DropFrame(bitstream.id()); | |
| 239 return; | |
| 240 } | |
| 212 const uint8_t* buf = static_cast<uint8_t*>(memory.memory()); | 241 const uint8_t* buf = static_cast<uint8_t*>(memory.memory()); |
| 213 | 242 |
| 214 // NALUs are stored with Annex B format in the bitstream buffer (start codes), | 243 // NALUs are stored with Annex B format in the bitstream buffer (start codes), |
| 215 // but VideoToolbox expects AVCC/MPEG-4 format (length headers), so we must | 244 // but VideoToolbox expects AVCC/MPEG-4 format (length headers), so we must |
| 216 // rewrite the data. | 245 // rewrite the data. |
| 217 // | 246 // |
| 218 // 1. Locate relevant NALUs and compute the size of the translated data. | 247 // 1. Locate relevant NALUs and compute the size of the translated data. |
| 219 // Also record any parameter sets for VideoToolbox initialization. | 248 // Also record any parameter sets for VideoToolbox initialization. |
| 220 size_t data_size = 0; | 249 size_t data_size = 0; |
| 221 std::vector<media::H264NALU> nalus; | 250 std::vector<media::H264NALU> nalus; |
| 222 std::vector<const uint8_t*> config_nalu_data_ptrs; | 251 std::vector<const uint8_t*> config_nalu_data_ptrs; |
| 223 std::vector<size_t> config_nalu_data_sizes; | 252 std::vector<size_t> config_nalu_data_sizes; |
| 224 parser_.SetStream(buf, size); | 253 parser_.SetStream(buf, size); |
| 225 media::H264NALU nalu; | 254 media::H264NALU nalu; |
| 226 while (true) { | 255 while (true) { |
| 227 media::H264Parser::Result result = parser_.AdvanceToNextNALU(&nalu); | 256 media::H264Parser::Result result = parser_.AdvanceToNextNALU(&nalu); |
| 228 if (result == media::H264Parser::kEOStream) | 257 if (result == media::H264Parser::kEOStream) |
| 229 break; | 258 break; |
| 230 CHECK_EQ(result, media::H264Parser::kOk); | 259 if (result != media::H264Parser::kOk) { |
| 260 LOG(ERROR) << "Failed to find H.264 NALU"; | |
| 261 NotifyError(PLATFORM_FAILURE); | |
| 262 DropFrame(bitstream.id()); | |
| 263 return; | |
| 264 } | |
| 231 // TODO(sandersd): Check that these are only at the start. | 265 // TODO(sandersd): Check that these are only at the start. |
| 232 if (nalu.nal_unit_type == media::H264NALU::kSPS || | 266 if (nalu.nal_unit_type == media::H264NALU::kSPS || |
| 233 nalu.nal_unit_type == media::H264NALU::kPPS || | 267 nalu.nal_unit_type == media::H264NALU::kPPS || |
| 234 nalu.nal_unit_type == media::H264NALU::kSPSExt) { | 268 nalu.nal_unit_type == media::H264NALU::kSPSExt) { |
| 235 DVLOG(2) << "Parameter set " << nalu.nal_unit_type; | 269 DVLOG(2) << "Parameter set " << nalu.nal_unit_type; |
| 236 config_nalu_data_ptrs.push_back(nalu.data); | 270 config_nalu_data_ptrs.push_back(nalu.data); |
| 237 config_nalu_data_sizes.push_back(nalu.size); | 271 config_nalu_data_sizes.push_back(nalu.size); |
| 238 } else { | 272 } else { |
| 239 nalus.push_back(nalu); | 273 nalus.push_back(nalu); |
| 240 data_size += kNALUHeaderLength + nalu.size; | 274 data_size += kNALUHeaderLength + nalu.size; |
| 241 } | 275 } |
| 242 } | 276 } |
| 243 | 277 |
| 244 // 2. Initialize VideoToolbox. | 278 // 2. Initialize VideoToolbox. |
| 245 // TODO(sandersd): Reinitialize when there are new parameter sets. | 279 // TODO(sandersd): Reinitialize when there are new parameter sets. |
| 246 if (!session_) | 280 if (!session_) { |
| 247 ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes); | 281 if (!ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes)) { |
| 282 // ConfigureDecoder() already called NotifyError(). | |
| 283 DropFrame(bitstream.id()); | |
| 284 return; | |
| 285 } | |
| 286 } | |
| 248 | 287 |
| 249 // If there are no non-configuration units, immediately return an empty | 288 // If there are no non-configuration units, immediately return an empty |
| 250 // (ie. dropped) frame. It is an error to create a MemoryBlock with zero | 289 // (ie. dropped) frame. It is an error to create a MemoryBlock with zero |
| 251 // size. | 290 // size. |
| 252 if (!data_size) { | 291 if (!data_size) { |
| 253 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 292 DropFrame(bitstream.id()); |
| 254 &VTVideoDecodeAccelerator::OutputTask, | |
| 255 weak_this_factory_.GetWeakPtr(), | |
| 256 DecodedFrame(bitstream.id(), NULL))); | |
| 257 return; | 293 return; |
| 258 } | 294 } |
| 259 | 295 |
| 260 // 3. Allocate a memory-backed CMBlockBuffer for the translated data. | 296 // 3. Allocate a memory-backed CMBlockBuffer for the translated data. |
| 261 base::ScopedCFTypeRef<CMBlockBufferRef> data; | 297 base::ScopedCFTypeRef<CMBlockBufferRef> data; |
| 262 CHECK(!CMBlockBufferCreateWithMemoryBlock( | 298 OSStatus status = CMBlockBufferCreateWithMemoryBlock( |
| 263 kCFAllocatorDefault, | 299 kCFAllocatorDefault, |
| 264 NULL, // &memory_block | 300 NULL, // &memory_block |
| 265 data_size, // block_length | 301 data_size, // block_length |
| 266 kCFAllocatorDefault, // block_allocator | 302 kCFAllocatorDefault, // block_allocator |
| 267 NULL, // &custom_block_source | 303 NULL, // &custom_block_source |
| 268 0, // offset_to_data | 304 0, // offset_to_data |
| 269 data_size, // data_length | 305 data_size, // data_length |
| 270 0, // flags | 306 0, // flags |
| 271 data.InitializeInto())); | 307 data.InitializeInto()); |
| 308 if (status) { | |
| 309 NOTIFY_STATUS("CMBlockBufferCreateWithMemoryBlock()", status); | |
| 310 DropFrame(bitstream.id()); | |
| 311 return; | |
| 312 } | |
| 272 | 313 |
| 273 // 4. Copy NALU data, inserting length headers. | 314 // 4. Copy NALU data, inserting length headers. |
| 274 size_t offset = 0; | 315 size_t offset = 0; |
| 275 for (size_t i = 0; i < nalus.size(); i++) { | 316 for (size_t i = 0; i < nalus.size(); i++) { |
| 276 media::H264NALU& nalu = nalus[i]; | 317 media::H264NALU& nalu = nalus[i]; |
| 277 uint32_t header = base::HostToNet32(static_cast<uint32_t>(nalu.size)); | 318 uint32_t header = base::HostToNet32(static_cast<uint32_t>(nalu.size)); |
| 278 CHECK(!CMBlockBufferReplaceDataBytes( | 319 status = CMBlockBufferReplaceDataBytes( |
| 279 &header, data, offset, kNALUHeaderLength)); | 320 &header, data, offset, kNALUHeaderLength); |
| 321 if (status) { | |
| 322 NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); | |
| 323 DropFrame(bitstream.id()); | |
| 324 return; | |
| 325 } | |
| 280 offset += kNALUHeaderLength; | 326 offset += kNALUHeaderLength; |
| 281 CHECK(!CMBlockBufferReplaceDataBytes(nalu.data, data, offset, nalu.size)); | 327 status = CMBlockBufferReplaceDataBytes(nalu.data, data, offset, nalu.size); |
| 328 if (status) { | |
| 329 NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); | |
| 330 DropFrame(bitstream.id()); | |
| 331 return; | |
| 332 } | |
| 282 offset += nalu.size; | 333 offset += nalu.size; |
| 283 } | 334 } |
| 284 | 335 |
| 285 // 5. Package the data for VideoToolbox and request decoding. | 336 // 5. Package the data for VideoToolbox and request decoding. |
| 286 base::ScopedCFTypeRef<CMSampleBufferRef> frame; | 337 base::ScopedCFTypeRef<CMSampleBufferRef> frame; |
| 287 CHECK(!CMSampleBufferCreate( | 338 status = CMSampleBufferCreate( |
| 288 kCFAllocatorDefault, | 339 kCFAllocatorDefault, |
| 289 data, // data_buffer | 340 data, // data_buffer |
| 290 true, // data_ready | 341 true, // data_ready |
| 291 NULL, // make_data_ready_callback | 342 NULL, // make_data_ready_callback |
| 292 NULL, // make_data_ready_refcon | 343 NULL, // make_data_ready_refcon |
| 293 format_, // format_description | 344 format_, // format_description |
| 294 1, // num_samples | 345 1, // num_samples |
| 295 0, // num_sample_timing_entries | 346 0, // num_sample_timing_entries |
| 296 NULL, // &sample_timing_array | 347 NULL, // &sample_timing_array |
| 297 0, // num_sample_size_entries | 348 0, // num_sample_size_entries |
| 298 NULL, // &sample_size_array | 349 NULL, // &sample_size_array |
| 299 frame.InitializeInto())); | 350 frame.InitializeInto()); |
| 351 if (status) { | |
| 352 NOTIFY_STATUS("CMSampleBufferCreate()", status); | |
| 353 DropFrame(bitstream.id()); | |
| 354 return; | |
| 355 } | |
| 300 | 356 |
| 301 // Asynchronous Decompression allows for parallel submission of frames | 357 // Asynchronous Decompression allows for parallel submission of frames |
| 302 // (without it, DecodeFrame() does not return until the frame has been | 358 // (without it, DecodeFrame() does not return until the frame has been |
| 303 // decoded). We don't enable Temporal Processing so that frames are always | 359 // decoded). We don't enable Temporal Processing so that frames are always |
| 304 // returned in decode order; this makes it easier to avoid deadlock. | 360 // returned in decode order; this makes it easier to avoid deadlock. |
| 305 VTDecodeFrameFlags decode_flags = | 361 VTDecodeFrameFlags decode_flags = |
| 306 kVTDecodeFrame_EnableAsynchronousDecompression; | 362 kVTDecodeFrame_EnableAsynchronousDecompression; |
| 307 | 363 |
| 308 intptr_t bitstream_id = bitstream.id(); | 364 intptr_t bitstream_id = bitstream.id(); |
| 309 CHECK(!VTDecompressionSessionDecodeFrame( | 365 status = VTDecompressionSessionDecodeFrame( |
| 310 session_, | 366 session_, |
| 311 frame, // sample_buffer | 367 frame, // sample_buffer |
| 312 decode_flags, // decode_flags | 368 decode_flags, // decode_flags |
| 313 reinterpret_cast<void*>(bitstream_id), // source_frame_refcon | 369 reinterpret_cast<void*>(bitstream_id), // source_frame_refcon |
| 314 NULL)); // &info_flags_out | 370 NULL); // &info_flags_out |
| 371 if (status) { | |
| 372 NOTIFY_STATUS("VTDecompressionSessionDecodeFrame()", status); | |
| 373 DropFrame(bitstream.id()); | |
| 374 return; | |
| 375 } | |
| 315 } | 376 } |
| 316 | 377 |
| 317 // This method may be called on any VideoToolbox thread. | 378 // This method may be called on any VideoToolbox thread. |
| 318 // TODO(sandersd): Proper error reporting instead of CHECKs. | |
| 319 void VTVideoDecodeAccelerator::Output( | 379 void VTVideoDecodeAccelerator::Output( |
| 320 int32_t bitstream_id, | 380 int32_t bitstream_id, |
| 321 OSStatus status, | 381 OSStatus status, |
| 322 CVImageBufferRef image_buffer) { | 382 CVImageBufferRef image_buffer) { |
| 323 CHECK(!status); | 383 // TODO(sandersd): Handle dropped frames. |
| 324 CHECK_EQ(CFGetTypeID(image_buffer), CVPixelBufferGetTypeID()); | 384 if (status) { |
| 385 NOTIFY_STATUS("Decoding", status); | |
| 386 DropFrame(bitstream_id); | |
| 387 return; | |
| 388 } | |
| 389 if (CFGetTypeID(image_buffer) != CVPixelBufferGetTypeID()) { | |
| 390 LOG(ERROR) << "Decoded frame is not a CVPixelBuffer"; | |
| 391 NotifyError(PLATFORM_FAILURE); | |
| 392 DropFrame(bitstream_id); | |
| 393 return; | |
| 394 } | |
| 325 CFRetain(image_buffer); | 395 CFRetain(image_buffer); |
| 326 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 396 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 327 &VTVideoDecodeAccelerator::OutputTask, | 397 &VTVideoDecodeAccelerator::OutputTask, |
| 328 weak_this_factory_.GetWeakPtr(), | 398 weak_this_factory_.GetWeakPtr(), |
| 329 DecodedFrame(bitstream_id, image_buffer))); | 399 DecodedFrame(bitstream_id, image_buffer))); |
| 330 } | 400 } |
| 331 | 401 |
| 332 void VTVideoDecodeAccelerator::OutputTask(DecodedFrame frame) { | 402 void VTVideoDecodeAccelerator::OutputTask(DecodedFrame frame) { |
| 333 DCHECK(CalledOnValidThread()); | 403 DCHECK(CalledOnValidThread()); |
| 334 decoded_frames_.push(frame); | 404 decoded_frames_.push(frame); |
| 335 ProcessDecodedFrames(); | 405 ProcessDecodedFrames(); |
| 336 } | 406 } |
| 337 | 407 |
| 338 void VTVideoDecodeAccelerator::SizeChangedTask(gfx::Size coded_size) { | 408 void VTVideoDecodeAccelerator::SizeChangedTask(gfx::Size coded_size) { |
| 339 DCHECK(CalledOnValidThread()); | 409 DCHECK(CalledOnValidThread()); |
| 340 texture_size_ = coded_size; | 410 texture_size_ = coded_size; |
| 341 // TODO(sandersd): Dismiss existing picture buffers. | 411 // TODO(sandersd): Dismiss existing picture buffers. |
| 342 client_->ProvidePictureBuffers( | 412 client_->ProvidePictureBuffers( |
| 343 kNumPictureBuffers, texture_size_, GL_TEXTURE_RECTANGLE_ARB); | 413 kNumPictureBuffers, texture_size_, GL_TEXTURE_RECTANGLE_ARB); |
| 344 } | 414 } |
| 345 | 415 |
| 346 void VTVideoDecodeAccelerator::AssignPictureBuffers( | 416 void VTVideoDecodeAccelerator::AssignPictureBuffers( |
| 347 const std::vector<media::PictureBuffer>& pictures) { | 417 const std::vector<media::PictureBuffer>& pictures) { |
| 348 DCHECK(CalledOnValidThread()); | 418 DCHECK(CalledOnValidThread()); |
| 349 | 419 |
| 350 for (size_t i = 0; i < pictures.size(); i++) { | 420 for (size_t i = 0; i < pictures.size(); i++) { |
| 351 CHECK(!texture_ids_.count(pictures[i].id())); | 421 DCHECK(!texture_ids_.count(pictures[i].id())); |
| 352 available_picture_ids_.push(pictures[i].id()); | 422 available_picture_ids_.push(pictures[i].id()); |
| 353 texture_ids_[pictures[i].id()] = pictures[i].texture_id(); | 423 texture_ids_[pictures[i].id()] = pictures[i].texture_id(); |
| 354 } | 424 } |
| 355 | 425 |
| 356 // Pictures are not marked as uncleared until after this method returns, and | 426 // Pictures are not marked as uncleared until after this method returns, and |
| 357 // they will be broken if they are used before that happens. So, schedule | 427 // they will be broken if they are used before that happens. So, schedule |
| 358 // future work after that happens. | 428 // future work after that happens. |
| 359 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 429 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 360 &VTVideoDecodeAccelerator::ProcessDecodedFrames, | 430 &VTVideoDecodeAccelerator::ProcessDecodedFrames, |
| 361 weak_this_factory_.GetWeakPtr())); | 431 weak_this_factory_.GetWeakPtr())); |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 392 pending_actions_.pop(); | 462 pending_actions_.pop(); |
| 393 } | 463 } |
| 394 } | 464 } |
| 395 | 465 |
| 396 void VTVideoDecodeAccelerator::ProcessDecodedFrames() { | 466 void VTVideoDecodeAccelerator::ProcessDecodedFrames() { |
| 397 DCHECK(CalledOnValidThread()); | 467 DCHECK(CalledOnValidThread()); |
| 398 | 468 |
| 399 while (!decoded_frames_.empty()) { | 469 while (!decoded_frames_.empty()) { |
| 400 if (pending_actions_.empty()) { | 470 if (pending_actions_.empty()) { |
| 401 // No pending actions; send frames normally. | 471 // No pending actions; send frames normally. |
| 402 SendPictures(pending_bitstream_ids_.back()); | 472 if (!has_error_) |
| 473 SendPictures(pending_bitstream_ids_.back()); | |
| 403 return; | 474 return; |
| 404 } | 475 } |
| 405 | 476 |
| 406 int32_t next_action_bitstream_id = pending_actions_.front().bitstream_id; | 477 int32_t next_action_bitstream_id = pending_actions_.front().bitstream_id; |
| 407 int32_t last_sent_bitstream_id = -1; | 478 int32_t last_sent_bitstream_id = -1; |
| 408 switch (pending_actions_.front().action) { | 479 switch (pending_actions_.front().action) { |
| 409 case ACTION_FLUSH: | 480 case ACTION_FLUSH: |
| 410 // Send frames normally. | 481 // Send frames normally. |
| 482 if (has_error_) | |
| 483 return; | |
| 411 last_sent_bitstream_id = SendPictures(next_action_bitstream_id); | 484 last_sent_bitstream_id = SendPictures(next_action_bitstream_id); |
| 412 break; | 485 break; |
| 413 | 486 |
| 414 case ACTION_RESET: | 487 case ACTION_RESET: |
|
Pawel Osciak
2014/10/22 10:45:21
Do we still want to handle this here if has_error_
sandersd (OOO until July 31)
2014/10/22 22:15:45
I couldn't tell what the correct behavior was here
| |
| 415 // Drop decoded frames. | 488 // Drop decoded frames. |
| 416 while (!decoded_frames_.empty() && | 489 while (!decoded_frames_.empty() && |
| 417 last_sent_bitstream_id != next_action_bitstream_id) { | 490 last_sent_bitstream_id != next_action_bitstream_id) { |
| 418 last_sent_bitstream_id = decoded_frames_.front().bitstream_id; | 491 last_sent_bitstream_id = decoded_frames_.front().bitstream_id; |
| 419 decoded_frames_.pop(); | 492 decoded_frames_.pop(); |
| 420 DCHECK_EQ(pending_bitstream_ids_.front(), last_sent_bitstream_id); | 493 DCHECK_EQ(pending_bitstream_ids_.front(), last_sent_bitstream_id); |
| 421 pending_bitstream_ids_.pop(); | 494 pending_bitstream_ids_.pop(); |
| 422 client_->NotifyEndOfBitstreamBuffer(last_sent_bitstream_id); | 495 client_->NotifyEndOfBitstreamBuffer(last_sent_bitstream_id); |
| 423 } | 496 } |
| 424 break; | 497 break; |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 447 // Complete all actions pending for this |bitstream_id|, then loop to see | 520 // Complete all actions pending for this |bitstream_id|, then loop to see |
| 448 // if progress can be made on the next action. | 521 // if progress can be made on the next action. |
| 449 CompleteActions(next_action_bitstream_id); | 522 CompleteActions(next_action_bitstream_id); |
| 450 } | 523 } |
| 451 } | 524 } |
| 452 | 525 |
| 453 int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { | 526 int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { |
| 454 DCHECK(CalledOnValidThread()); | 527 DCHECK(CalledOnValidThread()); |
| 455 DCHECK(!decoded_frames_.empty()); | 528 DCHECK(!decoded_frames_.empty()); |
| 456 | 529 |
| 530 // TODO(sandersd): Store the actual last sent bitstream ID? | |
| 531 int32_t last_sent_bitstream_id = -1; | |
| 532 | |
| 457 if (available_picture_ids_.empty()) | 533 if (available_picture_ids_.empty()) |
| 458 return -1; | 534 return last_sent_bitstream_id; |
| 459 | 535 |
| 460 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(cgl_context_); | 536 gfx::ScopedCGLSetCurrentContext scoped_set_current_context(cgl_context_); |
| 461 glEnable(GL_TEXTURE_RECTANGLE_ARB); | 537 glEnable(GL_TEXTURE_RECTANGLE_ARB); |
| 462 | |
| 463 int32_t last_sent_bitstream_id = -1; | |
| 464 while (!available_picture_ids_.empty() && | 538 while (!available_picture_ids_.empty() && |
| 465 !decoded_frames_.empty() && | 539 !decoded_frames_.empty() && |
| 466 last_sent_bitstream_id != up_to_bitstream_id) { | 540 last_sent_bitstream_id != up_to_bitstream_id && |
| 467 DecodedFrame frame = decoded_frames_.front(); | 541 !has_error_) { |
| 468 decoded_frames_.pop(); | 542 // We don't pop |frame| until it is consumed, which won't happen if an |
| 543 // error occurs. Conveniently, this also removes some refcounting. | |
| 544 const DecodedFrame& frame = decoded_frames_.front(); | |
| 469 DCHECK_EQ(pending_bitstream_ids_.front(), frame.bitstream_id); | 545 DCHECK_EQ(pending_bitstream_ids_.front(), frame.bitstream_id); |
| 470 pending_bitstream_ids_.pop(); | 546 |
| 547 // Likewise, |picture_id| won't be popped if |image_buffer| is NULL or an | |
| 548 // error occurs. | |
| 549 // TODO(sandersd): Don't block waiting for a |picture_id| when | |
| 550 // |image_buffer| is NULL. | |
| 471 int32_t picture_id = available_picture_ids_.front(); | 551 int32_t picture_id = available_picture_ids_.front(); |
| 472 available_picture_ids_.pop(); | |
| 473 | 552 |
| 474 CVImageBufferRef image_buffer = frame.image_buffer.get(); | 553 CVImageBufferRef image_buffer = frame.image_buffer.get(); |
| 475 if (image_buffer) { | 554 if (image_buffer) { |
| 476 IOSurfaceRef surface = CVPixelBufferGetIOSurface(image_buffer); | 555 IOSurfaceRef surface = CVPixelBufferGetIOSurface(image_buffer); |
| 477 | 556 |
| 478 // TODO(sandersd): Find out why this sometimes fails due to no GL context. | 557 // TODO(sandersd): Find out why this sometimes fails due to no GL context. |
| 479 gfx::ScopedTextureBinder | 558 gfx::ScopedTextureBinder |
| 480 texture_binder(GL_TEXTURE_RECTANGLE_ARB, texture_ids_[picture_id]); | 559 texture_binder(GL_TEXTURE_RECTANGLE_ARB, texture_ids_[picture_id]); |
| 481 CHECK(!CGLTexImageIOSurface2D( | 560 CGLError status = CGLTexImageIOSurface2D( |
| 482 cgl_context_, // ctx | 561 cgl_context_, // ctx |
| 483 GL_TEXTURE_RECTANGLE_ARB, // target | 562 GL_TEXTURE_RECTANGLE_ARB, // target |
| 484 GL_RGB, // internal_format | 563 GL_RGB, // internal_format |
| 485 texture_size_.width(), // width | 564 texture_size_.width(), // width |
| 486 texture_size_.height(), // height | 565 texture_size_.height(), // height |
| 487 GL_YCBCR_422_APPLE, // format | 566 GL_YCBCR_422_APPLE, // format |
| 488 GL_UNSIGNED_SHORT_8_8_APPLE, // type | 567 GL_UNSIGNED_SHORT_8_8_APPLE, // type |
| 489 surface, // io_surface | 568 surface, // io_surface |
| 490 0)); // plane | 569 0); // plane |
| 570 if (status != kCGLNoError) { | |
| 571 NOTIFY_STATUS("CGLTexImageIOSurface2D()", status); | |
| 572 break; | |
| 573 } | |
| 491 | 574 |
| 492 picture_bindings_[picture_id] = frame.image_buffer; | 575 picture_bindings_[picture_id] = frame.image_buffer; |
| 493 client_->PictureReady(media::Picture( | 576 client_->PictureReady(media::Picture( |
| 494 picture_id, frame.bitstream_id, gfx::Rect(texture_size_))); | 577 picture_id, frame.bitstream_id, gfx::Rect(texture_size_))); |
| 578 available_picture_ids_.pop(); | |
| 495 } | 579 } |
| 496 | 580 |
| 497 client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); | 581 client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); |
| 498 last_sent_bitstream_id = frame.bitstream_id; | 582 last_sent_bitstream_id = frame.bitstream_id; |
| 583 decoded_frames_.pop(); | |
| 584 pending_bitstream_ids_.pop(); | |
| 499 } | 585 } |
| 500 | |
| 501 glDisable(GL_TEXTURE_RECTANGLE_ARB); | 586 glDisable(GL_TEXTURE_RECTANGLE_ARB); |
| 502 return last_sent_bitstream_id; | 587 return last_sent_bitstream_id; |
| 503 } | 588 } |
| 504 | 589 |
| 505 void VTVideoDecodeAccelerator::FlushTask() { | 590 void VTVideoDecodeAccelerator::FlushTask() { |
| 506 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 591 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 507 CHECK(!VTDecompressionSessionFinishDelayedFrames(session_)); | 592 OSStatus status = VTDecompressionSessionFinishDelayedFrames(session_); |
| 593 if (status) | |
| 594 NOTIFY_STATUS("VTDecompressionSessionFinishDelayedFrames()", status); | |
| 508 } | 595 } |
| 509 | 596 |
| 510 void VTVideoDecodeAccelerator::QueueAction(Action action) { | 597 void VTVideoDecodeAccelerator::QueueAction(Action action) { |
| 511 DCHECK(CalledOnValidThread()); | 598 DCHECK(CalledOnValidThread()); |
| 512 if (pending_bitstream_ids_.empty()) { | 599 if (pending_bitstream_ids_.empty()) { |
| 513 // If there are no pending frames, all actions complete immediately. | 600 // If there are no pending frames, all actions complete immediately. |
| 514 CompleteAction(action); | 601 CompleteAction(action); |
| 515 } else { | 602 } else { |
| 516 // Otherwise, queue the action. | 603 // Otherwise, queue the action. |
| 517 pending_actions_.push(PendingAction(action, pending_bitstream_ids_.back())); | 604 pending_actions_.push(PendingAction(action, pending_bitstream_ids_.back())); |
| 518 | 605 |
| 519 // Request a flush to make sure the action will eventually complete. | 606 // Request a flush to make sure the action will eventually complete. |
| 520 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( | 607 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
| 521 &VTVideoDecodeAccelerator::FlushTask, base::Unretained(this))); | 608 &VTVideoDecodeAccelerator::FlushTask, base::Unretained(this))); |
| 522 | 609 |
| 523 // See if we can make progress now that there is a new pending action. | 610 // See if we can make progress now that there is a new pending action. |
| 524 ProcessDecodedFrames(); | 611 ProcessDecodedFrames(); |
| 525 } | 612 } |
| 526 } | 613 } |
| 527 | 614 |
| 615 void VTVideoDecodeAccelerator::NotifyError(Error error) { | |
| 616 if (!CalledOnValidThread()) { | |
| 617 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 618 &VTVideoDecodeAccelerator::NotifyError, | |
| 619 weak_this_factory_.GetWeakPtr(), | |
| 620 error)); | |
| 621 return; | |
| 622 } | |
| 623 has_error_ = true; | |
| 624 client_->NotifyError(error); | |
| 625 } | |
| 626 | |
| 627 void VTVideoDecodeAccelerator::DropFrame(int32_t bitstream_id) { | |
|
Pawel Osciak
2014/10/22 10:45:21
There's a lot of DropFrame() around and it makes m
sandersd (OOO until July 31)
2014/10/22 22:15:45
I don't really see an alternative; either we succe
| |
| 628 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | |
| 629 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | |
|
Pawel Osciak
2014/10/22 10:45:21
It's not exactly clear to me why we need to dutifu
sandersd (OOO until July 31)
2014/10/22 22:15:45
There is a comment now where the ScopedClosureRunn
| |
| 630 &VTVideoDecodeAccelerator::OutputTask, | |
| 631 weak_this_factory_.GetWeakPtr(), | |
| 632 DecodedFrame(bitstream_id, NULL))); | |
| 633 } | |
| 634 | |
| 528 void VTVideoDecodeAccelerator::Flush() { | 635 void VTVideoDecodeAccelerator::Flush() { |
| 529 DCHECK(CalledOnValidThread()); | 636 DCHECK(CalledOnValidThread()); |
| 530 QueueAction(ACTION_FLUSH); | 637 QueueAction(ACTION_FLUSH); |
| 531 } | 638 } |
| 532 | 639 |
| 533 void VTVideoDecodeAccelerator::Reset() { | 640 void VTVideoDecodeAccelerator::Reset() { |
| 534 DCHECK(CalledOnValidThread()); | 641 DCHECK(CalledOnValidThread()); |
| 535 QueueAction(ACTION_RESET); | 642 QueueAction(ACTION_RESET); |
| 536 } | 643 } |
| 537 | 644 |
| 538 void VTVideoDecodeAccelerator::Destroy() { | 645 void VTVideoDecodeAccelerator::Destroy() { |
| 539 DCHECK(CalledOnValidThread()); | 646 DCHECK(CalledOnValidThread()); |
| 540 // Drop any other pending actions. | 647 // Drop any other pending actions. |
| 541 while (!pending_actions_.empty()) | 648 while (!pending_actions_.empty()) |
| 542 pending_actions_.pop(); | 649 pending_actions_.pop(); |
| 543 // Return all bitstream buffers. | 650 // Return all bitstream buffers. |
| 544 while (!pending_bitstream_ids_.empty()) { | 651 while (!pending_bitstream_ids_.empty()) { |
| 545 client_->NotifyEndOfBitstreamBuffer(pending_bitstream_ids_.front()); | 652 client_->NotifyEndOfBitstreamBuffer(pending_bitstream_ids_.front()); |
| 546 pending_bitstream_ids_.pop(); | 653 pending_bitstream_ids_.pop(); |
| 547 } | 654 } |
| 548 QueueAction(ACTION_DESTROY); | 655 QueueAction(ACTION_DESTROY); |
| 549 } | 656 } |
| 550 | 657 |
| 551 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { | 658 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |
| 552 return false; | 659 return false; |
| 553 } | 660 } |
| 554 | 661 |
| 555 } // namespace content | 662 } // namespace content |
| OLD | NEW |