| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 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 // THREAD SAFETY | 5 // THREAD SAFETY |
| 6 // | 6 // |
| 7 // AlsaPcmOutputStream object is *not* thread-safe and should only be used | 7 // AlsaPcmOutputStream object is *not* thread-safe and should only be used |
| 8 // from the audio thread. We DCHECK on this assumption whenever we can. | 8 // from the audio thread. We DCHECK on this assumption whenever we can. |
| 9 // | 9 // |
| 10 // SEMANTICS OF Close() | 10 // SEMANTICS OF Close() |
| (...skipping 24 matching lines...) Expand all Loading... |
| 35 #include "media/audio/alsa/alsa_output.h" | 35 #include "media/audio/alsa/alsa_output.h" |
| 36 | 36 |
| 37 #include <stddef.h> | 37 #include <stddef.h> |
| 38 | 38 |
| 39 #include <algorithm> | 39 #include <algorithm> |
| 40 | 40 |
| 41 #include "base/bind.h" | 41 #include "base/bind.h" |
| 42 #include "base/logging.h" | 42 #include "base/logging.h" |
| 43 #include "base/memory/free_deleter.h" | 43 #include "base/memory/free_deleter.h" |
| 44 #include "base/stl_util.h" | 44 #include "base/stl_util.h" |
| 45 #include "base/threading/thread_task_runner_handle.h" |
| 45 #include "base/trace_event/trace_event.h" | 46 #include "base/trace_event/trace_event.h" |
| 46 #include "media/audio/alsa/alsa_util.h" | 47 #include "media/audio/alsa/alsa_util.h" |
| 47 #include "media/audio/alsa/alsa_wrapper.h" | 48 #include "media/audio/alsa/alsa_wrapper.h" |
| 48 #include "media/audio/alsa/audio_manager_alsa.h" | 49 #include "media/audio/alsa/audio_manager_alsa.h" |
| 49 #include "media/base/channel_mixer.h" | 50 #include "media/base/channel_mixer.h" |
| 50 #include "media/base/data_buffer.h" | 51 #include "media/base/data_buffer.h" |
| 51 #include "media/base/seekable_buffer.h" | 52 #include "media/base/seekable_buffer.h" |
| 52 | 53 |
| 53 namespace media { | 54 namespace media { |
| 54 | 55 |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 149 bytes_per_frame_(params.GetBytesPerFrame()), | 150 bytes_per_frame_(params.GetBytesPerFrame()), |
| 150 packet_size_(params.GetBytesPerBuffer()), | 151 packet_size_(params.GetBytesPerBuffer()), |
| 151 latency_(std::max( | 152 latency_(std::max( |
| 152 base::TimeDelta::FromMicroseconds(kMinLatencyMicros), | 153 base::TimeDelta::FromMicroseconds(kMinLatencyMicros), |
| 153 FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))), | 154 FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))), |
| 154 bytes_per_output_frame_(bytes_per_frame_), | 155 bytes_per_output_frame_(bytes_per_frame_), |
| 155 alsa_buffer_frames_(0), | 156 alsa_buffer_frames_(0), |
| 156 stop_stream_(false), | 157 stop_stream_(false), |
| 157 wrapper_(wrapper), | 158 wrapper_(wrapper), |
| 158 manager_(manager), | 159 manager_(manager), |
| 159 message_loop_(base::MessageLoop::current()), | 160 task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 160 playback_handle_(NULL), | 161 playback_handle_(NULL), |
| 161 frames_per_packet_(packet_size_ / bytes_per_frame_), | 162 frames_per_packet_(packet_size_ / bytes_per_frame_), |
| 162 state_(kCreated), | 163 state_(kCreated), |
| 163 volume_(1.0f), | 164 volume_(1.0f), |
| 164 source_callback_(NULL), | 165 source_callback_(NULL), |
| 165 audio_bus_(AudioBus::Create(params)), | 166 audio_bus_(AudioBus::Create(params)), |
| 166 weak_factory_(this) { | 167 weak_factory_(this) { |
| 167 DCHECK(manager_->GetTaskRunner()->BelongsToCurrentThread()); | 168 DCHECK(manager_->GetTaskRunner()->BelongsToCurrentThread()); |
| 168 DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_); | 169 DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_); |
| 169 | 170 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 181 | 182 |
| 182 AlsaPcmOutputStream::~AlsaPcmOutputStream() { | 183 AlsaPcmOutputStream::~AlsaPcmOutputStream() { |
| 183 InternalState current_state = state(); | 184 InternalState current_state = state(); |
| 184 DCHECK(current_state == kCreated || | 185 DCHECK(current_state == kCreated || |
| 185 current_state == kIsClosed || | 186 current_state == kIsClosed || |
| 186 current_state == kInError); | 187 current_state == kInError); |
| 187 DCHECK(!playback_handle_); | 188 DCHECK(!playback_handle_); |
| 188 } | 189 } |
| 189 | 190 |
| 190 bool AlsaPcmOutputStream::Open() { | 191 bool AlsaPcmOutputStream::Open() { |
| 191 DCHECK(IsOnAudioThread()); | 192 DCHECK(CalledOnValidThread()); |
| 192 | 193 |
| 193 if (state() == kInError) | 194 if (state() == kInError) |
| 194 return false; | 195 return false; |
| 195 | 196 |
| 196 if (!CanTransitionTo(kIsOpened)) { | 197 if (!CanTransitionTo(kIsOpened)) { |
| 197 NOTREACHED() << "Invalid state: " << state(); | 198 NOTREACHED() << "Invalid state: " << state(); |
| 198 return false; | 199 return false; |
| 199 } | 200 } |
| 200 | 201 |
| 201 // We do not need to check if the transition was successful because | 202 // We do not need to check if the transition was successful because |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 239 // Buffer size is at least twice of packet size. | 240 // Buffer size is at least twice of packet size. |
| 240 alsa_buffer_frames_ = frames_per_packet_ * 2; | 241 alsa_buffer_frames_ = frames_per_packet_ * 2; |
| 241 } else { | 242 } else { |
| 242 alsa_buffer_frames_ = buffer_size; | 243 alsa_buffer_frames_ = buffer_size; |
| 243 } | 244 } |
| 244 | 245 |
| 245 return true; | 246 return true; |
| 246 } | 247 } |
| 247 | 248 |
| 248 void AlsaPcmOutputStream::Close() { | 249 void AlsaPcmOutputStream::Close() { |
| 249 DCHECK(IsOnAudioThread()); | 250 DCHECK(CalledOnValidThread()); |
| 250 | 251 |
| 251 if (state() != kIsClosed) | 252 if (state() != kIsClosed) |
| 252 TransitionTo(kIsClosed); | 253 TransitionTo(kIsClosed); |
| 253 | 254 |
| 254 // Shutdown the audio device. | 255 // Shutdown the audio device. |
| 255 if (playback_handle_) { | 256 if (playback_handle_) { |
| 256 if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { | 257 if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) { |
| 257 LOG(WARNING) << "Unable to close audio device. Leaking handle."; | 258 LOG(WARNING) << "Unable to close audio device. Leaking handle."; |
| 258 } | 259 } |
| 259 playback_handle_ = NULL; | 260 playback_handle_ = NULL; |
| 260 | 261 |
| 261 // Release the buffer. | 262 // Release the buffer. |
| 262 buffer_.reset(); | 263 buffer_.reset(); |
| 263 | 264 |
| 264 // Signal anything that might already be scheduled to stop. | 265 // Signal anything that might already be scheduled to stop. |
| 265 stop_stream_ = true; // Not necessary in production, but unit tests | 266 stop_stream_ = true; // Not necessary in production, but unit tests |
| 266 // uses the flag to verify that stream was closed. | 267 // uses the flag to verify that stream was closed. |
| 267 } | 268 } |
| 268 | 269 |
| 269 weak_factory_.InvalidateWeakPtrs(); | 270 weak_factory_.InvalidateWeakPtrs(); |
| 270 | 271 |
| 271 // Signal to the manager that we're closed and can be removed. | 272 // Signal to the manager that we're closed and can be removed. |
| 272 // Should be last call in the method as it deletes "this". | 273 // Should be last call in the method as it deletes "this". |
| 273 manager_->ReleaseOutputStream(this); | 274 manager_->ReleaseOutputStream(this); |
| 274 } | 275 } |
| 275 | 276 |
| 276 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { | 277 void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) { |
| 277 DCHECK(IsOnAudioThread()); | 278 DCHECK(CalledOnValidThread()); |
| 278 | 279 |
| 279 CHECK(callback); | 280 CHECK(callback); |
| 280 | 281 |
| 281 if (stop_stream_) | 282 if (stop_stream_) |
| 282 return; | 283 return; |
| 283 | 284 |
| 284 // Only post the task if we can enter the playing state. | 285 // Only post the task if we can enter the playing state. |
| 285 if (TransitionTo(kIsPlaying) != kIsPlaying) | 286 if (TransitionTo(kIsPlaying) != kIsPlaying) |
| 286 return; | 287 return; |
| 287 | 288 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 316 memset(silent_packet->writable_data(), 0, silent_packet->data_size()); | 317 memset(silent_packet->writable_data(), 0, silent_packet->data_size()); |
| 317 buffer_->Append(silent_packet); | 318 buffer_->Append(silent_packet); |
| 318 WritePacket(); | 319 WritePacket(); |
| 319 | 320 |
| 320 // Start the callback chain. | 321 // Start the callback chain. |
| 321 set_source_callback(callback); | 322 set_source_callback(callback); |
| 322 WriteTask(); | 323 WriteTask(); |
| 323 } | 324 } |
| 324 | 325 |
| 325 void AlsaPcmOutputStream::Stop() { | 326 void AlsaPcmOutputStream::Stop() { |
| 326 DCHECK(IsOnAudioThread()); | 327 DCHECK(CalledOnValidThread()); |
| 327 | 328 |
| 328 // Reset the callback, so that it is not called anymore. | 329 // Reset the callback, so that it is not called anymore. |
| 329 set_source_callback(NULL); | 330 set_source_callback(NULL); |
| 330 weak_factory_.InvalidateWeakPtrs(); | 331 weak_factory_.InvalidateWeakPtrs(); |
| 331 | 332 |
| 332 TransitionTo(kIsStopped); | 333 TransitionTo(kIsStopped); |
| 333 } | 334 } |
| 334 | 335 |
| 335 void AlsaPcmOutputStream::SetVolume(double volume) { | 336 void AlsaPcmOutputStream::SetVolume(double volume) { |
| 336 DCHECK(IsOnAudioThread()); | 337 DCHECK(CalledOnValidThread()); |
| 337 | 338 |
| 338 volume_ = static_cast<float>(volume); | 339 volume_ = static_cast<float>(volume); |
| 339 } | 340 } |
| 340 | 341 |
| 341 void AlsaPcmOutputStream::GetVolume(double* volume) { | 342 void AlsaPcmOutputStream::GetVolume(double* volume) { |
| 342 DCHECK(IsOnAudioThread()); | 343 DCHECK(CalledOnValidThread()); |
| 343 | 344 |
| 344 *volume = volume_; | 345 *volume = volume_; |
| 345 } | 346 } |
| 346 | 347 |
| 347 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { | 348 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) { |
| 348 DCHECK(IsOnAudioThread()); | 349 DCHECK(CalledOnValidThread()); |
| 349 | 350 |
| 350 // If stopped, simulate a 0-length packet. | 351 // If stopped, simulate a 0-length packet. |
| 351 if (stop_stream_) { | 352 if (stop_stream_) { |
| 352 buffer_->Clear(); | 353 buffer_->Clear(); |
| 353 *source_exhausted = true; | 354 *source_exhausted = true; |
| 354 return; | 355 return; |
| 355 } | 356 } |
| 356 | 357 |
| 357 *source_exhausted = false; | 358 *source_exhausted = false; |
| 358 | 359 |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 412 packet->set_data_size(packet_size); | 413 packet->set_data_size(packet_size); |
| 413 // Add the packet to the buffer. | 414 // Add the packet to the buffer. |
| 414 buffer_->Append(packet); | 415 buffer_->Append(packet); |
| 415 } else { | 416 } else { |
| 416 *source_exhausted = true; | 417 *source_exhausted = true; |
| 417 } | 418 } |
| 418 } | 419 } |
| 419 } | 420 } |
| 420 | 421 |
| 421 void AlsaPcmOutputStream::WritePacket() { | 422 void AlsaPcmOutputStream::WritePacket() { |
| 422 DCHECK(IsOnAudioThread()); | 423 DCHECK(CalledOnValidThread()); |
| 423 | 424 |
| 424 // If the device is in error, just eat the bytes. | 425 // If the device is in error, just eat the bytes. |
| 425 if (stop_stream_) { | 426 if (stop_stream_) { |
| 426 buffer_->Clear(); | 427 buffer_->Clear(); |
| 427 return; | 428 return; |
| 428 } | 429 } |
| 429 | 430 |
| 430 if (state() != kIsPlaying) | 431 if (state() != kIsPlaying) |
| 431 return; | 432 return; |
| 432 | 433 |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 471 // This ensures that shorter sounds will still play. | 472 // This ensures that shorter sounds will still play. |
| 472 if (playback_handle_ && | 473 if (playback_handle_ && |
| 473 (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && | 474 (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) && |
| 474 GetCurrentDelay() > 0) { | 475 GetCurrentDelay() > 0) { |
| 475 wrapper_->PcmStart(playback_handle_); | 476 wrapper_->PcmStart(playback_handle_); |
| 476 } | 477 } |
| 477 } | 478 } |
| 478 } | 479 } |
| 479 | 480 |
| 480 void AlsaPcmOutputStream::WriteTask() { | 481 void AlsaPcmOutputStream::WriteTask() { |
| 481 DCHECK(IsOnAudioThread()); | 482 DCHECK(CalledOnValidThread()); |
| 482 | 483 |
| 483 if (stop_stream_) | 484 if (stop_stream_) |
| 484 return; | 485 return; |
| 485 | 486 |
| 486 if (state() == kIsStopped) | 487 if (state() == kIsStopped) |
| 487 return; | 488 return; |
| 488 | 489 |
| 489 bool source_exhausted; | 490 bool source_exhausted; |
| 490 BufferPacket(&source_exhausted); | 491 BufferPacket(&source_exhausted); |
| 491 WritePacket(); | 492 WritePacket(); |
| 492 | 493 |
| 493 ScheduleNextWrite(source_exhausted); | 494 ScheduleNextWrite(source_exhausted); |
| 494 } | 495 } |
| 495 | 496 |
| 496 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { | 497 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) { |
| 497 DCHECK(IsOnAudioThread()); | 498 DCHECK(CalledOnValidThread()); |
| 498 | 499 |
| 499 if (stop_stream_ || state() != kIsPlaying) | 500 if (stop_stream_ || state() != kIsPlaying) |
| 500 return; | 501 return; |
| 501 | 502 |
| 502 const uint32_t kTargetFramesAvailable = alsa_buffer_frames_ / 2; | 503 const uint32_t kTargetFramesAvailable = alsa_buffer_frames_ / 2; |
| 503 uint32_t available_frames = GetAvailableFrames(); | 504 uint32_t available_frames = GetAvailableFrames(); |
| 504 | 505 |
| 505 base::TimeDelta next_fill_time; | 506 base::TimeDelta next_fill_time; |
| 506 if (buffer_->forward_bytes() && available_frames) { | 507 if (buffer_->forward_bytes() && available_frames) { |
| 507 // If we've got data available and ALSA has room, deliver it immediately. | 508 // If we've got data available and ALSA has room, deliver it immediately. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 520 } else if (!source_exhausted) { | 521 } else if (!source_exhausted) { |
| 521 // The sound card has |kTargetFramesAvailable| or more frames available. | 522 // The sound card has |kTargetFramesAvailable| or more frames available. |
| 522 // Invoke the next write immediately to avoid underrun. | 523 // Invoke the next write immediately to avoid underrun. |
| 523 next_fill_time = base::TimeDelta(); | 524 next_fill_time = base::TimeDelta(); |
| 524 } else { | 525 } else { |
| 525 // The sound card has frames available, but our source is exhausted, so | 526 // The sound card has frames available, but our source is exhausted, so |
| 526 // avoid busy looping by delaying a bit. | 527 // avoid busy looping by delaying a bit. |
| 527 next_fill_time = base::TimeDelta::FromMilliseconds(10); | 528 next_fill_time = base::TimeDelta::FromMilliseconds(10); |
| 528 } | 529 } |
| 529 | 530 |
| 530 message_loop_->PostDelayedTask(FROM_HERE, base::Bind( | 531 task_runner_->PostDelayedTask( |
| 531 &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()), | 532 FROM_HERE, |
| 533 base::Bind(&AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()), |
| 532 next_fill_time); | 534 next_fill_time); |
| 533 } | 535 } |
| 534 | 536 |
| 535 // static | 537 // static |
| 536 base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames, | 538 base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames, |
| 537 double sample_rate) { | 539 double sample_rate) { |
| 538 return base::TimeDelta::FromMicroseconds( | 540 return base::TimeDelta::FromMicroseconds( |
| 539 frames * base::Time::kMicrosecondsPerSecond / sample_rate); | 541 frames * base::Time::kMicrosecondsPerSecond / sample_rate); |
| 540 } | 542 } |
| 541 | 543 |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 620 } | 622 } |
| 621 | 623 |
| 622 if (delay < 0) { | 624 if (delay < 0) { |
| 623 delay = 0; | 625 delay = 0; |
| 624 } | 626 } |
| 625 | 627 |
| 626 return delay; | 628 return delay; |
| 627 } | 629 } |
| 628 | 630 |
| 629 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { | 631 snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { |
| 630 DCHECK(IsOnAudioThread()); | 632 DCHECK(CalledOnValidThread()); |
| 631 | 633 |
| 632 if (stop_stream_) | 634 if (stop_stream_) |
| 633 return 0; | 635 return 0; |
| 634 | 636 |
| 635 // Find the number of frames queued in the sound device. | 637 // Find the number of frames queued in the sound device. |
| 636 snd_pcm_sframes_t available_frames = | 638 snd_pcm_sframes_t available_frames = |
| 637 wrapper_->PcmAvailUpdate(playback_handle_); | 639 wrapper_->PcmAvailUpdate(playback_handle_); |
| 638 if (available_frames < 0) { | 640 if (available_frames < 0) { |
| 639 available_frames = wrapper_->PcmRecover(playback_handle_, | 641 available_frames = wrapper_->PcmRecover(playback_handle_, |
| 640 available_frames, | 642 available_frames, |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 753 return to == kIsClosed || to == kInError; | 755 return to == kIsClosed || to == kInError; |
| 754 | 756 |
| 755 case kIsClosed: | 757 case kIsClosed: |
| 756 default: | 758 default: |
| 757 return false; | 759 return false; |
| 758 } | 760 } |
| 759 } | 761 } |
| 760 | 762 |
| 761 AlsaPcmOutputStream::InternalState | 763 AlsaPcmOutputStream::InternalState |
| 762 AlsaPcmOutputStream::TransitionTo(InternalState to) { | 764 AlsaPcmOutputStream::TransitionTo(InternalState to) { |
| 763 DCHECK(IsOnAudioThread()); | 765 DCHECK(CalledOnValidThread()); |
| 764 | 766 |
| 765 if (!CanTransitionTo(to)) { | 767 if (!CanTransitionTo(to)) { |
| 766 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; | 768 NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to; |
| 767 state_ = kInError; | 769 state_ = kInError; |
| 768 } else { | 770 } else { |
| 769 state_ = to; | 771 state_ = to; |
| 770 } | 772 } |
| 771 return state_; | 773 return state_; |
| 772 } | 774 } |
| 773 | 775 |
| 774 AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { | 776 AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() { |
| 775 return state_; | 777 return state_; |
| 776 } | 778 } |
| 777 | 779 |
| 778 bool AlsaPcmOutputStream::IsOnAudioThread() const { | |
| 779 return message_loop_ && message_loop_ == base::MessageLoop::current(); | |
| 780 } | |
| 781 | |
| 782 int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus, | 780 int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus, |
| 783 uint32_t total_bytes_delay) { | 781 uint32_t total_bytes_delay) { |
| 784 TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback"); | 782 TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback"); |
| 785 | 783 |
| 786 if (source_callback_) | 784 if (source_callback_) |
| 787 return source_callback_->OnMoreData(audio_bus, total_bytes_delay, 0); | 785 return source_callback_->OnMoreData(audio_bus, total_bytes_delay, 0); |
| 788 | 786 |
| 789 return 0; | 787 return 0; |
| 790 } | 788 } |
| 791 | 789 |
| 792 void AlsaPcmOutputStream::RunErrorCallback(int code) { | 790 void AlsaPcmOutputStream::RunErrorCallback(int code) { |
| 793 if (source_callback_) | 791 if (source_callback_) |
| 794 source_callback_->OnError(this); | 792 source_callback_->OnError(this); |
| 795 } | 793 } |
| 796 | 794 |
| 797 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to | 795 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to |
| 798 // release ownership of the currently registered callback. | 796 // release ownership of the currently registered callback. |
| 799 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { | 797 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) { |
| 800 DCHECK(IsOnAudioThread()); | 798 DCHECK(CalledOnValidThread()); |
| 801 source_callback_ = callback; | 799 source_callback_ = callback; |
| 802 } | 800 } |
| 803 | 801 |
| 804 } // namespace media | 802 } // namespace media |
| OLD | NEW |