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/callback_helpers.h" | |
| 11 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 12 #include "base/sys_byteorder.h" | 11 #include "base/sys_byteorder.h" |
| 13 #include "base/thread_task_runner_handle.h" | 12 #include "base/thread_task_runner_handle.h" |
| 14 #include "content/common/gpu/media/vt_video_decode_accelerator.h" | 13 #include "content/common/gpu/media/vt_video_decode_accelerator.h" |
| 15 #include "content/public/common/content_switches.h" | 14 #include "content/public/common/content_switches.h" |
| 16 #include "media/filters/h264_parser.h" | 15 #include "media/filters/h264_parser.h" |
| 17 #include "ui/gl/scoped_binders.h" | 16 #include "ui/gl/scoped_binders.h" |
| 18 | 17 |
| 19 using content_common_gpu_media::kModuleVt; | 18 using content_common_gpu_media::kModuleVt; |
| 20 using content_common_gpu_media::InitializeStubs; | 19 using content_common_gpu_media::InitializeStubs; |
| 21 using content_common_gpu_media::IsVtInitialized; | 20 using content_common_gpu_media::IsVtInitialized; |
| 22 using content_common_gpu_media::StubPathMap; | 21 using content_common_gpu_media::StubPathMap; |
| 23 | 22 |
| 24 #define NOTIFY_STATUS(name, status) \ | 23 #define NOTIFY_STATUS(name, status) \ |
| 25 do { \ | 24 do { \ |
| 26 LOG(ERROR) << name << " failed with status " << status; \ | 25 DLOG(ERROR) << name << " failed with status " << status; \ |
| 27 NotifyError(PLATFORM_FAILURE); \ | 26 NotifyError(PLATFORM_FAILURE); \ |
| 28 } while (0) | 27 } while (0) |
| 29 | 28 |
| 30 namespace content { | 29 namespace content { |
| 31 | 30 |
| 32 // Size of NALU length headers in AVCC/MPEG-4 format (can be 1, 2, or 4). | 31 // Size to use for NALU length headers in AVC format (can be 1, 2, or 4). |
| 33 static const int kNALUHeaderLength = 4; | 32 static const int kNALUHeaderLength = 4; |
| 34 | 33 |
| 35 // We only request 5 picture buffers from the client which are used to hold the | 34 // We request 5 picture buffers from the client, each of which has a texture ID |
| 36 // decoded samples. These buffers are then reused when the client tells us that | 35 // that we can bind decoded frames to. We need enough to satisfy preroll, and |
| 37 // it is done with the buffer. | 36 // enough to avoid unnecessary stalling, but no more than that. The resource |
| 37 // requirements are low, as we don't need the textures to be backed by storage. | |
| 38 static const int kNumPictureBuffers = 5; | 38 static const int kNumPictureBuffers = 5; |
| 39 | 39 |
| 40 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator. | 40 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator. |
| 41 static void OutputThunk( | 41 static void OutputThunk( |
| 42 void* decompression_output_refcon, | 42 void* decompression_output_refcon, |
| 43 void* source_frame_refcon, | 43 void* source_frame_refcon, |
| 44 OSStatus status, | 44 OSStatus status, |
| 45 VTDecodeInfoFlags info_flags, | 45 VTDecodeInfoFlags info_flags, |
| 46 CVImageBufferRef image_buffer, | 46 CVImageBufferRef image_buffer, |
| 47 CMTime presentation_time_stamp, | 47 CMTime presentation_time_stamp, |
| 48 CMTime presentation_duration) { | 48 CMTime presentation_duration) { |
| 49 VTVideoDecodeAccelerator* vda = | 49 VTVideoDecodeAccelerator* vda = |
| 50 reinterpret_cast<VTVideoDecodeAccelerator*>(decompression_output_refcon); | 50 reinterpret_cast<VTVideoDecodeAccelerator*>(decompression_output_refcon); |
| 51 int32_t bitstream_id = reinterpret_cast<intptr_t>(source_frame_refcon); | 51 vda->Output(source_frame_refcon, status, image_buffer); |
| 52 vda->Output(bitstream_id, status, image_buffer); | |
| 53 } | 52 } |
| 54 | 53 |
| 55 VTVideoDecodeAccelerator::DecodedFrame::DecodedFrame( | 54 VTVideoDecodeAccelerator::Task::Task(TaskType type) : type(type) { |
| 56 int32_t bitstream_id, | |
| 57 CVImageBufferRef image_buffer) | |
| 58 : bitstream_id(bitstream_id), | |
| 59 image_buffer(image_buffer) { | |
| 60 } | 55 } |
| 61 | 56 |
| 62 VTVideoDecodeAccelerator::DecodedFrame::~DecodedFrame() { | 57 VTVideoDecodeAccelerator::Task::~Task() { |
| 63 } | 58 } |
| 64 | 59 |
| 65 VTVideoDecodeAccelerator::PendingAction::PendingAction( | 60 VTVideoDecodeAccelerator::Frame::Frame(int32_t bitstream_id) |
| 66 Action action, | 61 : bitstream_id(bitstream_id) { |
| 67 int32_t bitstream_id) | |
| 68 : action(action), | |
| 69 bitstream_id(bitstream_id) { | |
| 70 } | 62 } |
| 71 | 63 |
| 72 VTVideoDecodeAccelerator::PendingAction::~PendingAction() { | 64 VTVideoDecodeAccelerator::Frame::~Frame() { |
| 73 } | 65 } |
| 74 | 66 |
| 75 VTVideoDecodeAccelerator::VTVideoDecodeAccelerator( | 67 VTVideoDecodeAccelerator::VTVideoDecodeAccelerator( |
| 76 CGLContextObj cgl_context, | 68 CGLContextObj cgl_context, |
| 77 const base::Callback<bool(void)>& make_context_current) | 69 const base::Callback<bool(void)>& make_context_current) |
| 78 : cgl_context_(cgl_context), | 70 : cgl_context_(cgl_context), |
| 79 make_context_current_(make_context_current), | 71 make_context_current_(make_context_current), |
| 80 client_(NULL), | 72 client_(NULL), |
| 81 has_error_(false), | 73 state_(STATE_NORMAL), |
| 82 format_(NULL), | 74 format_(NULL), |
| 83 session_(NULL), | 75 session_(NULL), |
| 84 gpu_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 76 gpu_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 85 weak_this_factory_(this), | 77 weak_this_factory_(this), |
| 86 decoder_thread_("VTDecoderThread") { | 78 decoder_thread_("VTDecoderThread") { |
| 87 DCHECK(!make_context_current_.is_null()); | 79 DCHECK(!make_context_current_.is_null()); |
| 88 callback_.decompressionOutputCallback = OutputThunk; | 80 callback_.decompressionOutputCallback = OutputThunk; |
| 89 callback_.decompressionOutputRefCon = this; | 81 callback_.decompressionOutputRefCon = this; |
| 90 } | 82 } |
| 91 | 83 |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 119 return false; | 111 return false; |
| 120 } | 112 } |
| 121 | 113 |
| 122 // Spawn a thread to handle parsing and calling VideoToolbox. | 114 // Spawn a thread to handle parsing and calling VideoToolbox. |
| 123 if (!decoder_thread_.Start()) | 115 if (!decoder_thread_.Start()) |
| 124 return false; | 116 return false; |
| 125 | 117 |
| 126 return true; | 118 return true; |
| 127 } | 119 } |
| 128 | 120 |
| 129 bool VTVideoDecodeAccelerator::ConfigureDecoder( | 121 bool VTVideoDecodeAccelerator::FinishDelayedFrames() { |
| 130 const std::vector<const uint8_t*>& nalu_data_ptrs, | |
| 131 const std::vector<size_t>& nalu_data_sizes) { | |
| 132 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 122 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 123 if (session_) { | |
| 124 OSStatus status = VTDecompressionSessionFinishDelayedFrames(session_); | |
| 125 if (status) { | |
| 126 NOTIFY_STATUS("VTDecompressionSessionFinishDelayedFrames()", status); | |
| 127 return false; | |
| 128 } | |
| 129 } | |
| 130 return true; | |
| 131 } | |
| 132 | |
| 133 bool VTVideoDecodeAccelerator::ConfigureDecoder() { | |
| 134 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | |
| 135 DCHECK(!last_sps_.empty()); | |
| 136 DCHECK(!last_pps_.empty()); | |
| 137 | |
| 138 // Build the configuration records. | |
| 139 std::vector<const uint8_t*> nalu_data_ptrs; | |
| 140 std::vector<size_t> nalu_data_sizes; | |
| 141 nalu_data_ptrs.reserve(3); | |
| 142 nalu_data_sizes.reserve(3); | |
| 143 nalu_data_ptrs.push_back(&last_sps_.front()); | |
| 144 nalu_data_sizes.push_back(last_sps_.size()); | |
| 145 if (!last_spsext_.empty()) { | |
| 146 nalu_data_ptrs.push_back(&last_spsext_.front()); | |
| 147 nalu_data_sizes.push_back(last_spsext_.size()); | |
| 148 } | |
| 149 nalu_data_ptrs.push_back(&last_pps_.front()); | |
| 150 nalu_data_sizes.push_back(last_pps_.size()); | |
| 133 | 151 |
| 134 // Construct a new format description from the parameter sets. | 152 // Construct a new format description from the parameter sets. |
| 135 // TODO(sandersd): Replace this with custom code to support OS X < 10.9. | 153 // TODO(sandersd): Replace this with custom code to support OS X < 10.9. |
| 136 format_.reset(); | 154 format_.reset(); |
| 137 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( | 155 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( |
| 138 kCFAllocatorDefault, | 156 kCFAllocatorDefault, |
| 139 nalu_data_ptrs.size(), // parameter_set_count | 157 nalu_data_ptrs.size(), // parameter_set_count |
| 140 &nalu_data_ptrs.front(), // ¶meter_set_pointers | 158 &nalu_data_ptrs.front(), // ¶meter_set_pointers |
| 141 &nalu_data_sizes.front(), // ¶meter_set_sizes | 159 &nalu_data_sizes.front(), // ¶meter_set_sizes |
| 142 kNALUHeaderLength, // nal_unit_header_length | 160 kNALUHeaderLength, // nal_unit_header_length |
| 143 format_.InitializeInto()); | 161 format_.InitializeInto()); |
| 144 if (status) { | 162 if (status) { |
| 145 NOTIFY_STATUS("CMVideoFormatDescriptionCreateFromH264ParameterSets()", | 163 NOTIFY_STATUS("CMVideoFormatDescriptionCreateFromH264ParameterSets()", |
| 146 status); | 164 status); |
| 147 return false; | 165 return false; |
| 148 } | 166 } |
| 149 | 167 |
| 150 // If the session is compatible, there's nothing to do. | 168 // Store the new configuration data. |
| 169 CMVideoDimensions coded_dimensions = | |
| 170 CMVideoFormatDescriptionGetDimensions(format_); | |
| 171 coded_size_.SetSize(coded_dimensions.width, coded_dimensions.height); | |
| 172 | |
| 173 // If the session is compatible, there's nothing else to do. | |
| 151 if (session_ && | 174 if (session_ && |
| 152 VTDecompressionSessionCanAcceptFormatDescription(session_, format_)) { | 175 VTDecompressionSessionCanAcceptFormatDescription(session_, format_)) { |
| 153 return true; | 176 return true; |
| 154 } | 177 } |
| 155 | 178 |
| 156 // Prepare VideoToolbox configuration dictionaries. | 179 // Prepare VideoToolbox configuration dictionaries. |
| 157 base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( | 180 base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( |
| 158 CFDictionaryCreateMutable( | 181 CFDictionaryCreateMutable( |
| 159 kCFAllocatorDefault, | 182 kCFAllocatorDefault, |
| 160 1, // capacity | 183 1, // capacity |
| 161 &kCFTypeDictionaryKeyCallBacks, | 184 &kCFTypeDictionaryKeyCallBacks, |
| 162 &kCFTypeDictionaryValueCallBacks)); | 185 &kCFTypeDictionaryValueCallBacks)); |
| 163 | 186 |
| 164 CFDictionarySetValue( | 187 CFDictionarySetValue( |
| 165 decoder_config, | 188 decoder_config, |
| 166 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder | 189 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder |
| 167 CFSTR("EnableHardwareAcceleratedVideoDecoder"), | 190 CFSTR("EnableHardwareAcceleratedVideoDecoder"), |
| 168 kCFBooleanTrue); | 191 kCFBooleanTrue); |
| 169 | 192 |
| 170 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( | 193 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( |
| 171 CFDictionaryCreateMutable( | 194 CFDictionaryCreateMutable( |
| 172 kCFAllocatorDefault, | 195 kCFAllocatorDefault, |
| 173 4, // capacity | 196 4, // capacity |
| 174 &kCFTypeDictionaryKeyCallBacks, | 197 &kCFTypeDictionaryKeyCallBacks, |
| 175 &kCFTypeDictionaryValueCallBacks)); | 198 &kCFTypeDictionaryValueCallBacks)); |
| 176 | 199 |
| 177 CMVideoDimensions coded_dimensions = | |
| 178 CMVideoFormatDescriptionGetDimensions(format_); | |
| 179 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) | 200 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) |
| 180 // TODO(sandersd): RGBA option for 4:4:4 video. | 201 // TODO(sandersd): RGBA option for 4:4:4 video. |
| 181 int32_t pixel_format = kCVPixelFormatType_422YpCbCr8; | 202 int32_t pixel_format = kCVPixelFormatType_422YpCbCr8; |
| 182 base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); | 203 base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); |
| 183 base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_dimensions.width)); | 204 base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_dimensions.width)); |
| 184 base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_dimensions.height)); | 205 base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_dimensions.height)); |
| 185 #undef CFINT | 206 #undef CFINT |
| 186 CFDictionarySetValue( | 207 CFDictionarySetValue( |
| 187 image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); | 208 image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); |
| 188 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); | 209 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 200 &callback_, // output_callback | 221 &callback_, // output_callback |
| 201 session_.InitializeInto()); | 222 session_.InitializeInto()); |
| 202 if (status) { | 223 if (status) { |
| 203 NOTIFY_STATUS("VTDecompressionSessionCreate()", status); | 224 NOTIFY_STATUS("VTDecompressionSessionCreate()", status); |
| 204 return false; | 225 return false; |
| 205 } | 226 } |
| 206 | 227 |
| 207 return true; | 228 return true; |
| 208 } | 229 } |
| 209 | 230 |
| 210 void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { | |
| 211 DCHECK(CalledOnValidThread()); | |
| 212 // Not actually a requirement of the VDA API, but we're lazy and use negative | |
| 213 // values as flags internally. Revisit that if this actually happens. | |
| 214 if (bitstream.id() < 0) { | |
| 215 LOG(ERROR) << "Negative bitstream ID"; | |
| 216 NotifyError(INVALID_ARGUMENT); | |
| 217 client_->NotifyEndOfBitstreamBuffer(bitstream.id()); | |
| 218 return; | |
| 219 } | |
| 220 pending_bitstream_ids_.push(bitstream.id()); | |
| 221 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( | |
| 222 &VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), | |
| 223 bitstream)); | |
| 224 } | |
| 225 | |
| 226 void VTVideoDecodeAccelerator::DecodeTask( | 231 void VTVideoDecodeAccelerator::DecodeTask( |
| 227 const media::BitstreamBuffer& bitstream) { | 232 const media::BitstreamBuffer& bitstream, |
| 233 Frame* frame) { | |
| 228 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 234 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 229 | 235 |
| 230 // Once we have a bitstream buffer, we must either decode it or drop it. | |
| 231 // This construct ensures that the buffer is always dropped unless we call | |
| 232 // drop_bitstream.Release(). | |
| 233 base::ScopedClosureRunner drop_bitstream(base::Bind( | |
| 234 &VTVideoDecodeAccelerator::DropBitstream, base::Unretained(this), | |
| 235 bitstream.id())); | |
| 236 | |
| 237 // Map the bitstream buffer. | 236 // Map the bitstream buffer. |
| 238 base::SharedMemory memory(bitstream.handle(), true); | 237 base::SharedMemory memory(bitstream.handle(), true); |
| 239 size_t size = bitstream.size(); | 238 size_t size = bitstream.size(); |
| 240 if (!memory.Map(size)) { | 239 if (!memory.Map(size)) { |
| 241 LOG(ERROR) << "Failed to map bitstream buffer"; | 240 DLOG(ERROR) << "Failed to map bitstream buffer"; |
| 242 NotifyError(PLATFORM_FAILURE); | 241 NotifyError(PLATFORM_FAILURE); |
| 243 return; | 242 return; |
| 244 } | 243 } |
| 245 const uint8_t* buf = static_cast<uint8_t*>(memory.memory()); | 244 const uint8_t* buf = static_cast<uint8_t*>(memory.memory()); |
| 246 | 245 |
| 247 // NALUs are stored with Annex B format in the bitstream buffer (start codes), | 246 // NALUs are stored with Annex B format in the bitstream buffer (start codes), |
| 248 // but VideoToolbox expects AVCC/MPEG-4 format (length headers), so we must | 247 // but VideoToolbox expects AVC format (length headers), so we must rewrite |
| 249 // rewrite the data. | 248 // the data. |
| 250 // | 249 // |
| 251 // 1. Locate relevant NALUs and compute the size of the translated data. | 250 // Locate relevant NALUs and compute the size of the rewritten data. Also |
| 252 // Also record any parameter sets for VideoToolbox initialization. | 251 // record any parameter sets for VideoToolbox initialization. |
| 253 bool config_changed = false; | 252 bool config_changed = false; |
| 254 size_t data_size = 0; | 253 size_t data_size = 0; |
| 255 std::vector<media::H264NALU> nalus; | 254 std::vector<media::H264NALU> nalus; |
| 256 parser_.SetStream(buf, size); | 255 parser_.SetStream(buf, size); |
| 257 media::H264NALU nalu; | 256 media::H264NALU nalu; |
| 258 while (true) { | 257 while (true) { |
| 259 media::H264Parser::Result result = parser_.AdvanceToNextNALU(&nalu); | 258 media::H264Parser::Result result = parser_.AdvanceToNextNALU(&nalu); |
| 260 if (result == media::H264Parser::kEOStream) | 259 if (result == media::H264Parser::kEOStream) |
| 261 break; | 260 break; |
| 262 if (result != media::H264Parser::kOk) { | 261 if (result != media::H264Parser::kOk) { |
| 263 LOG(ERROR) << "Failed to find H.264 NALU"; | 262 DLOG(ERROR) << "Failed to find H.264 NALU"; |
| 264 NotifyError(PLATFORM_FAILURE); | 263 NotifyError(PLATFORM_FAILURE); |
| 265 return; | 264 return; |
| 266 } | 265 } |
| 267 // TODO(sandersd): Strict ordering rules. | |
| 268 switch (nalu.nal_unit_type) { | 266 switch (nalu.nal_unit_type) { |
| 269 case media::H264NALU::kSPS: | 267 case media::H264NALU::kSPS: |
| 270 last_sps_.assign(nalu.data, nalu.data + nalu.size); | 268 last_sps_.assign(nalu.data, nalu.data + nalu.size); |
| 271 last_spsext_.clear(); | 269 last_spsext_.clear(); |
| 272 config_changed = true; | 270 config_changed = true; |
| 273 break; | 271 break; |
| 274 case media::H264NALU::kSPSExt: | 272 case media::H264NALU::kSPSExt: |
| 275 // TODO(sandersd): Check that the previous NALU was an SPS. | 273 // TODO(sandersd): Check that the previous NALU was an SPS. |
| 276 last_spsext_.assign(nalu.data, nalu.data + nalu.size); | 274 last_spsext_.assign(nalu.data, nalu.data + nalu.size); |
| 277 config_changed = true; | 275 config_changed = true; |
| 278 break; | 276 break; |
| 279 case media::H264NALU::kPPS: | 277 case media::H264NALU::kPPS: |
| 280 last_pps_.assign(nalu.data, nalu.data + nalu.size); | 278 last_pps_.assign(nalu.data, nalu.data + nalu.size); |
| 281 config_changed = true; | 279 config_changed = true; |
| 282 break; | 280 break; |
| 281 case media::H264NALU::kSliceDataA: | |
| 282 case media::H264NALU::kSliceDataB: | |
| 283 case media::H264NALU::kSliceDataC: | |
| 284 DLOG(ERROR) << "Coded slide data partitions not implemented."; | |
| 285 NotifyError(PLATFORM_FAILURE); | |
| 286 return; | |
| 287 case media::H264NALU::kIDRSlice: | |
| 288 case media::H264NALU::kNonIDRSlice: | |
| 289 // TODO(sandersd): Compute pic_order_count. | |
| 283 default: | 290 default: |
| 284 nalus.push_back(nalu); | 291 nalus.push_back(nalu); |
| 285 data_size += kNALUHeaderLength + nalu.size; | 292 data_size += kNALUHeaderLength + nalu.size; |
| 286 break; | 293 break; |
| 287 } | 294 } |
| 288 } | 295 } |
| 289 | 296 |
| 290 // 2. Initialize VideoToolbox. | 297 // Initialize VideoToolbox. |
| 291 // TODO(sandersd): Check if the new configuration is identical before | 298 // TODO(sandersd): Instead of assuming that the last SPS and PPS units are |
| 292 // reconfiguring. | 299 // always the correct ones, maintain a cache of recent SPS and PPS units and |
| 300 // select from them using the slice header. | |
| 293 if (config_changed) { | 301 if (config_changed) { |
| 294 if (last_sps_.size() == 0 || last_pps_.size() == 0) { | 302 if (last_sps_.size() == 0 || last_pps_.size() == 0) { |
| 295 LOG(ERROR) << "Invalid configuration data"; | 303 DLOG(ERROR) << "Invalid configuration data"; |
| 296 NotifyError(INVALID_ARGUMENT); | 304 NotifyError(INVALID_ARGUMENT); |
| 297 return; | 305 return; |
| 298 } | 306 } |
| 299 // TODO(sandersd): Check that the SPS and PPS IDs match. | 307 if (!ConfigureDecoder()) |
| 300 std::vector<const uint8_t*> nalu_data_ptrs; | |
| 301 std::vector<size_t> nalu_data_sizes; | |
| 302 nalu_data_ptrs.push_back(&last_sps_.front()); | |
| 303 nalu_data_sizes.push_back(last_sps_.size()); | |
| 304 if (last_spsext_.size() != 0) { | |
| 305 nalu_data_ptrs.push_back(&last_spsext_.front()); | |
| 306 nalu_data_sizes.push_back(last_spsext_.size()); | |
| 307 } | |
| 308 nalu_data_ptrs.push_back(&last_pps_.front()); | |
| 309 nalu_data_sizes.push_back(last_pps_.size()); | |
| 310 | |
| 311 // If ConfigureDecoder() fails, it already called NotifyError(). | |
| 312 if (!ConfigureDecoder(nalu_data_ptrs, nalu_data_sizes)) | |
| 313 return; | 308 return; |
| 314 } | 309 } |
| 315 | 310 |
| 316 // If there are no non-configuration units, immediately return an empty | 311 // If there are no non-configuration units, drop the bitstream buffer by |
| 317 // (ie. dropped) frame. It is an error to create a MemoryBlock with zero | 312 // returning an empty frame. |
| 318 // size. | 313 if (!data_size) { |
| 319 if (!data_size) | 314 if (!FinishDelayedFrames()) |
| 315 return; | |
| 316 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 317 &VTVideoDecodeAccelerator::DecodeDone, | |
| 318 weak_this_factory_.GetWeakPtr(), frame)); | |
|
DaleCurtis
2014/11/11 21:02:34
I don't think it's legal to create this WeakPtr he
sandersd (OOO until July 31)
2014/11/11 22:14:39
As far as I can tell, there is no meaningful diffe
DaleCurtis
2014/11/11 23:28:39
There's a difference if you're calling InvalidateW
| |
| 320 return; | 319 return; |
| 320 } | |
| 321 | 321 |
| 322 // If the session is not configured, fail. | 322 // If the session is not configured by this point, fail. |
| 323 if (!session_) { | 323 if (!session_) { |
| 324 LOG(ERROR) << "Image slice without configuration data"; | 324 DLOG(ERROR) << "Image slice without configuration"; |
| 325 NotifyError(INVALID_ARGUMENT); | 325 NotifyError(INVALID_ARGUMENT); |
| 326 return; | 326 return; |
| 327 } | 327 } |
| 328 | 328 |
| 329 // 3. Allocate a memory-backed CMBlockBuffer for the translated data. | 329 // Create a memory-backed CMBlockBuffer for the translated data. |
| 330 // TODO(sandersd): Check that the slice's PPS matches the current PPS. | 330 // TODO(sandersd): Pool of memory blocks. |
| 331 base::ScopedCFTypeRef<CMBlockBufferRef> data; | 331 base::ScopedCFTypeRef<CMBlockBufferRef> data; |
| 332 OSStatus status = CMBlockBufferCreateWithMemoryBlock( | 332 OSStatus status = CMBlockBufferCreateWithMemoryBlock( |
| 333 kCFAllocatorDefault, | 333 kCFAllocatorDefault, |
| 334 NULL, // &memory_block | 334 NULL, // &memory_block |
| 335 data_size, // block_length | 335 data_size, // block_length |
| 336 kCFAllocatorDefault, // block_allocator | 336 kCFAllocatorDefault, // block_allocator |
| 337 NULL, // &custom_block_source | 337 NULL, // &custom_block_source |
| 338 0, // offset_to_data | 338 0, // offset_to_data |
| 339 data_size, // data_length | 339 data_size, // data_length |
| 340 0, // flags | 340 0, // flags |
| 341 data.InitializeInto()); | 341 data.InitializeInto()); |
| 342 if (status) { | 342 if (status) { |
| 343 NOTIFY_STATUS("CMBlockBufferCreateWithMemoryBlock()", status); | 343 NOTIFY_STATUS("CMBlockBufferCreateWithMemoryBlock()", status); |
| 344 return; | 344 return; |
| 345 } | 345 } |
| 346 | 346 |
| 347 // 4. Copy NALU data, inserting length headers. | 347 // Copy NALU data into the CMBlockBuffer, inserting length headers. |
| 348 size_t offset = 0; | 348 size_t offset = 0; |
| 349 for (size_t i = 0; i < nalus.size(); i++) { | 349 for (size_t i = 0; i < nalus.size(); i++) { |
| 350 media::H264NALU& nalu = nalus[i]; | 350 media::H264NALU& nalu = nalus[i]; |
| 351 uint32_t header = base::HostToNet32(static_cast<uint32_t>(nalu.size)); | 351 uint32_t header = base::HostToNet32(static_cast<uint32_t>(nalu.size)); |
| 352 status = CMBlockBufferReplaceDataBytes( | 352 status = CMBlockBufferReplaceDataBytes( |
| 353 &header, data, offset, kNALUHeaderLength); | 353 &header, data, offset, kNALUHeaderLength); |
| 354 if (status) { | 354 if (status) { |
| 355 NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); | 355 NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); |
| 356 return; | 356 return; |
| 357 } | 357 } |
| 358 offset += kNALUHeaderLength; | 358 offset += kNALUHeaderLength; |
| 359 status = CMBlockBufferReplaceDataBytes(nalu.data, data, offset, nalu.size); | 359 status = CMBlockBufferReplaceDataBytes(nalu.data, data, offset, nalu.size); |
| 360 if (status) { | 360 if (status) { |
| 361 NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); | 361 NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); |
| 362 return; | 362 return; |
| 363 } | 363 } |
| 364 offset += nalu.size; | 364 offset += nalu.size; |
| 365 } | 365 } |
| 366 | 366 |
| 367 // 5. Package the data for VideoToolbox and request decoding. | 367 // Package the data in a CMSampleBuffer. |
| 368 base::ScopedCFTypeRef<CMSampleBufferRef> frame; | 368 base::ScopedCFTypeRef<CMSampleBufferRef> sample; |
| 369 status = CMSampleBufferCreate( | 369 status = CMSampleBufferCreate( |
| 370 kCFAllocatorDefault, | 370 kCFAllocatorDefault, |
| 371 data, // data_buffer | 371 data, // data_buffer |
| 372 true, // data_ready | 372 true, // data_ready |
| 373 NULL, // make_data_ready_callback | 373 NULL, // make_data_ready_callback |
| 374 NULL, // make_data_ready_refcon | 374 NULL, // make_data_ready_refcon |
| 375 format_, // format_description | 375 format_, // format_description |
| 376 1, // num_samples | 376 1, // num_samples |
| 377 0, // num_sample_timing_entries | 377 0, // num_sample_timing_entries |
| 378 NULL, // &sample_timing_array | 378 NULL, // &sample_timing_array |
| 379 0, // num_sample_size_entries | 379 0, // num_sample_size_entries |
| 380 NULL, // &sample_size_array | 380 NULL, // &sample_size_array |
| 381 frame.InitializeInto()); | 381 sample.InitializeInto()); |
| 382 if (status) { | 382 if (status) { |
| 383 NOTIFY_STATUS("CMSampleBufferCreate()", status); | 383 NOTIFY_STATUS("CMSampleBufferCreate()", status); |
| 384 return; | 384 return; |
| 385 } | 385 } |
| 386 | 386 |
| 387 // Update the frame data. | |
| 388 frame->coded_size = coded_size_; | |
| 389 | |
| 390 // Send the frame for decoding. | |
| 387 // Asynchronous Decompression allows for parallel submission of frames | 391 // Asynchronous Decompression allows for parallel submission of frames |
| 388 // (without it, DecodeFrame() does not return until the frame has been | 392 // (without it, DecodeFrame() does not return until the frame has been |
| 389 // decoded). We don't enable Temporal Processing so that frames are always | 393 // decoded). We don't enable Temporal Processing so that frames are always |
| 390 // returned in decode order; this makes it easier to avoid deadlock. | 394 // returned in decode order; this makes it easier to avoid deadlock. |
| 391 VTDecodeFrameFlags decode_flags = | 395 VTDecodeFrameFlags decode_flags = |
| 392 kVTDecodeFrame_EnableAsynchronousDecompression; | 396 kVTDecodeFrame_EnableAsynchronousDecompression; |
| 393 | |
| 394 intptr_t bitstream_id = bitstream.id(); | |
| 395 status = VTDecompressionSessionDecodeFrame( | 397 status = VTDecompressionSessionDecodeFrame( |
| 396 session_, | 398 session_, |
| 397 frame, // sample_buffer | 399 sample, // sample_buffer |
| 398 decode_flags, // decode_flags | 400 decode_flags, // decode_flags |
| 399 reinterpret_cast<void*>(bitstream_id), // source_frame_refcon | 401 reinterpret_cast<void*>(frame), // source_frame_refcon |
| 400 NULL); // &info_flags_out | 402 NULL); // &info_flags_out |
| 401 if (status) { | 403 if (status) { |
| 402 NOTIFY_STATUS("VTDecompressionSessionDecodeFrame()", status); | 404 NOTIFY_STATUS("VTDecompressionSessionDecodeFrame()", status); |
| 403 return; | 405 return; |
| 404 } | 406 } |
| 405 | |
| 406 // Now that the bitstream is decoding, don't drop it. | |
| 407 (void)drop_bitstream.Release(); | |
| 408 } | 407 } |
| 409 | 408 |
| 410 // This method may be called on any VideoToolbox thread. | 409 // This method may be called on any VideoToolbox thread. |
| 411 void VTVideoDecodeAccelerator::Output( | 410 void VTVideoDecodeAccelerator::Output( |
| 412 int32_t bitstream_id, | 411 void* source_frame_refcon, |
| 413 OSStatus status, | 412 OSStatus status, |
| 414 CVImageBufferRef image_buffer) { | 413 CVImageBufferRef image_buffer) { |
| 415 if (status) { | 414 if (status) { |
| 416 // TODO(sandersd): Handle dropped frames. | |
| 417 NOTIFY_STATUS("Decoding", status); | 415 NOTIFY_STATUS("Decoding", status); |
| 418 image_buffer = NULL; | |
| 419 } else if (CFGetTypeID(image_buffer) != CVPixelBufferGetTypeID()) { | 416 } else if (CFGetTypeID(image_buffer) != CVPixelBufferGetTypeID()) { |
| 420 LOG(ERROR) << "Decoded frame is not a CVPixelBuffer"; | 417 DLOG(ERROR) << "Decoded frame is not a CVPixelBuffer"; |
| 421 NotifyError(PLATFORM_FAILURE); | 418 NotifyError(PLATFORM_FAILURE); |
| 422 image_buffer = NULL; | |
| 423 } else { | 419 } else { |
| 424 CFRetain(image_buffer); | 420 Frame* frame = reinterpret_cast<Frame*>(source_frame_refcon); |
| 421 frame->image.reset(image_buffer, base::scoped_policy::RETAIN); | |
| 422 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 423 &VTVideoDecodeAccelerator::DecodeDone, | |
| 424 weak_this_factory_.GetWeakPtr(), frame)); | |
|
DaleCurtis
2014/11/11 21:02:34
Ditto.
sandersd (OOO until July 31)
2014/11/11 22:14:39
Acknowledged.
| |
| 425 } | 425 } |
| 426 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 427 &VTVideoDecodeAccelerator::OutputTask, | |
| 428 weak_this_factory_.GetWeakPtr(), | |
| 429 DecodedFrame(bitstream_id, image_buffer))); | |
| 430 } | 426 } |
| 431 | 427 |
| 432 void VTVideoDecodeAccelerator::OutputTask(DecodedFrame frame) { | 428 void VTVideoDecodeAccelerator::DecodeDone(Frame* frame) { |
| 433 DCHECK(CalledOnValidThread()); | 429 DCHECK(CalledOnValidThread()); |
| 434 decoded_frames_.push(frame); | 430 DCHECK_EQ(frame->bitstream_id, pending_frames_.front()->bitstream_id); |
| 435 ProcessDecodedFrames(); | 431 Task task(TASK_FRAME); |
| 432 task.frame = pending_frames_.front(); | |
| 433 pending_frames_.pop(); | |
| 434 pending_tasks_.push(task); | |
| 435 ProcessTasks(); | |
| 436 } | |
| 437 | |
| 438 void VTVideoDecodeAccelerator::FlushTask(TaskType type) { | |
| 439 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | |
| 440 FinishDelayedFrames(); | |
| 441 | |
| 442 // Always queue a task, even if FinishDelayedFrames() fails, so that | |
| 443 // destruction always completes. | |
| 444 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 445 &VTVideoDecodeAccelerator::FlushDone, | |
| 446 weak_this_factory_.GetWeakPtr(), type)); | |
| 447 } | |
| 448 | |
| 449 void VTVideoDecodeAccelerator::FlushDone(TaskType type) { | |
| 450 DCHECK(CalledOnValidThread()); | |
| 451 pending_tasks_.push(Task(type)); | |
| 452 ProcessTasks(); | |
| 453 } | |
| 454 | |
| 455 void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { | |
| 456 DCHECK(CalledOnValidThread()); | |
| 457 DCHECK_EQ(assigned_bitstream_ids_.count(bitstream.id()), 0u); | |
| 458 assigned_bitstream_ids_.insert(bitstream.id()); | |
| 459 Frame* frame = new Frame(bitstream.id()); | |
| 460 pending_frames_.push(make_linked_ptr(frame)); | |
| 461 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( | |
| 462 &VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), | |
| 463 bitstream, frame)); | |
| 436 } | 464 } |
| 437 | 465 |
| 438 void VTVideoDecodeAccelerator::AssignPictureBuffers( | 466 void VTVideoDecodeAccelerator::AssignPictureBuffers( |
| 439 const std::vector<media::PictureBuffer>& pictures) { | 467 const std::vector<media::PictureBuffer>& pictures) { |
| 440 DCHECK(CalledOnValidThread()); | 468 DCHECK(CalledOnValidThread()); |
| 441 | 469 |
| 442 for (size_t i = 0; i < pictures.size(); i++) { | 470 for (const media::PictureBuffer& picture : pictures) { |
| 443 DCHECK(!texture_ids_.count(pictures[i].id())); | 471 DCHECK(!texture_ids_.count(picture.id())); |
| 444 assigned_picture_ids_.insert(pictures[i].id()); | 472 assigned_picture_ids_.insert(picture.id()); |
| 445 available_picture_ids_.push_back(pictures[i].id()); | 473 available_picture_ids_.push_back(picture.id()); |
| 446 texture_ids_[pictures[i].id()] = pictures[i].texture_id(); | 474 texture_ids_[picture.id()] = picture.texture_id(); |
| 447 } | 475 } |
| 448 | 476 |
| 449 // Pictures are not marked as uncleared until after this method returns, and | 477 // Pictures are not marked as uncleared until after this method returns, and |
| 450 // they will be broken if they are used before that happens. So, schedule | 478 // they will be broken if they are used before that happens. So, schedule |
| 451 // future work after that happens. | 479 // future work after that happens. |
| 452 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 480 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 453 &VTVideoDecodeAccelerator::ProcessDecodedFrames, | 481 &VTVideoDecodeAccelerator::ProcessTasks, |
| 454 weak_this_factory_.GetWeakPtr())); | 482 weak_this_factory_.GetWeakPtr())); |
| 455 } | 483 } |
| 456 | 484 |
| 457 void VTVideoDecodeAccelerator::ReusePictureBuffer(int32_t picture_id) { | 485 void VTVideoDecodeAccelerator::ReusePictureBuffer(int32_t picture_id) { |
| 458 DCHECK(CalledOnValidThread()); | 486 DCHECK(CalledOnValidThread()); |
| 459 DCHECK_EQ(CFGetRetainCount(picture_bindings_[picture_id]), 1); | 487 DCHECK_EQ(CFGetRetainCount(picture_bindings_[picture_id]), 1); |
| 460 picture_bindings_.erase(picture_id); | 488 picture_bindings_.erase(picture_id); |
| 461 // Don't put the picture back in the available list if has been dismissed. | |
| 462 if (assigned_picture_ids_.count(picture_id) != 0) { | 489 if (assigned_picture_ids_.count(picture_id) != 0) { |
| 463 available_picture_ids_.push_back(picture_id); | 490 available_picture_ids_.push_back(picture_id); |
| 464 ProcessDecodedFrames(); | 491 ProcessTasks(); |
| 492 } else { | |
| 493 client_->DismissPictureBuffer(picture_id); | |
| 465 } | 494 } |
| 466 } | 495 } |
| 467 | 496 |
| 468 void VTVideoDecodeAccelerator::CompleteAction(Action action) { | 497 void VTVideoDecodeAccelerator::ProcessTasks() { |
| 469 DCHECK(CalledOnValidThread()); | 498 DCHECK(CalledOnValidThread()); |
| 470 | 499 |
| 471 switch (action) { | 500 while (!pending_tasks_.empty()) { |
| 472 case ACTION_FLUSH: | 501 const Task& task = pending_tasks_.front(); |
| 473 client_->NotifyFlushDone(); | 502 |
| 474 break; | 503 switch (state_) { |
| 475 case ACTION_RESET: | 504 case STATE_NORMAL: |
| 476 client_->NotifyResetDone(); | 505 if (!ProcessTask(task)) |
| 477 break; | 506 return; |
| 478 case ACTION_DESTROY: | 507 pending_tasks_.pop(); |
| 479 delete this; | 508 break; |
| 480 break; | 509 |
| 510 case STATE_ERROR: | |
| 511 // Do nothing until Destroy() is called. | |
| 512 break; | |
| 513 | |
| 514 case STATE_DESTROYING: | |
| 515 // Discard tasks until destruction is complete. | |
| 516 if (task.type == TASK_DESTROY) { | |
| 517 delete this; | |
| 518 return; | |
| 519 } | |
| 520 pending_tasks_.pop(); | |
| 521 break; | |
| 522 } | |
| 481 } | 523 } |
| 482 } | 524 } |
| 483 | 525 |
| 484 void VTVideoDecodeAccelerator::CompleteActions(int32_t bitstream_id) { | 526 bool VTVideoDecodeAccelerator::ProcessTask(const Task& task) { |
| 485 DCHECK(CalledOnValidThread()); | 527 DCHECK(CalledOnValidThread()); |
| 486 while (!pending_actions_.empty() && | 528 DCHECK_EQ(state_, STATE_NORMAL); |
| 487 pending_actions_.front().bitstream_id == bitstream_id) { | 529 |
| 488 CompleteAction(pending_actions_.front().action); | 530 switch (task.type) { |
| 489 pending_actions_.pop(); | 531 case TASK_FRAME: |
| 532 return ProcessFrame(*task.frame); | |
| 533 | |
| 534 case TASK_FLUSH: | |
| 535 DCHECK_EQ(task.type, pending_flush_tasks_.front()); | |
| 536 pending_flush_tasks_.pop(); | |
| 537 client_->NotifyFlushDone(); | |
| 538 return true; | |
| 539 | |
| 540 case TASK_RESET: | |
| 541 DCHECK_EQ(task.type, pending_flush_tasks_.front()); | |
| 542 pending_flush_tasks_.pop(); | |
| 543 client_->NotifyResetDone(); | |
| 544 return true; | |
| 545 | |
| 546 case TASK_DESTROY: | |
| 547 NOTREACHED() << "Can't destroy while in STATE_NORMAL."; | |
| 548 NotifyError(ILLEGAL_STATE); | |
| 549 return false; | |
| 490 } | 550 } |
| 491 } | 551 } |
| 492 | 552 |
| 493 void VTVideoDecodeAccelerator::ProcessDecodedFrames() { | 553 bool VTVideoDecodeAccelerator::ProcessFrame(const Frame& frame) { |
| 494 DCHECK(CalledOnValidThread()); | 554 DCHECK(CalledOnValidThread()); |
| 555 DCHECK_EQ(state_, STATE_NORMAL); | |
| 556 // If the next pending flush is for a reset, then the frame will be dropped. | |
| 557 bool resetting = !pending_flush_tasks_.empty() && | |
| 558 pending_flush_tasks_.front() == TASK_RESET; | |
| 559 if (!resetting && frame.image.get()) { | |
| 560 // If the |coded_size| has changed, request new picture buffers and then | |
| 561 // wait for them. | |
| 562 // TODO(sandersd): If GpuVideoDecoder didn't specifically check the size of | |
| 563 // textures, this would be unnecessary, as the size is actually a property | |
| 564 // of the texture binding, not the texture. We rebind every frame, so the | |
| 565 // size passed to ProvidePictureBuffers() is meaningless. | |
| 566 if (picture_size_ != frame.coded_size) { | |
| 567 // Dismiss current pictures. | |
| 568 for (int32_t picture_id : assigned_picture_ids_) | |
| 569 client_->DismissPictureBuffer(picture_id); | |
| 570 assigned_picture_ids_.clear(); | |
| 571 available_picture_ids_.clear(); | |
| 495 | 572 |
| 496 while (!decoded_frames_.empty()) { | 573 // Request new pictures. |
| 497 if (pending_actions_.empty()) { | 574 picture_size_ = frame.coded_size; |
| 498 // No pending actions; send frames normally. | 575 client_->ProvidePictureBuffers( |
| 499 if (!has_error_) | 576 kNumPictureBuffers, coded_size_, GL_TEXTURE_RECTANGLE_ARB); |
| 500 SendPictures(pending_bitstream_ids_.back()); | 577 return false; |
| 501 return; | |
| 502 } | 578 } |
| 503 | 579 if (!SendFrame(frame)) |
| 504 int32_t next_action_bitstream_id = pending_actions_.front().bitstream_id; | 580 return false; |
| 505 int32_t last_sent_bitstream_id = -1; | |
| 506 switch (pending_actions_.front().action) { | |
| 507 case ACTION_FLUSH: | |
| 508 // Send frames normally. | |
| 509 if (has_error_) | |
| 510 return; | |
| 511 last_sent_bitstream_id = SendPictures(next_action_bitstream_id); | |
| 512 break; | |
| 513 | |
| 514 case ACTION_RESET: | |
| 515 // Drop decoded frames. | |
| 516 if (has_error_) | |
| 517 return; | |
| 518 while (!decoded_frames_.empty() && | |
| 519 last_sent_bitstream_id != next_action_bitstream_id) { | |
| 520 last_sent_bitstream_id = decoded_frames_.front().bitstream_id; | |
| 521 decoded_frames_.pop(); | |
| 522 DCHECK_EQ(pending_bitstream_ids_.front(), last_sent_bitstream_id); | |
| 523 pending_bitstream_ids_.pop(); | |
| 524 client_->NotifyEndOfBitstreamBuffer(last_sent_bitstream_id); | |
| 525 } | |
| 526 break; | |
| 527 | |
| 528 case ACTION_DESTROY: | |
| 529 // Drop decoded frames, without bookkeeping. | |
| 530 while (!decoded_frames_.empty()) { | |
| 531 last_sent_bitstream_id = decoded_frames_.front().bitstream_id; | |
| 532 decoded_frames_.pop(); | |
| 533 } | |
| 534 | |
| 535 // Handle completing the action specially, as it is important not to | |
| 536 // access |this| after calling CompleteAction(). | |
| 537 if (last_sent_bitstream_id == next_action_bitstream_id) | |
| 538 CompleteAction(ACTION_DESTROY); | |
| 539 | |
| 540 // Either |this| was deleted or no more progress can be made. | |
| 541 return; | |
| 542 } | |
| 543 | |
| 544 // If we ran out of buffers (or pictures), no more progress can be made | |
| 545 // until more frames are decoded. | |
| 546 if (last_sent_bitstream_id != next_action_bitstream_id) | |
| 547 return; | |
| 548 | |
| 549 // Complete all actions pending for this |bitstream_id|, then loop to see | |
| 550 // if progress can be made on the next action. | |
| 551 CompleteActions(next_action_bitstream_id); | |
| 552 } | 581 } |
| 582 assigned_bitstream_ids_.erase(frame.bitstream_id); | |
| 583 client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); | |
| 584 return true; | |
| 553 } | 585 } |
| 554 | 586 |
| 555 int32_t VTVideoDecodeAccelerator::ProcessDroppedFrames( | 587 bool VTVideoDecodeAccelerator::SendFrame(const Frame& frame) { |
| 556 int32_t last_sent_bitstream_id, | |
| 557 int32_t up_to_bitstream_id) { | |
| 558 DCHECK(CalledOnValidThread()); | 588 DCHECK(CalledOnValidThread()); |
| 559 // Drop frames as long as there is a frame, we have not reached the next | 589 DCHECK_EQ(state_, STATE_NORMAL); |
| 560 // action, and the next frame has no image. | |
| 561 while (!decoded_frames_.empty() && | |
| 562 last_sent_bitstream_id != up_to_bitstream_id && | |
| 563 decoded_frames_.front().image_buffer.get() == NULL) { | |
| 564 const DecodedFrame& frame = decoded_frames_.front(); | |
| 565 DCHECK_EQ(pending_bitstream_ids_.front(), frame.bitstream_id); | |
| 566 client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); | |
| 567 last_sent_bitstream_id = frame.bitstream_id; | |
| 568 decoded_frames_.pop(); | |
| 569 pending_bitstream_ids_.pop(); | |
| 570 } | |
| 571 return last_sent_bitstream_id; | |
| 572 } | |
| 573 | 590 |
| 574 // TODO(sandersd): If GpuVideoDecoder didn't specifically check the size of | 591 if (available_picture_ids_.empty()) |
| 575 // textures, this would be unnecessary, as the size is actually a property of | 592 return false; |
| 576 // the texture binding, not the texture. We rebind every frame, so the size | |
| 577 // passed to ProvidePictureBuffers() is meaningless. | |
| 578 void VTVideoDecodeAccelerator::ProcessSizeChangeIfNeeded() { | |
| 579 DCHECK(CalledOnValidThread()); | |
| 580 DCHECK(!decoded_frames_.empty()); | |
| 581 | 593 |
| 582 // Find the size of the next image. | 594 int32_t picture_id = available_picture_ids_.back(); |
| 583 const DecodedFrame& frame = decoded_frames_.front(); | 595 IOSurfaceRef surface = CVPixelBufferGetIOSurface(frame.image.get()); |
| 584 CVImageBufferRef image_buffer = frame.image_buffer.get(); | |
| 585 size_t width = CVPixelBufferGetWidth(image_buffer); | |
| 586 size_t height = CVPixelBufferGetHeight(image_buffer); | |
| 587 gfx::Size image_size(width, height); | |
| 588 | |
| 589 if (picture_size_ != image_size) { | |
| 590 // Dismiss all assigned picture buffers. | |
| 591 for (int32_t picture_id : assigned_picture_ids_) | |
| 592 client_->DismissPictureBuffer(picture_id); | |
| 593 assigned_picture_ids_.clear(); | |
| 594 available_picture_ids_.clear(); | |
| 595 | |
| 596 // Request new pictures. | |
| 597 client_->ProvidePictureBuffers( | |
| 598 kNumPictureBuffers, image_size, GL_TEXTURE_RECTANGLE_ARB); | |
| 599 picture_size_ = image_size; | |
| 600 } | |
| 601 } | |
| 602 | |
| 603 int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { | |
| 604 DCHECK(CalledOnValidThread()); | |
| 605 DCHECK(!decoded_frames_.empty()); | |
| 606 | |
| 607 // TODO(sandersd): Store the actual last sent bitstream ID? | |
| 608 int32_t last_sent_bitstream_id = -1; | |
| 609 | |
| 610 last_sent_bitstream_id = | |
| 611 ProcessDroppedFrames(last_sent_bitstream_id, up_to_bitstream_id); | |
| 612 if (last_sent_bitstream_id == up_to_bitstream_id || decoded_frames_.empty()) | |
| 613 return last_sent_bitstream_id; | |
| 614 | |
| 615 ProcessSizeChangeIfNeeded(); | |
| 616 if (available_picture_ids_.empty()) | |
| 617 return last_sent_bitstream_id; | |
| 618 | 596 |
| 619 if (!make_context_current_.Run()) { | 597 if (!make_context_current_.Run()) { |
| 620 LOG(ERROR) << "Failed to make GL context current"; | 598 DLOG(ERROR) << "Failed to make GL context current"; |
| 621 NotifyError(PLATFORM_FAILURE); | 599 NotifyError(PLATFORM_FAILURE); |
| 622 return last_sent_bitstream_id; | 600 return false; |
| 623 } | 601 } |
| 624 | 602 |
| 625 glEnable(GL_TEXTURE_RECTANGLE_ARB); | 603 glEnable(GL_TEXTURE_RECTANGLE_ARB); |
| 626 while (!available_picture_ids_.empty() && !has_error_) { | 604 gfx::ScopedTextureBinder |
| 627 DCHECK_NE(last_sent_bitstream_id, up_to_bitstream_id); | 605 texture_binder(GL_TEXTURE_RECTANGLE_ARB, texture_ids_[picture_id]); |
| 628 DCHECK(!decoded_frames_.empty()); | 606 CGLError status = CGLTexImageIOSurface2D( |
| 629 | 607 cgl_context_, // ctx |
| 630 // We don't pop |frame| or |picture_id| until they are consumed, which may | 608 GL_TEXTURE_RECTANGLE_ARB, // target |
| 631 // not happen if an error occurs. Conveniently, this also removes some | 609 GL_RGB, // internal_format |
| 632 // refcounting. | 610 frame.coded_size.width(), // width |
| 633 const DecodedFrame& frame = decoded_frames_.front(); | 611 frame.coded_size.height(), // height |
| 634 DCHECK_EQ(pending_bitstream_ids_.front(), frame.bitstream_id); | 612 GL_YCBCR_422_APPLE, // format |
| 635 int32_t picture_id = available_picture_ids_.back(); | 613 GL_UNSIGNED_SHORT_8_8_APPLE, // type |
| 636 | 614 surface, // io_surface |
| 637 CVImageBufferRef image_buffer = frame.image_buffer.get(); | 615 0); // plane |
| 638 IOSurfaceRef surface = CVPixelBufferGetIOSurface(image_buffer); | 616 if (status != kCGLNoError) { |
| 639 | 617 NOTIFY_STATUS("CGLTexImageIOSurface2D()", status); |
| 640 gfx::ScopedTextureBinder | 618 return false; |
| 641 texture_binder(GL_TEXTURE_RECTANGLE_ARB, texture_ids_[picture_id]); | |
| 642 CGLError status = CGLTexImageIOSurface2D( | |
| 643 cgl_context_, // ctx | |
| 644 GL_TEXTURE_RECTANGLE_ARB, // target | |
| 645 GL_RGB, // internal_format | |
| 646 picture_size_.width(), // width | |
| 647 picture_size_.height(), // height | |
| 648 GL_YCBCR_422_APPLE, // format | |
| 649 GL_UNSIGNED_SHORT_8_8_APPLE, // type | |
| 650 surface, // io_surface | |
| 651 0); // plane | |
| 652 if (status != kCGLNoError) { | |
| 653 NOTIFY_STATUS("CGLTexImageIOSurface2D()", status); | |
| 654 break; | |
| 655 } | |
| 656 | |
| 657 picture_bindings_[picture_id] = frame.image_buffer; | |
| 658 client_->PictureReady(media::Picture( | |
| 659 picture_id, frame.bitstream_id, gfx::Rect(picture_size_))); | |
| 660 available_picture_ids_.pop_back(); | |
| 661 client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); | |
| 662 last_sent_bitstream_id = frame.bitstream_id; | |
| 663 decoded_frames_.pop(); | |
| 664 pending_bitstream_ids_.pop(); | |
| 665 | |
| 666 last_sent_bitstream_id = | |
| 667 ProcessDroppedFrames(last_sent_bitstream_id, up_to_bitstream_id); | |
| 668 if (last_sent_bitstream_id == up_to_bitstream_id || decoded_frames_.empty()) | |
| 669 break; | |
| 670 | |
| 671 ProcessSizeChangeIfNeeded(); | |
| 672 } | 619 } |
| 673 glDisable(GL_TEXTURE_RECTANGLE_ARB); | 620 glDisable(GL_TEXTURE_RECTANGLE_ARB); |
| 674 | 621 |
| 675 return last_sent_bitstream_id; | 622 available_picture_ids_.pop_back(); |
| 676 } | 623 picture_bindings_[picture_id] = frame.image; |
| 677 | 624 client_->PictureReady(media::Picture( |
| 678 void VTVideoDecodeAccelerator::FlushTask() { | 625 picture_id, frame.bitstream_id, gfx::Rect(frame.coded_size))); |
| 679 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 626 return true; |
| 680 OSStatus status = VTDecompressionSessionFinishDelayedFrames(session_); | |
| 681 if (status) | |
| 682 NOTIFY_STATUS("VTDecompressionSessionFinishDelayedFrames()", status); | |
| 683 } | |
| 684 | |
| 685 void VTVideoDecodeAccelerator::QueueAction(Action action) { | |
| 686 DCHECK(CalledOnValidThread()); | |
| 687 if (pending_bitstream_ids_.empty()) { | |
| 688 // If there are no pending frames, all actions complete immediately. | |
| 689 CompleteAction(action); | |
| 690 } else { | |
| 691 // Otherwise, queue the action. | |
| 692 pending_actions_.push(PendingAction(action, pending_bitstream_ids_.back())); | |
| 693 | |
| 694 // Request a flush to make sure the action will eventually complete. | |
| 695 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( | |
| 696 &VTVideoDecodeAccelerator::FlushTask, base::Unretained(this))); | |
| 697 | |
| 698 // See if we can make progress now that there is a new pending action. | |
| 699 ProcessDecodedFrames(); | |
| 700 } | |
| 701 } | 627 } |
| 702 | 628 |
| 703 void VTVideoDecodeAccelerator::NotifyError(Error error) { | 629 void VTVideoDecodeAccelerator::NotifyError(Error error) { |
| 704 if (!CalledOnValidThread()) { | 630 if (!CalledOnValidThread()) { |
| 705 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 631 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 706 &VTVideoDecodeAccelerator::NotifyError, | 632 &VTVideoDecodeAccelerator::NotifyError, |
| 707 weak_this_factory_.GetWeakPtr(), | 633 weak_this_factory_.GetWeakPtr(), error)); |
| 708 error)); | 634 } else if (state_ == STATE_NORMAL) { |
| 709 return; | 635 state_ = STATE_ERROR; |
| 636 client_->NotifyError(error); | |
| 710 } | 637 } |
| 711 has_error_ = true; | |
| 712 client_->NotifyError(error); | |
| 713 } | 638 } |
| 714 | 639 |
| 715 void VTVideoDecodeAccelerator::DropBitstream(int32_t bitstream_id) { | 640 void VTVideoDecodeAccelerator::QueueFlush(TaskType type) { |
| 716 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 641 DCHECK(CalledOnValidThread()); |
| 717 gpu_task_runner_->PostTask(FROM_HERE, base::Bind( | 642 pending_flush_tasks_.push(type); |
| 718 &VTVideoDecodeAccelerator::OutputTask, | 643 decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
| 719 weak_this_factory_.GetWeakPtr(), | 644 &VTVideoDecodeAccelerator::FlushTask, base::Unretained(this), |
| 720 DecodedFrame(bitstream_id, NULL))); | 645 type)); |
| 646 | |
| 647 // If this is a new flush request, see if we can make progress. | |
| 648 if (pending_flush_tasks_.size() == 1) | |
| 649 ProcessTasks(); | |
| 721 } | 650 } |
| 722 | 651 |
| 723 void VTVideoDecodeAccelerator::Flush() { | 652 void VTVideoDecodeAccelerator::Flush() { |
| 724 DCHECK(CalledOnValidThread()); | 653 DCHECK(CalledOnValidThread()); |
| 725 QueueAction(ACTION_FLUSH); | 654 QueueFlush(TASK_FLUSH); |
| 726 } | 655 } |
| 727 | 656 |
| 728 void VTVideoDecodeAccelerator::Reset() { | 657 void VTVideoDecodeAccelerator::Reset() { |
| 729 DCHECK(CalledOnValidThread()); | 658 DCHECK(CalledOnValidThread()); |
| 730 QueueAction(ACTION_RESET); | 659 QueueFlush(TASK_RESET); |
| 731 } | 660 } |
| 732 | 661 |
| 733 void VTVideoDecodeAccelerator::Destroy() { | 662 void VTVideoDecodeAccelerator::Destroy() { |
| 734 DCHECK(CalledOnValidThread()); | 663 DCHECK(CalledOnValidThread()); |
| 735 // Drop any other pending actions. | 664 for (int32_t bitstream_id : assigned_bitstream_ids_) |
| 736 while (!pending_actions_.empty()) | 665 client_->NotifyEndOfBitstreamBuffer(bitstream_id); |
| 737 pending_actions_.pop(); | 666 assigned_bitstream_ids_.clear(); |
| 738 // Return all bitstream buffers. | 667 state_ = STATE_DESTROYING; |
| 739 while (!pending_bitstream_ids_.empty()) { | 668 QueueFlush(TASK_DESTROY); |
| 740 client_->NotifyEndOfBitstreamBuffer(pending_bitstream_ids_.front()); | |
| 741 pending_bitstream_ids_.pop(); | |
| 742 } | |
| 743 QueueAction(ACTION_DESTROY); | |
| 744 } | 669 } |
| 745 | 670 |
| 746 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { | 671 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |
| 747 return false; | 672 return false; |
| 748 } | 673 } |
| 749 | 674 |
| 750 } // namespace content | 675 } // namespace content |
| OLD | NEW |