| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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/avda_codec_allocator.h" | 5 #include "media/gpu/avda_codec_allocator.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <memory> | 9 #include <memory> |
| 10 | 10 |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/metrics/histogram.h" | |
| 13 #include "base/sys_info.h" | 12 #include "base/sys_info.h" |
| 14 #include "base/task_runner_util.h" | 13 #include "base/task_runner_util.h" |
| 15 #include "base/threading/thread.h" | 14 #include "base/threading/thread.h" |
| 16 #include "base/threading/thread_checker.h" | 15 #include "base/threading/thread_checker.h" |
| 17 #include "base/threading/thread_task_runner_handle.h" | 16 #include "base/threading/thread_task_runner_handle.h" |
| 18 #include "base/time/default_tick_clock.h" | 17 #include "base/time/default_tick_clock.h" |
| 19 #include "base/trace_event/trace_event.h" | 18 #include "base/trace_event/trace_event.h" |
| 19 #include "media/base/android/sdk_media_codec_bridge.h" |
| 20 #include "media/base/limits.h" | 20 #include "media/base/limits.h" |
| 21 #include "media/base/media.h" | 21 #include "media/base/media.h" |
| 22 #include "media/base/timestamp_constants.h" | 22 #include "media/base/timestamp_constants.h" |
| 23 #include "media/gpu/android_video_decode_accelerator.h" |
| 23 | 24 |
| 24 namespace media { | 25 namespace media { |
| 25 | 26 |
| 26 namespace { | 27 namespace { |
| 27 | 28 |
| 28 // Give tasks on the construction thread 800ms before considering them hung. | 29 base::LazyInstance<AVDACodecAllocator>::Leaky g_avda_codec_allocator = |
| 29 // MediaCodec.configure() calls typically take 100-200ms on a N5, so 800ms is | 30 LAZY_INSTANCE_INITIALIZER; |
| 30 // expected to very rarely result in false positives. Also, false positives have | 31 |
| 31 // low impact because we resume using the thread if its apparently hung task | 32 // Give tasks 800ms before considering them hung. MediaCodec.configure() calls |
| 32 // completes. | 33 // typically take 100-200ms on a N5, so 800ms is expected to very rarely result |
| 34 // in false positives. Also, false positives have low impact because we resume |
| 35 // using the thread when the task completes. |
| 33 constexpr base::TimeDelta kHungTaskDetectionTimeout = | 36 constexpr base::TimeDelta kHungTaskDetectionTimeout = |
| 34 base::TimeDelta::FromMilliseconds(800); | 37 base::TimeDelta::FromMilliseconds(800); |
| 35 | 38 |
| 39 // Delete |codec| and signal |done_event| if it's not null. |
| 40 void DeleteMediaCodecAndSignal(std::unique_ptr<VideoCodecBridge> codec, |
| 41 base::WaitableEvent* done_event) { |
| 42 codec.reset(); |
| 43 if (done_event) |
| 44 done_event->Signal(); |
| 45 } |
| 46 |
| 36 } // namespace | 47 } // namespace |
| 37 | 48 |
| 49 CodecConfig::CodecConfig() {} |
| 50 CodecConfig::~CodecConfig() {} |
| 51 |
| 38 AVDACodecAllocator::TestInformation::TestInformation() {} | 52 AVDACodecAllocator::TestInformation::TestInformation() {} |
| 39 AVDACodecAllocator::TestInformation::~TestInformation() {} | 53 AVDACodecAllocator::TestInformation::~TestInformation() {} |
| 40 | 54 |
| 41 AVDACodecAllocator::HangDetector::HangDetector(base::TickClock* tick_clock) | 55 AVDACodecAllocator::HangDetector::HangDetector(base::TickClock* tick_clock) |
| 42 : tick_clock_(tick_clock) {} | 56 : tick_clock_(tick_clock) {} |
| 43 | 57 |
| 44 void AVDACodecAllocator::HangDetector::WillProcessTask( | 58 void AVDACodecAllocator::HangDetector::WillProcessTask( |
| 45 const base::PendingTask& pending_task) { | 59 const base::PendingTask& pending_task) { |
| 46 base::AutoLock l(lock_); | 60 base::AutoLock l(lock_); |
| 47 task_start_time_ = tick_clock_->NowTicks(); | 61 task_start_time_ = tick_clock_->NowTicks(); |
| 48 } | 62 } |
| 49 | 63 |
| 50 void AVDACodecAllocator::HangDetector::DidProcessTask( | 64 void AVDACodecAllocator::HangDetector::DidProcessTask( |
| 51 const base::PendingTask& pending_task) { | 65 const base::PendingTask& pending_task) { |
| 52 base::AutoLock l(lock_); | 66 base::AutoLock l(lock_); |
| 53 task_start_time_ = base::TimeTicks(); | 67 task_start_time_ = base::TimeTicks(); |
| 54 } | 68 } |
| 55 | 69 |
| 56 bool AVDACodecAllocator::HangDetector::IsThreadLikelyHung() { | 70 bool AVDACodecAllocator::HangDetector::IsThreadLikelyHung() { |
| 57 base::AutoLock l(lock_); | 71 base::AutoLock l(lock_); |
| 58 if (task_start_time_.is_null()) | 72 if (task_start_time_.is_null()) |
| 59 return false; | 73 return false; |
| 60 | 74 |
| 61 return (tick_clock_->NowTicks() - task_start_time_) > | 75 return (tick_clock_->NowTicks() - task_start_time_) > |
| 62 kHungTaskDetectionTimeout; | 76 kHungTaskDetectionTimeout; |
| 63 } | 77 } |
| 64 | 78 |
| 65 // Make sure the construction threads are started for |avda|. | 79 // static |
| 66 bool AVDACodecAllocator::StartThread(AndroidVideoDecodeAccelerator* avda) { | 80 AVDACodecAllocator* AVDACodecAllocator::Instance() { |
| 81 return g_avda_codec_allocator.Pointer(); |
| 82 } |
| 83 |
| 84 // Make sure the construction threads are started for |client|. |
| 85 bool AVDACodecAllocator::StartThread(AVDACodecAllocatorClient* client) { |
| 67 DCHECK(thread_checker_.CalledOnValidThread()); | 86 DCHECK(thread_checker_.CalledOnValidThread()); |
| 68 | 87 |
| 69 // Cancel any pending StopThreadTask()s because we need the threads now. | 88 // Cancel any pending StopThreadTask()s because we need the threads now. |
| 70 weak_this_factory_.InvalidateWeakPtrs(); | 89 weak_this_factory_.InvalidateWeakPtrs(); |
| 71 | 90 |
| 72 // Try to start all threads if they haven't been started. Remember that | 91 // Try to start all threads if they haven't been started. Remember that |
| 73 // threads fail to start fairly often. | 92 // threads fail to start fairly often. |
| 74 for (size_t i = 0; i < threads_.size(); i++) { | 93 for (size_t i = 0; i < threads_.size(); i++) { |
| 75 if (threads_[i]->thread.IsRunning()) | 94 if (threads_[i]->thread.IsRunning()) |
| 76 continue; | 95 continue; |
| 77 | 96 |
| 78 if (!threads_[i]->thread.Start()) | 97 if (!threads_[i]->thread.Start()) |
| 79 continue; | 98 continue; |
| 80 | 99 |
| 81 // Register the hang detector to observe the thread's MessageLoop. | 100 // Register the hang detector to observe the thread's MessageLoop. |
| 82 threads_[i]->thread.task_runner()->PostTask( | 101 threads_[i]->thread.task_runner()->PostTask( |
| 83 FROM_HERE, | 102 FROM_HERE, |
| 84 base::Bind(&base::MessageLoop::AddTaskObserver, | 103 base::Bind(&base::MessageLoop::AddTaskObserver, |
| 85 base::Unretained(threads_[i]->thread.message_loop()), | 104 base::Unretained(threads_[i]->thread.message_loop()), |
| 86 &threads_[i]->hang_detector)); | 105 &threads_[i]->hang_detector)); |
| 87 } | 106 } |
| 88 | 107 |
| 89 // Make sure that the construction thread started, else refuse to run. | 108 // Make sure that the construction thread started, else refuse to run. |
| 90 // If other threads fail to start, then we'll post to the GPU main thread for | 109 // If other threads fail to start, then we'll post to the GPU main thread for |
| 91 // those cases. SW allocation failures are much less rare, so this usually | 110 // those cases. SW allocation failures are much less rare, so this usually |
| 92 // just costs us the latency of doing the codec allocation on the main thread. | 111 // just costs us the latency of doing the codec allocation on the main thread. |
| 93 if (!threads_[TaskType::AUTO_CODEC]->thread.IsRunning()) | 112 if (!threads_[TaskType::AUTO_CODEC]->thread.IsRunning()) |
| 94 return false; | 113 return false; |
| 95 | 114 |
| 96 thread_avda_instances_.insert(avda); | 115 clients_.insert(client); |
| 97 UMA_HISTOGRAM_ENUMERATION("Media.AVDA.NumAVDAInstances", | |
| 98 thread_avda_instances_.size(), | |
| 99 31); // PRESUBMIT_IGNORE_UMA_MAX | |
| 100 return true; | 116 return true; |
| 101 } | 117 } |
| 102 | 118 |
| 103 void AVDACodecAllocator::StopThread(AndroidVideoDecodeAccelerator* avda) { | 119 void AVDACodecAllocator::StopThread(AVDACodecAllocatorClient* client) { |
| 104 DCHECK(thread_checker_.CalledOnValidThread()); | 120 DCHECK(thread_checker_.CalledOnValidThread()); |
| 105 | 121 |
| 106 thread_avda_instances_.erase(avda); | 122 clients_.erase(client); |
| 107 // Post a task to stop the thread through the thread's task runner and back | 123 // Post a task to stop the thread through the thread's task runner and back |
| 108 // to this thread. This ensures that all pending tasks are run first. If the | 124 // to this thread. This ensures that all pending tasks are run first. If the |
| 109 // thread is hung we don't post a task to avoid leaking an unbounded number | 125 // thread is hung we don't post a task to avoid leaking an unbounded number |
| 110 // of tasks on its queue. If the thread is not hung, but appears to be, it | 126 // of tasks on its queue. If the thread is not hung, but appears to be, it |
| 111 // will stay alive until next time an AVDA tries to stop it. We're | 127 // will stay alive until next time an AVDA tries to stop it. We're |
| 112 // guaranteed to not run StopThreadTask() when the thread is hung because if | 128 // guaranteed to not run StopThreadTask() when the thread is hung because if |
| 113 // an AVDA queues tasks after DoNothing(), the StopThreadTask() reply will | 129 // an AVDA queues tasks after DoNothing(), the StopThreadTask() reply will |
| 114 // be canceled by invalidating its weak pointer. | 130 // be canceled by invalidating its weak pointer. |
| 115 base::WaitableEvent* event = | 131 base::WaitableEvent* event = |
| 116 (test_info_ ? test_info_->stop_event_.get() : nullptr); | 132 (test_info_ ? test_info_->stop_event_.get() : nullptr); |
| 117 if (!thread_avda_instances_.empty()) { | 133 if (!clients_.empty()) { |
| 118 // If we aren't stopping, then signal immediately. | 134 // If we aren't stopping, then signal immediately. |
| 119 if (event) | 135 if (event) |
| 120 event->Signal(); | 136 event->Signal(); |
| 121 return; | 137 return; |
| 122 } | 138 } |
| 123 | 139 |
| 124 for (size_t i = 0; i < threads_.size(); i++) { | 140 for (size_t i = 0; i < threads_.size(); i++) { |
| 125 if (threads_[i]->thread.IsRunning() && | 141 if (threads_[i]->thread.IsRunning() && |
| 126 !threads_[i]->hang_detector.IsThreadLikelyHung()) { | 142 !threads_[i]->hang_detector.IsThreadLikelyHung()) { |
| 127 threads_[i]->thread.task_runner()->PostTaskAndReply( | 143 threads_[i]->thread.task_runner()->PostTaskAndReply( |
| (...skipping 15 matching lines...) Expand all Loading... |
| 143 // Fail over to the main thread if this thread failed to start. Note that | 159 // Fail over to the main thread if this thread failed to start. Note that |
| 144 // if the AUTO_CODEC thread fails to start, then AVDA init will fail. | 160 // if the AUTO_CODEC thread fails to start, then AVDA init will fail. |
| 145 // We won't fall back autodetection to the main thread, even without a | 161 // We won't fall back autodetection to the main thread, even without a |
| 146 // special case here. | 162 // special case here. |
| 147 if (!thread.IsRunning()) | 163 if (!thread.IsRunning()) |
| 148 return base::ThreadTaskRunnerHandle::Get(); | 164 return base::ThreadTaskRunnerHandle::Get(); |
| 149 | 165 |
| 150 return thread.task_runner(); | 166 return thread.task_runner(); |
| 151 } | 167 } |
| 152 | 168 |
| 169 bool AVDACodecAllocator::AllocateSurface(AVDACodecAllocatorClient* client, |
| 170 int surface_id) { |
| 171 DVLOG(1) << __func__ << ": " << surface_id; |
| 172 DCHECK(thread_checker_.CalledOnValidThread()); |
| 173 |
| 174 if (surface_id == SurfaceManager::kNoSurfaceID) |
| 175 return true; |
| 176 |
| 177 // If it's not owned or being released, |client| now owns it. |
| 178 if (!surface_owners_.count(surface_id) && |
| 179 !pending_codec_releases_.count(surface_id)) { |
| 180 surface_owners_[surface_id].owner = client; |
| 181 return true; |
| 182 } |
| 183 |
| 184 // Otherwise |client| replaces the previous waiter (if any). |
| 185 OwnerRecord& record = surface_owners_[surface_id]; |
| 186 if (record.waiter) |
| 187 record.waiter->OnSurfaceAvailable(false); |
| 188 record.waiter = client; |
| 189 return false; |
| 190 } |
| 191 |
| 192 void AVDACodecAllocator::DeallocateSurface(AVDACodecAllocatorClient* client, |
| 193 int surface_id) { |
| 194 DCHECK(thread_checker_.CalledOnValidThread()); |
| 195 if (surface_id == SurfaceManager::kNoSurfaceID || |
| 196 !surface_owners_.count(surface_id)) { |
| 197 return; |
| 198 } |
| 199 |
| 200 OwnerRecord& record = surface_owners_[surface_id]; |
| 201 if (record.owner == client) |
| 202 record.owner = nullptr; |
| 203 else if (record.waiter == client) |
| 204 record.waiter = nullptr; |
| 205 |
| 206 // Promote the waiter if possible. |
| 207 if (record.waiter && !record.owner && |
| 208 !pending_codec_releases_.count(surface_id)) { |
| 209 record.owner = record.waiter; |
| 210 record.waiter = nullptr; |
| 211 record.owner->OnSurfaceAvailable(true); |
| 212 return; |
| 213 } |
| 214 |
| 215 // Remove the record if it's now unused. |
| 216 if (!record.owner && !record.waiter) |
| 217 surface_owners_.erase(surface_id); |
| 218 } |
| 219 |
| 220 // During surface teardown we have to handle the following cases. |
| 221 // 1) No AVDA has acquired the surface, or the surface has already been |
| 222 // completely released. |
| 223 // 2) A MediaCodec is currently being configured with the surface on another |
| 224 // thread. Whether an AVDA owns the surface or has already deallocated it, |
| 225 // the MediaCodec should be dropped when configuration completes. |
| 226 // 3) An AVDA owns the surface and it responds to OnSurfaceDestroyed() by: |
| 227 // a) Replacing the destroyed surface by calling MediaCodec#setSurface(). |
| 228 // b) Releasing the MediaCodec it's attached to. |
| 229 // 4) No AVDA owns the surface, but the MediaCodec it's attached to is currently |
| 230 // being destroyed on another thread. |
| 231 void AVDACodecAllocator::OnSurfaceDestroyed(int surface_id) { |
| 232 DVLOG(1) << __func__ << ": " << surface_id; |
| 233 DCHECK(thread_checker_.CalledOnValidThread()); |
| 234 |
| 235 // Notify the owner and waiter (if any). |
| 236 if (surface_owners_.count(surface_id)) { |
| 237 OwnerRecord& record = surface_owners_[surface_id]; |
| 238 if (record.waiter) { |
| 239 record.waiter->OnSurfaceAvailable(false); |
| 240 record.waiter = nullptr; |
| 241 } |
| 242 |
| 243 if (record.owner) |
| 244 record.owner->OnSurfaceDestroyed(); |
| 245 |
| 246 surface_owners_.erase(surface_id); |
| 247 } |
| 248 |
| 249 // The codec might have been released above in OnSurfaceDestroyed(), or was |
| 250 // already pending release. |
| 251 if (!pending_codec_releases_.count(surface_id)) |
| 252 return; |
| 253 |
| 254 // The codec is being released so we have to wait for it here. It's a |
| 255 // TimedWait() because the MediaCodec release may hang due to framework bugs. |
| 256 // And in that case we don't want to hang the browser UI thread. Android ANRs |
| 257 // occur when the UI thread is blocked for 5 seconds, so waiting for 2 seconds |
| 258 // gives us leeway to avoid an ANR. Verified no ANR on a Nexus 7. |
| 259 base::WaitableEvent& released = |
| 260 pending_codec_releases_.find(surface_id)->second; |
| 261 released.TimedWait(base::TimeDelta::FromSeconds(2)); |
| 262 if (!released.IsSignaled()) |
| 263 DLOG(WARNING) << __func__ << ": timed out waiting for MediaCodec#release()"; |
| 264 } |
| 265 |
| 266 std::unique_ptr<VideoCodecBridge> AVDACodecAllocator::CreateMediaCodecSync( |
| 267 scoped_refptr<CodecConfig> codec_config) { |
| 268 TRACE_EVENT0("media", "AVDA::CreateMediaCodecSync"); |
| 269 |
| 270 jobject media_crypto = codec_config->media_crypto_ |
| 271 ? codec_config->media_crypto_->obj() |
| 272 : nullptr; |
| 273 |
| 274 // |needs_protected_surface_| implies encrypted stream. |
| 275 DCHECK(!codec_config->needs_protected_surface_ || media_crypto); |
| 276 |
| 277 const bool require_software_codec = |
| 278 codec_config->task_type_ == TaskType::SW_CODEC; |
| 279 |
| 280 std::unique_ptr<VideoCodecBridge> codec(VideoCodecBridge::CreateDecoder( |
| 281 codec_config->codec_, codec_config->needs_protected_surface_, |
| 282 codec_config->initial_expected_coded_size_, |
| 283 codec_config->surface_.j_surface().obj(), media_crypto, |
| 284 codec_config->csd0_, codec_config->csd1_, true, require_software_codec)); |
| 285 |
| 286 return codec; |
| 287 } |
| 288 |
| 289 void AVDACodecAllocator::CreateMediaCodecAsync( |
| 290 base::WeakPtr<AVDACodecAllocatorClient> client, |
| 291 scoped_refptr<CodecConfig> codec_config) { |
| 292 base::PostTaskAndReplyWithResult( |
| 293 TaskRunnerFor(codec_config->task_type_).get(), FROM_HERE, |
| 294 base::Bind(&AVDACodecAllocator::CreateMediaCodecSync, |
| 295 base::Unretained(this), codec_config), |
| 296 base::Bind(&AVDACodecAllocatorClient::OnCodecConfigured, client)); |
| 297 } |
| 298 |
| 299 void AVDACodecAllocator::ReleaseMediaCodec( |
| 300 std::unique_ptr<VideoCodecBridge> media_codec, |
| 301 TaskType task_type, |
| 302 int surface_id) { |
| 303 DCHECK(thread_checker_.CalledOnValidThread()); |
| 304 DCHECK(media_codec); |
| 305 |
| 306 // No need to track the release if it's a SurfaceTexture. |
| 307 if (surface_id == SurfaceManager::kNoSurfaceID) { |
| 308 TaskRunnerFor(task_type)->PostTask( |
| 309 FROM_HERE, base::Bind(&DeleteMediaCodecAndSignal, |
| 310 base::Passed(std::move(media_codec)), nullptr)); |
| 311 return; |
| 312 } |
| 313 |
| 314 pending_codec_releases_.emplace( |
| 315 std::piecewise_construct, std::forward_as_tuple(surface_id), |
| 316 std::forward_as_tuple(base::WaitableEvent::ResetPolicy::MANUAL, |
| 317 base::WaitableEvent::InitialState::NOT_SIGNALED)); |
| 318 base::WaitableEvent* released = |
| 319 &pending_codec_releases_.find(surface_id)->second; |
| 320 |
| 321 // TODO(watk): Even if this is the current thread, things will work, but we |
| 322 // should refactor this to not tolerate threads failing to start. |
| 323 TaskRunnerFor(task_type)->PostTaskAndReply( |
| 324 FROM_HERE, base::Bind(&DeleteMediaCodecAndSignal, |
| 325 base::Passed(std::move(media_codec)), released), |
| 326 base::Bind(&AVDACodecAllocator::OnMediaCodecAndSurfaceReleased, |
| 327 base::Unretained(this), surface_id)); |
| 328 } |
| 329 |
| 330 void AVDACodecAllocator::OnMediaCodecAndSurfaceReleased(int surface_id) { |
| 331 DCHECK(thread_checker_.CalledOnValidThread()); |
| 332 |
| 333 pending_codec_releases_.erase(surface_id); |
| 334 if (!surface_owners_.count(surface_id)) |
| 335 return; |
| 336 |
| 337 OwnerRecord& record = surface_owners_[surface_id]; |
| 338 if (!record.owner && record.waiter) { |
| 339 record.owner = record.waiter; |
| 340 record.waiter = nullptr; |
| 341 record.owner->OnSurfaceAvailable(true); |
| 342 } |
| 343 } |
| 344 |
| 153 // Returns a hint about whether the construction thread has hung for | 345 // Returns a hint about whether the construction thread has hung for |
| 154 // |task_type|. Note that if a thread isn't started, then we'll just return | 346 // |task_type|. Note that if a thread isn't started, then we'll just return |
| 155 // "not hung", since it'll run on the current thread anyway. The hang | 347 // "not hung", since it'll run on the current thread anyway. The hang |
| 156 // detector will see no pending jobs in that case, so it's automatic. | 348 // detector will see no pending jobs in that case, so it's automatic. |
| 157 bool AVDACodecAllocator::IsThreadLikelyHung(TaskType task_type) { | 349 bool AVDACodecAllocator::IsThreadLikelyHung(TaskType task_type) { |
| 158 DCHECK(thread_checker_.CalledOnValidThread()); | 350 DCHECK(thread_checker_.CalledOnValidThread()); |
| 159 return threads_[task_type]->hang_detector.IsThreadLikelyHung(); | 351 return threads_[task_type]->hang_detector.IsThreadLikelyHung(); |
| 160 } | 352 } |
| 161 | 353 |
| 162 bool AVDACodecAllocator::IsAnyRegisteredAVDA() { | 354 bool AVDACodecAllocator::IsAnyRegisteredAVDA() { |
| 163 return !thread_avda_instances_.empty(); | 355 return !clients_.empty(); |
| 164 } | 356 } |
| 165 | 357 |
| 166 AVDACodecAllocator::TaskType AVDACodecAllocator::TaskTypeForAllocation() { | 358 TaskType AVDACodecAllocator::TaskTypeForAllocation() { |
| 167 if (!IsThreadLikelyHung(TaskType::AUTO_CODEC)) | 359 if (!IsThreadLikelyHung(TaskType::AUTO_CODEC)) |
| 168 return TaskType::AUTO_CODEC; | 360 return TaskType::AUTO_CODEC; |
| 169 | 361 |
| 170 if (!IsThreadLikelyHung(TaskType::SW_CODEC)) | 362 if (!IsThreadLikelyHung(TaskType::SW_CODEC)) |
| 171 return TaskType::SW_CODEC; | 363 return TaskType::SW_CODEC; |
| 172 | 364 |
| 173 // If nothing is working, then we can't allocate anyway. | 365 // If nothing is working, then we can't allocate anyway. |
| 174 return TaskType::FAILED_CODEC; | 366 return TaskType::FAILED_CODEC; |
| 175 } | 367 } |
| 176 | 368 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 206 } | 398 } |
| 207 | 399 |
| 208 void AVDACodecAllocator::StopThreadTask(size_t index, | 400 void AVDACodecAllocator::StopThreadTask(size_t index, |
| 209 base::WaitableEvent* event) { | 401 base::WaitableEvent* event) { |
| 210 threads_[index]->thread.Stop(); | 402 threads_[index]->thread.Stop(); |
| 211 if (event) | 403 if (event) |
| 212 event->Signal(); | 404 event->Signal(); |
| 213 } | 405 } |
| 214 | 406 |
| 215 } // namespace media | 407 } // namespace media |
| OLD | NEW |