| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 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 "media/gpu/android_video_decode_accelerator.h" | 5 #include "media/gpu/android_video_decode_accelerator.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <memory> | 9 #include <memory> |
| 10 | 10 |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 108 static inline const base::TimeDelta ErrorPostingDelay() { | 108 static inline const base::TimeDelta ErrorPostingDelay() { |
| 109 return base::TimeDelta::FromSeconds(2); | 109 return base::TimeDelta::FromSeconds(2); |
| 110 } | 110 } |
| 111 | 111 |
| 112 // For RecordFormatChangedMetric. | 112 // For RecordFormatChangedMetric. |
| 113 enum FormatChangedValue { | 113 enum FormatChangedValue { |
| 114 CodecInitialized = false, | 114 CodecInitialized = false, |
| 115 MissingFormatChanged = true | 115 MissingFormatChanged = true |
| 116 }; | 116 }; |
| 117 | 117 |
| 118 // Maximum number of concurrent, incomplete codec creations that we'll allow |
| 119 // before turning off autodection of codec type. |
| 120 enum { kMaxConcurrentCodecAutodetections = 4 }; |
| 121 |
| 118 static inline void RecordFormatChangedMetric(FormatChangedValue value) { | 122 static inline void RecordFormatChangedMetric(FormatChangedValue value) { |
| 119 UMA_HISTOGRAM_BOOLEAN("Media.AVDA.MissingFormatChanged", !!value); | 123 UMA_HISTOGRAM_BOOLEAN("Media.AVDA.MissingFormatChanged", !!value); |
| 120 } | 124 } |
| 121 | 125 |
| 122 // Handle OnFrameAvailable callbacks safely. Since they occur asynchronously, | 126 // Handle OnFrameAvailable callbacks safely. Since they occur asynchronously, |
| 123 // we take care that the AVDA that wants them still exists. A WeakPtr to | 127 // we take care that the AVDA that wants them still exists. A WeakPtr to |
| 124 // the AVDA would be preferable, except that OnFrameAvailable callbacks can | 128 // the AVDA would be preferable, except that OnFrameAvailable callbacks can |
| 125 // occur off the gpu main thread. We also can't guarantee when the | 129 // occur off the gpu main thread. We also can't guarantee when the |
| 126 // SurfaceTexture will quit sending callbacks to coordinate with the | 130 // SurfaceTexture will quit sending callbacks to coordinate with the |
| 127 // destruction of the AVDA, so we have a separate object that the cb can own. | 131 // destruction of the AVDA, so we have a separate object that the cb can own. |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 178 // buffer to MediaCodec). This is inherently a race, since we don't know if | 182 // buffer to MediaCodec). This is inherently a race, since we don't know if |
| 179 // MediaCodec is broken or just slow. Since the MediaCodec API doesn't let | 183 // MediaCodec is broken or just slow. Since the MediaCodec API doesn't let |
| 180 // us wait on MediaCodec state changes prior to L, we more or less have to | 184 // us wait on MediaCodec state changes prior to L, we more or less have to |
| 181 // time out or keep polling forever in some common cases. | 185 // time out or keep polling forever in some common cases. |
| 182 class AVDATimerManager { | 186 class AVDATimerManager { |
| 183 public: | 187 public: |
| 184 // Make sure that the construction thread is started for |avda_instance|. | 188 // Make sure that the construction thread is started for |avda_instance|. |
| 185 bool StartThread(AndroidVideoDecodeAccelerator* avda_instance) { | 189 bool StartThread(AndroidVideoDecodeAccelerator* avda_instance) { |
| 186 DCHECK(thread_checker_.CalledOnValidThread()); | 190 DCHECK(thread_checker_.CalledOnValidThread()); |
| 187 | 191 |
| 188 if (thread_avda_instances_.empty()) { | 192 // If we chose not to shut it down due to pending codec constructions, then |
| 193 // the thread might already be started even if there are no avda instances. |
| 194 // Plus, sometimes we just fail to start the thread. |
| 195 if (!construction_thread_.IsRunning()) { |
| 189 if (!construction_thread_.Start()) { | 196 if (!construction_thread_.Start()) { |
| 190 LOG(ERROR) << "Failed to start construction thread."; | 197 LOG(ERROR) << "Failed to start construction thread."; |
| 191 return false; | 198 return false; |
| 192 } | 199 } |
| 193 } | 200 } |
| 194 | 201 |
| 195 thread_avda_instances_.insert(avda_instance); | 202 thread_avda_instances_.insert(avda_instance); |
| 196 return true; | 203 return true; |
| 197 } | 204 } |
| 198 | 205 |
| 199 // |avda_instance| will no longer need the construction thread. Stop the | 206 // |avda_instance| will no longer need the construction thread. Stop the |
| 200 // thread if this is the last instance. | 207 // thread if this is the last instance. |
| 201 void StopThread(AndroidVideoDecodeAccelerator* avda_instance) { | 208 void StopThread(AndroidVideoDecodeAccelerator* avda_instance) { |
| 202 DCHECK(thread_checker_.CalledOnValidThread()); | 209 DCHECK(thread_checker_.CalledOnValidThread()); |
| 203 | 210 |
| 204 thread_avda_instances_.erase(avda_instance); | 211 thread_avda_instances_.erase(avda_instance); |
| 205 if (thread_avda_instances_.empty()) | 212 if (!thread_avda_instances_.empty()) |
| 206 construction_thread_.Stop(); | 213 return; |
| 214 |
| 215 // Don't stop the thread if there are outstanding requests, since they |
| 216 // might be hung. They also might simply be incomplete, and the thread |
| 217 // will stay running until we try to shut it down again. |
| 218 base::AutoLock auto_lock(autodetection_info_.lock_); |
| 219 if (autodetection_info_.outstanding_) |
| 220 return; |
| 221 |
| 222 construction_thread_.Stop(); |
| 207 } | 223 } |
| 208 | 224 |
| 209 // Request periodic callback of |avda_instance|->DoIOTask(). Does nothing if | 225 // Request periodic callback of |avda_instance|->DoIOTask(). Does nothing if |
| 210 // the instance is already registered and the timer started. The first request | 226 // the instance is already registered and the timer started. The first request |
| 211 // will start the repeating timer on an interval of DecodePollDelay(). | 227 // will start the repeating timer on an interval of DecodePollDelay(). |
| 212 void StartTimer(AndroidVideoDecodeAccelerator* avda_instance) { | 228 void StartTimer(AndroidVideoDecodeAccelerator* avda_instance) { |
| 213 DCHECK(thread_checker_.CalledOnValidThread()); | 229 DCHECK(thread_checker_.CalledOnValidThread()); |
| 214 | 230 |
| 215 timer_avda_instances_.insert(avda_instance); | 231 timer_avda_instances_.insert(avda_instance); |
| 216 | 232 |
| (...skipping 25 matching lines...) Expand all Loading... |
| 242 io_timer_.Stop(); | 258 io_timer_.Stop(); |
| 243 } | 259 } |
| 244 | 260 |
| 245 // Eventually, we should run the timer on this thread. For now, we just keep | 261 // Eventually, we should run the timer on this thread. For now, we just keep |
| 246 // it as a convenience for construction. | 262 // it as a convenience for construction. |
| 247 scoped_refptr<base::SingleThreadTaskRunner> ConstructionTaskRunner() { | 263 scoped_refptr<base::SingleThreadTaskRunner> ConstructionTaskRunner() { |
| 248 DCHECK(thread_checker_.CalledOnValidThread()); | 264 DCHECK(thread_checker_.CalledOnValidThread()); |
| 249 return construction_thread_.task_runner(); | 265 return construction_thread_.task_runner(); |
| 250 } | 266 } |
| 251 | 267 |
| 268 // Called on the main thread when codec autodetection starts. There may be |
| 269 // several calls to this before any call to OnAsyncCodecAutodetectionComplete. |
| 270 void OnAsyncCodecAutodetectionStarted() { |
| 271 base::AutoLock auto_lock(autodetection_info_.lock_); |
| 272 ++autodetection_info_.outstanding_; |
| 273 } |
| 274 |
| 275 // Called on any thread when a codec is constructed with autodetection. This |
| 276 // assumes that requests are ordered, so please don't mix sync and async codec |
| 277 // construction here. This may be called on any thread. |
| 278 void OnAsyncCodecAutodetectionComplete() { |
| 279 base::AutoLock auto_lock_l(autodetection_info_.lock_); |
| 280 DCHECK_GT(autodetection_info_.outstanding_, 0); |
| 281 --autodetection_info_.outstanding_; |
| 282 } |
| 283 |
| 284 // Return a hint about whether autodetecting the codec type is safe or not. |
| 285 bool IsCodecAutodetectionProbablySafe() { |
| 286 base::AutoLock auto_lock_l(autodetection_info_.lock_); |
| 287 |
| 288 return autodetection_info_.outstanding_ < kMaxConcurrentCodecAutodetections; |
| 289 } |
| 290 |
| 252 // |avda| would like to use |surface_id|. If it is not busy, then mark it | 291 // |avda| would like to use |surface_id|. If it is not busy, then mark it |
| 253 // as busy and return true. If it is busy, then replace any existing waiter, | 292 // as busy and return true. If it is busy, then replace any existing waiter, |
| 254 // make |avda| the current waiter, and return false. Any existing waiter | 293 // make |avda| the current waiter, and return false. Any existing waiter |
| 255 // is assumed to be on the way out, so we fail its allocation request. | 294 // is assumed to be on the way out, so we fail its allocation request. |
| 256 bool AllocateSurface(int surface_id, AndroidVideoDecodeAccelerator* avda) { | 295 bool AllocateSurface(int surface_id, AndroidVideoDecodeAccelerator* avda) { |
| 257 // Nobody has to wait for no surface. | 296 // Nobody has to wait for no surface. |
| 258 if (surface_id == AndroidVideoDecodeAccelerator::Config::kNoSurfaceID) | 297 if (surface_id == AndroidVideoDecodeAccelerator::Config::kNoSurfaceID) |
| 259 return true; | 298 return true; |
| 260 | 299 |
| 261 auto iter = surface_waiter_map_.find(surface_id); | 300 auto iter = surface_waiter_map_.find(surface_id); |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 347 SurfaceWaiterMap surface_waiter_map_; | 386 SurfaceWaiterMap surface_waiter_map_; |
| 348 | 387 |
| 349 // Since we can't delete while iterating when using a set, defer erasure until | 388 // Since we can't delete while iterating when using a set, defer erasure until |
| 350 // after iteration complete. | 389 // after iteration complete. |
| 351 bool timer_running_ = false; | 390 bool timer_running_ = false; |
| 352 std::set<AndroidVideoDecodeAccelerator*> pending_erase_; | 391 std::set<AndroidVideoDecodeAccelerator*> pending_erase_; |
| 353 | 392 |
| 354 // Repeating timer responsible for draining pending IO to the codecs. | 393 // Repeating timer responsible for draining pending IO to the codecs. |
| 355 base::RepeatingTimer io_timer_; | 394 base::RepeatingTimer io_timer_; |
| 356 | 395 |
| 396 // Data for determining if codec creation is hanging. |
| 397 struct { |
| 398 // Lock that protects other members of this struct. |
| 399 base::Lock lock_; |
| 400 |
| 401 // Number of currently pending autodetection requests. |
| 402 int outstanding_ = 0; |
| 403 } autodetection_info_; |
| 404 |
| 357 base::Thread construction_thread_; | 405 base::Thread construction_thread_; |
| 358 | 406 |
| 359 base::ThreadChecker thread_checker_; | 407 base::ThreadChecker thread_checker_; |
| 360 | 408 |
| 361 DISALLOW_COPY_AND_ASSIGN(AVDATimerManager); | 409 DISALLOW_COPY_AND_ASSIGN(AVDATimerManager); |
| 362 }; | 410 }; |
| 363 | 411 |
| 364 static base::LazyInstance<AVDATimerManager>::Leaky g_avda_timer = | 412 static base::LazyInstance<AVDATimerManager>::Leaky g_avda_timer = |
| 365 LAZY_INSTANCE_INITIALIZER; | 413 LAZY_INSTANCE_INITIALIZER; |
| 366 | 414 |
| (...skipping 670 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1037 // Tell the strategy that we're changing codecs. The codec itself could be | 1085 // Tell the strategy that we're changing codecs. The codec itself could be |
| 1038 // used normally, since we don't replace it until we're back on the main | 1086 // used normally, since we don't replace it until we're back on the main |
| 1039 // thread. However, if we're using an output surface, then the incoming codec | 1087 // thread. However, if we're using an output surface, then the incoming codec |
| 1040 // might access that surface while the main thread is drawing. Telling the | 1088 // might access that surface while the main thread is drawing. Telling the |
| 1041 // strategy to forget the codec avoids this. | 1089 // strategy to forget the codec avoids this. |
| 1042 if (media_codec_) { | 1090 if (media_codec_) { |
| 1043 media_codec_.reset(); | 1091 media_codec_.reset(); |
| 1044 strategy_->CodecChanged(nullptr); | 1092 strategy_->CodecChanged(nullptr); |
| 1045 } | 1093 } |
| 1046 | 1094 |
| 1095 // Choose whether to autodetect the codec type. |
| 1096 codec_config_->allow_autodetection_ = |
| 1097 g_avda_timer.Pointer()->IsCodecAutodetectionProbablySafe(); |
| 1098 codec_config_->notify_completion_ = codec_config_->allow_autodetection_; |
| 1099 if (codec_config_->allow_autodetection_) |
| 1100 g_avda_timer.Pointer()->OnAsyncCodecAutodetectionStarted(); |
| 1101 |
| 1102 // If we're not trying autodetection, then use the main thread. The original |
| 1103 // might be blocked. |
| 1047 scoped_refptr<base::SingleThreadTaskRunner> task_runner = | 1104 scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| 1048 g_avda_timer.Pointer()->ConstructionTaskRunner(); | 1105 codec_config_->allow_autodetection_ |
| 1106 ? g_avda_timer.Pointer()->ConstructionTaskRunner() |
| 1107 : base::ThreadTaskRunnerHandle::Get(); |
| 1049 CHECK(task_runner); | 1108 CHECK(task_runner); |
| 1050 | 1109 |
| 1051 base::PostTaskAndReplyWithResult( | 1110 base::PostTaskAndReplyWithResult( |
| 1052 task_runner.get(), FROM_HERE, | 1111 task_runner.get(), FROM_HERE, |
| 1053 base::Bind(&AndroidVideoDecodeAccelerator::ConfigureMediaCodecOnAnyThread, | 1112 base::Bind(&AndroidVideoDecodeAccelerator::ConfigureMediaCodecOnAnyThread, |
| 1054 codec_config_), | 1113 codec_config_), |
| 1055 base::Bind(&AndroidVideoDecodeAccelerator::OnCodecConfigured, | 1114 base::Bind(&AndroidVideoDecodeAccelerator::OnCodecConfigured, |
| 1056 weak_this_factory_.GetWeakPtr())); | 1115 weak_this_factory_.GetWeakPtr())); |
| 1057 } | 1116 } |
| 1058 | 1117 |
| 1059 bool AndroidVideoDecodeAccelerator::ConfigureMediaCodecSynchronously() { | 1118 bool AndroidVideoDecodeAccelerator::ConfigureMediaCodecSynchronously() { |
| 1060 state_ = WAITING_FOR_CODEC; | 1119 state_ = WAITING_FOR_CODEC; |
| 1120 |
| 1121 // Decide whether to allow autodetection or not. Since we're on the main |
| 1122 // thread, and this request is unordered with respect to pending async config |
| 1123 // attempts, don't record it. It may break book-keeping, and there's not |
| 1124 // much we can do anyway. |
| 1125 codec_config_->allow_autodetection_ = |
| 1126 g_avda_timer.Pointer()->IsCodecAutodetectionProbablySafe(); |
| 1127 codec_config_->notify_completion_ = false; |
| 1128 |
| 1061 std::unique_ptr<media::VideoCodecBridge> media_codec = | 1129 std::unique_ptr<media::VideoCodecBridge> media_codec = |
| 1062 ConfigureMediaCodecOnAnyThread(codec_config_); | 1130 ConfigureMediaCodecOnAnyThread(codec_config_); |
| 1063 OnCodecConfigured(std::move(media_codec)); | 1131 OnCodecConfigured(std::move(media_codec)); |
| 1064 return !!media_codec_; | 1132 return !!media_codec_; |
| 1065 } | 1133 } |
| 1066 | 1134 |
| 1067 std::unique_ptr<media::VideoCodecBridge> | 1135 std::unique_ptr<media::VideoCodecBridge> |
| 1068 AndroidVideoDecodeAccelerator::ConfigureMediaCodecOnAnyThread( | 1136 AndroidVideoDecodeAccelerator::ConfigureMediaCodecOnAnyThread( |
| 1069 scoped_refptr<CodecConfig> codec_config) { | 1137 scoped_refptr<CodecConfig> codec_config) { |
| 1070 TRACE_EVENT0("media", "AVDA::ConfigureMediaCodec"); | 1138 TRACE_EVENT0("media", "AVDA::ConfigureMediaCodec"); |
| 1071 | 1139 |
| 1072 jobject media_crypto = codec_config->media_crypto_ | 1140 jobject media_crypto = codec_config->media_crypto_ |
| 1073 ? codec_config->media_crypto_->obj() | 1141 ? codec_config->media_crypto_->obj() |
| 1074 : nullptr; | 1142 : nullptr; |
| 1075 | 1143 |
| 1076 // |needs_protected_surface_| implies encrypted stream. | 1144 // |needs_protected_surface_| implies encrypted stream. |
| 1077 DCHECK(!codec_config->needs_protected_surface_ || media_crypto); | 1145 DCHECK(!codec_config->needs_protected_surface_ || media_crypto); |
| 1078 | 1146 |
| 1079 return std::unique_ptr<media::VideoCodecBridge>( | 1147 const bool require_software_codec = !codec_config->allow_autodetection_; |
| 1080 media::VideoCodecBridge::CreateDecoder( | 1148 |
| 1149 std::unique_ptr<media::VideoCodecBridge> codec( |
| 1150 VideoCodecBridge::CreateDecoder( |
| 1081 codec_config->codec_, codec_config->needs_protected_surface_, | 1151 codec_config->codec_, codec_config->needs_protected_surface_, |
| 1082 codec_config->initial_expected_coded_size_, | 1152 codec_config->initial_expected_coded_size_, |
| 1083 codec_config->surface_.j_surface().obj(), media_crypto, true)); | 1153 codec_config->surface_.j_surface().obj(), media_crypto, true, |
| 1154 require_software_codec)); |
| 1155 |
| 1156 // If we successfully completed after an autodetect, then let the other |
| 1157 // instances know that we didn't get stuck. |
| 1158 if (codec_config->notify_completion_) |
| 1159 g_avda_timer.Pointer()->OnAsyncCodecAutodetectionComplete(); |
| 1160 |
| 1161 return codec; |
| 1084 } | 1162 } |
| 1085 | 1163 |
| 1086 void AndroidVideoDecodeAccelerator::OnCodecConfigured( | 1164 void AndroidVideoDecodeAccelerator::OnCodecConfigured( |
| 1087 std::unique_ptr<media::VideoCodecBridge> media_codec) { | 1165 std::unique_ptr<media::VideoCodecBridge> media_codec) { |
| 1088 DCHECK(thread_checker_.CalledOnValidThread()); | 1166 DCHECK(thread_checker_.CalledOnValidThread()); |
| 1089 DCHECK(state_ == WAITING_FOR_CODEC || state_ == SURFACE_DESTROYED); | 1167 DCHECK(state_ == WAITING_FOR_CODEC || state_ == SURFACE_DESTROYED); |
| 1090 | 1168 |
| 1091 // Record one instance of the codec being initialized. | 1169 // Record one instance of the codec being initialized. |
| 1092 RecordFormatChangedMetric(FormatChangedValue::CodecInitialized); | 1170 RecordFormatChangedMetric(FormatChangedValue::CodecInitialized); |
| 1093 | 1171 |
| (...skipping 231 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1325 DCHECK(thread_checker_.CalledOnValidThread()); | 1403 DCHECK(thread_checker_.CalledOnValidThread()); |
| 1326 | 1404 |
| 1327 if (!on_destroying_surface_cb_.is_null()) { | 1405 if (!on_destroying_surface_cb_.is_null()) { |
| 1328 AVDASurfaceTracker::GetInstance()->UnregisterOnDestroyingSurfaceCallback( | 1406 AVDASurfaceTracker::GetInstance()->UnregisterOnDestroyingSurfaceCallback( |
| 1329 on_destroying_surface_cb_); | 1407 on_destroying_surface_cb_); |
| 1330 } | 1408 } |
| 1331 | 1409 |
| 1332 if (strategy_) | 1410 if (strategy_) |
| 1333 strategy_->EndCleanup(); | 1411 strategy_->EndCleanup(); |
| 1334 | 1412 |
| 1413 AVDATimerManager* manager = g_avda_timer.Pointer(); |
| 1414 |
| 1335 // We no longer care about |surface_id|, in case we did before. It's okay | 1415 // We no longer care about |surface_id|, in case we did before. It's okay |
| 1336 // if we have no surface and/or weren't the owner or a waiter. | 1416 // if we have no surface and/or weren't the owner or a waiter. |
| 1337 g_avda_timer.Pointer()->DeallocateSurface(config_.surface_id, this); | 1417 manager->DeallocateSurface(config_.surface_id, this); |
| 1338 | 1418 |
| 1339 // Note that async codec construction might still be in progress. In that | 1419 // Note that async codec construction might still be in progress. In that |
| 1340 // case, the codec will be deleted when it completes once we invalidate all | 1420 // case, the codec will be deleted when it completes once we invalidate all |
| 1341 // our weak refs. | 1421 // our weak refs. |
| 1342 weak_this_factory_.InvalidateWeakPtrs(); | 1422 weak_this_factory_.InvalidateWeakPtrs(); |
| 1343 if (media_codec_) { | 1423 if (media_codec_) { |
| 1344 g_avda_timer.Pointer()->StopTimer(this); | 1424 manager->StopTimer(this); |
| 1345 media_codec_.reset(); | 1425 // If codec construction is broken, then we can't release this codec if it's |
| 1426 // backed by hardware, else it may hang too. Post it to the construction |
| 1427 // thread, and it'll get freed if things start working. If things are |
| 1428 // already working, then it'll be freed soon. |
| 1429 if (media_codec_->IsSoftwareCodec()) { |
| 1430 media_codec_.reset(); |
| 1431 } else { |
| 1432 manager->ConstructionTaskRunner()->DeleteSoon(FROM_HERE, |
| 1433 media_codec_.release()); |
| 1434 } |
| 1346 } | 1435 } |
| 1347 delete this; | 1436 delete this; |
| 1348 } | 1437 } |
| 1349 | 1438 |
| 1350 bool AndroidVideoDecodeAccelerator::TryToSetupDecodeOnSeparateThread( | 1439 bool AndroidVideoDecodeAccelerator::TryToSetupDecodeOnSeparateThread( |
| 1351 const base::WeakPtr<Client>& decode_client, | 1440 const base::WeakPtr<Client>& decode_client, |
| 1352 const scoped_refptr<base::SingleThreadTaskRunner>& decode_task_runner) { | 1441 const scoped_refptr<base::SingleThreadTaskRunner>& decode_task_runner) { |
| 1353 return false; | 1442 return false; |
| 1354 } | 1443 } |
| 1355 | 1444 |
| (...skipping 307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1663 if (media::MediaCodecUtil::IsSurfaceViewOutputSupported()) { | 1752 if (media::MediaCodecUtil::IsSurfaceViewOutputSupported()) { |
| 1664 capabilities.flags |= media::VideoDecodeAccelerator::Capabilities:: | 1753 capabilities.flags |= media::VideoDecodeAccelerator::Capabilities:: |
| 1665 SUPPORTS_EXTERNAL_OUTPUT_SURFACE; | 1754 SUPPORTS_EXTERNAL_OUTPUT_SURFACE; |
| 1666 } | 1755 } |
| 1667 } | 1756 } |
| 1668 | 1757 |
| 1669 return capabilities; | 1758 return capabilities; |
| 1670 } | 1759 } |
| 1671 | 1760 |
| 1672 } // namespace media | 1761 } // namespace media |
| OLD | NEW |