OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/filters/audio_renderer_impl.h" | 5 #include "media/filters/audio_renderer_impl.h" |
6 | 6 |
7 #include <math.h> | 7 #include <math.h> |
8 | 8 |
9 #include <algorithm> | 9 #include <algorithm> |
10 | 10 |
11 #include "base/bind.h" | 11 #include "base/bind.h" |
12 #include "base/callback.h" | 12 #include "base/callback.h" |
13 #include "base/callback_helpers.h" | 13 #include "base/callback_helpers.h" |
14 #include "base/command_line.h" | 14 #include "base/command_line.h" |
15 #include "base/logging.h" | 15 #include "base/logging.h" |
16 #include "base/message_loop_proxy.h" | |
16 #include "media/audio/audio_util.h" | 17 #include "media/audio/audio_util.h" |
18 #include "media/base/bind_to_loop.h" | |
17 #include "media/base/demuxer_stream.h" | 19 #include "media/base/demuxer_stream.h" |
18 #include "media/base/media_switches.h" | 20 #include "media/base/media_switches.h" |
19 | 21 |
20 namespace media { | 22 namespace media { |
21 | 23 |
22 AudioRendererImpl::AudioRendererImpl(media::AudioRendererSink* sink) | 24 AudioRendererImpl::AudioRendererImpl(media::AudioRendererSink* sink) |
23 : state_(kUninitialized), | 25 : state_(kUninitialized), |
24 pending_read_(false), | 26 pending_read_(false), |
25 received_end_of_stream_(false), | 27 received_end_of_stream_(false), |
26 rendered_end_of_stream_(false), | 28 rendered_end_of_stream_(false), |
27 audio_time_buffered_(kNoTimestamp()), | 29 audio_time_buffered_(kNoTimestamp()), |
28 current_time_(kNoTimestamp()), | 30 current_time_(kNoTimestamp()), |
29 bytes_per_frame_(0), | 31 bytes_per_frame_(0), |
30 stopped_(false), | 32 stopped_(false), |
31 sink_(sink), | 33 sink_(sink), |
32 underflow_disabled_(false), | 34 underflow_disabled_(false), |
33 preroll_aborted_(false) { | 35 preroll_aborted_(false) { |
36 // We're created on the render thread, but this thread checker is for another. | |
37 pipeline_thread_checker_.DetachFromThread(); | |
34 } | 38 } |
35 | 39 |
36 void AudioRendererImpl::Play(const base::Closure& callback) { | 40 void AudioRendererImpl::Play(const base::Closure& callback) { |
41 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
37 { | 42 { |
38 base::AutoLock auto_lock(lock_); | 43 base::AutoLock auto_lock(lock_); |
39 DCHECK_EQ(kPaused, state_); | 44 DCHECK_EQ(kPaused, state_); |
40 state_ = kPlaying; | 45 state_ = kPlaying; |
41 callback.Run(); | 46 callback.Run(); |
42 } | 47 } |
43 | 48 |
44 if (stopped_) | 49 if (stopped_) |
45 return; | 50 return; |
46 | 51 |
47 if (GetPlaybackRate() != 0.0f) { | 52 if (GetPlaybackRate() != 0.0f) { |
48 DoPlay(); | 53 DoPlay(); |
49 } else { | 54 } else { |
50 DoPause(); | 55 DoPause(); |
51 } | 56 } |
52 } | 57 } |
53 | 58 |
54 void AudioRendererImpl::DoPlay() { | 59 void AudioRendererImpl::DoPlay() { |
60 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
55 earliest_end_time_ = base::Time::Now(); | 61 earliest_end_time_ = base::Time::Now(); |
56 sink_->Play(); | 62 sink_->Play(); |
57 } | 63 } |
58 | 64 |
59 void AudioRendererImpl::Pause(const base::Closure& callback) { | 65 void AudioRendererImpl::Pause(const base::Closure& callback) { |
66 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
60 { | 67 { |
61 base::AutoLock auto_lock(lock_); | 68 base::AutoLock auto_lock(lock_); |
62 DCHECK(state_ == kPlaying || state_ == kUnderflow || | 69 DCHECK(state_ == kPlaying || state_ == kUnderflow || |
63 state_ == kRebuffering); | 70 state_ == kRebuffering); |
64 pause_cb_ = callback; | 71 pause_cb_ = callback; |
65 state_ = kPaused; | 72 state_ = kPaused; |
66 | 73 |
67 // Pause only when we've completed our pending read. | 74 // Pause only when we've completed our pending read. |
68 if (!pending_read_) | 75 if (!pending_read_) |
69 base::ResetAndReturn(&pause_cb_).Run(); | 76 base::ResetAndReturn(&pause_cb_).Run(); |
70 } | 77 } |
71 | 78 |
72 if (stopped_) | 79 if (stopped_) |
73 return; | 80 return; |
74 | 81 |
75 DoPause(); | 82 DoPause(); |
76 } | 83 } |
77 | 84 |
78 void AudioRendererImpl::DoPause() { | 85 void AudioRendererImpl::DoPause() { |
86 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
79 sink_->Pause(false); | 87 sink_->Pause(false); |
80 } | 88 } |
81 | 89 |
82 void AudioRendererImpl::Flush(const base::Closure& callback) { | 90 void AudioRendererImpl::Flush(const base::Closure& callback) { |
91 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
83 decoder_->Reset(callback); | 92 decoder_->Reset(callback); |
84 } | 93 } |
85 | 94 |
86 void AudioRendererImpl::Stop(const base::Closure& callback) { | 95 void AudioRendererImpl::Stop(const base::Closure& callback) { |
96 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
87 DCHECK(!callback.is_null()); | 97 DCHECK(!callback.is_null()); |
88 | 98 |
99 if (!stopped_) { | |
100 sink_->Stop(); | |
101 stopped_ = true; | |
102 } | |
103 | |
89 { | 104 { |
90 base::AutoLock auto_lock(lock_); | 105 base::AutoLock auto_lock(lock_); |
91 if (!stopped_) { | |
92 sink_->Stop(); | |
93 stopped_ = true; | |
94 } | |
95 | |
96 state_ = kStopped; | 106 state_ = kStopped; |
97 algorithm_.reset(NULL); | 107 algorithm_.reset(NULL); |
98 init_cb_.Reset(); | 108 init_cb_.Reset(); |
99 underflow_cb_.Reset(); | 109 underflow_cb_.Reset(); |
100 time_cb_.Reset(); | 110 time_cb_.Reset(); |
101 } | 111 } |
102 | 112 |
103 callback.Run(); | 113 callback.Run(); |
104 } | 114 } |
105 | 115 |
106 void AudioRendererImpl::Preroll(base::TimeDelta time, | 116 void AudioRendererImpl::Preroll(base::TimeDelta time, |
107 const PipelineStatusCB& cb) { | 117 const PipelineStatusCB& cb) { |
108 base::AutoLock auto_lock(lock_); | 118 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); |
109 DCHECK_EQ(kPaused, state_); | |
110 DCHECK(!pending_read_) << "Pending read must complete before seeking"; | |
111 DCHECK(pause_cb_.is_null()); | |
112 DCHECK(preroll_cb_.is_null()); | |
113 state_ = kPrerolling; | |
114 preroll_cb_ = cb; | |
115 preroll_timestamp_ = time; | |
116 | |
117 // Throw away everything and schedule our reads. | |
118 audio_time_buffered_ = kNoTimestamp(); | |
119 current_time_ = kNoTimestamp(); | |
120 received_end_of_stream_ = false; | |
121 rendered_end_of_stream_ = false; | |
122 preroll_aborted_ = false; | |
123 | |
124 // |algorithm_| will request more reads. | |
125 algorithm_->FlushBuffers(); | |
126 | |
127 if (stopped_) | 119 if (stopped_) |
128 return; | 120 return; |
129 | 121 |
122 { | |
123 base::AutoLock auto_lock(lock_); | |
124 DCHECK_EQ(kPaused, state_); | |
125 DCHECK(!pending_read_) << "Pending read must complete before seeking"; | |
126 DCHECK(pause_cb_.is_null()); | |
127 DCHECK(preroll_cb_.is_null()); | |
128 state_ = kPrerolling; | |
129 preroll_cb_ = cb; | |
130 preroll_timestamp_ = time; | |
131 | |
132 // Throw away everything and schedule our reads. | |
133 audio_time_buffered_ = kNoTimestamp(); | |
134 current_time_ = kNoTimestamp(); | |
135 received_end_of_stream_ = false; | |
136 rendered_end_of_stream_ = false; | |
137 preroll_aborted_ = false; | |
138 | |
139 // |algorithm_| will request more reads. | |
140 algorithm_->FlushBuffers(); | |
141 } | |
142 | |
130 // Pause and flush the stream when we preroll to a new location. | 143 // Pause and flush the stream when we preroll to a new location. |
131 earliest_end_time_ = base::Time::Now(); | 144 earliest_end_time_ = base::Time::Now(); |
132 sink_->Pause(true); | 145 sink_->Pause(true); |
133 } | 146 } |
134 | 147 |
135 void AudioRendererImpl::Initialize(const scoped_refptr<DemuxerStream>& stream, | 148 void AudioRendererImpl::Initialize(const scoped_refptr<DemuxerStream>& stream, |
136 const AudioDecoderList& decoders, | 149 const AudioDecoderList& decoders, |
137 const PipelineStatusCB& init_cb, | 150 const PipelineStatusCB& init_cb, |
138 const StatisticsCB& statistics_cb, | 151 const StatisticsCB& statistics_cb, |
139 const base::Closure& underflow_cb, | 152 const base::Closure& underflow_cb, |
140 const TimeCB& time_cb, | 153 const TimeCB& time_cb, |
141 const base::Closure& ended_cb, | 154 const base::Closure& ended_cb, |
142 const base::Closure& disabled_cb, | 155 const base::Closure& disabled_cb, |
143 const PipelineStatusCB& error_cb) { | 156 const PipelineStatusCB& error_cb) { |
144 base::AutoLock auto_lock(lock_); | 157 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); |
145 DCHECK(stream); | 158 DCHECK(stream); |
146 DCHECK(!decoders.empty()); | 159 DCHECK(!decoders.empty()); |
147 DCHECK_EQ(stream->type(), DemuxerStream::AUDIO); | 160 DCHECK_EQ(stream->type(), DemuxerStream::AUDIO); |
148 DCHECK(!init_cb.is_null()); | 161 DCHECK(!init_cb.is_null()); |
149 DCHECK(!statistics_cb.is_null()); | 162 DCHECK(!statistics_cb.is_null()); |
150 DCHECK(!underflow_cb.is_null()); | 163 DCHECK(!underflow_cb.is_null()); |
151 DCHECK(!time_cb.is_null()); | 164 DCHECK(!time_cb.is_null()); |
152 DCHECK(!ended_cb.is_null()); | 165 DCHECK(!ended_cb.is_null()); |
153 DCHECK(!disabled_cb.is_null()); | 166 DCHECK(!disabled_cb.is_null()); |
154 DCHECK(!error_cb.is_null()); | 167 DCHECK(!error_cb.is_null()); |
155 DCHECK_EQ(kUninitialized, state_); | 168 DCHECK_EQ(kUninitialized, state_); |
156 | 169 |
157 init_cb_ = init_cb; | 170 init_cb_ = init_cb; |
158 statistics_cb_ = statistics_cb; | 171 statistics_cb_ = statistics_cb; |
159 underflow_cb_ = underflow_cb; | 172 underflow_cb_ = underflow_cb; |
160 time_cb_ = time_cb; | 173 time_cb_ = time_cb; |
161 ended_cb_ = ended_cb; | 174 ended_cb_ = ended_cb; |
162 disabled_cb_ = disabled_cb; | 175 disabled_cb_ = disabled_cb; |
163 error_cb_ = error_cb; | 176 error_cb_ = error_cb; |
164 | 177 |
165 scoped_ptr<AudioDecoderList> decoder_list(new AudioDecoderList(decoders)); | 178 scoped_ptr<AudioDecoderList> decoder_list(new AudioDecoderList(decoders)); |
166 InitializeNextDecoder(stream, decoder_list.Pass()); | 179 InitializeNextDecoder(stream, decoder_list.Pass()); |
167 } | 180 } |
168 | 181 |
169 void AudioRendererImpl::InitializeNextDecoder( | 182 void AudioRendererImpl::InitializeNextDecoder( |
170 const scoped_refptr<DemuxerStream>& demuxer_stream, | 183 const scoped_refptr<DemuxerStream>& demuxer_stream, |
171 scoped_ptr<AudioDecoderList> decoders) { | 184 scoped_ptr<AudioDecoderList> decoders) { |
172 lock_.AssertAcquired(); | 185 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); |
173 DCHECK(!decoders->empty()); | 186 DCHECK(!decoders->empty()); |
174 | 187 |
175 scoped_refptr<AudioDecoder> decoder = decoders->front(); | 188 scoped_refptr<AudioDecoder> decoder = decoders->front(); |
176 decoders->pop_front(); | 189 decoders->pop_front(); |
177 | 190 |
178 DCHECK(decoder); | 191 DCHECK(decoder); |
179 decoder_ = decoder; | 192 decoder_ = decoder; |
180 | |
181 base::AutoUnlock auto_unlock(lock_); | |
182 decoder->Initialize( | 193 decoder->Initialize( |
183 demuxer_stream, | 194 demuxer_stream, BindToLoop(base::MessageLoopProxy::current(), base::Bind( |
184 base::Bind(&AudioRendererImpl::OnDecoderInitDone, this, | 195 &AudioRendererImpl::OnDecoderInitDone, this, demuxer_stream, |
185 demuxer_stream, | 196 base::Passed(&decoders))), |
186 base::Passed(&decoders)), | |
187 statistics_cb_); | 197 statistics_cb_); |
188 } | 198 } |
189 | 199 |
190 void AudioRendererImpl::OnDecoderInitDone( | 200 void AudioRendererImpl::OnDecoderInitDone( |
191 const scoped_refptr<DemuxerStream>& demuxer_stream, | 201 const scoped_refptr<DemuxerStream>& demuxer_stream, |
192 scoped_ptr<AudioDecoderList> decoders, | 202 scoped_ptr<AudioDecoderList> decoders, |
193 PipelineStatus status) { | 203 PipelineStatus status) { |
194 base::AutoLock auto_lock(lock_); | 204 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); |
195 | 205 |
196 if (state_ == kStopped) { | 206 if (state_ == kStopped) { |
197 DCHECK(stopped_); | 207 DCHECK(stopped_); |
198 return; | 208 return; |
199 } | 209 } |
200 | 210 |
201 if (!decoders->empty() && status == DECODER_ERROR_NOT_SUPPORTED) { | 211 if (!decoders->empty() && status == DECODER_ERROR_NOT_SUPPORTED) { |
202 InitializeNextDecoder(demuxer_stream, decoders.Pass()); | 212 InitializeNextDecoder(demuxer_stream, decoders.Pass()); |
203 return; | 213 return; |
204 } | 214 } |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
260 // purposes it's okay that this buffer size might lead to jitter since it's | 270 // purposes it's okay that this buffer size might lead to jitter since it's |
261 // not a multiple of the hardware buffer size. | 271 // not a multiple of the hardware buffer size. |
262 format = AudioParameters::AUDIO_PCM_LOW_LATENCY; | 272 format = AudioParameters::AUDIO_PCM_LOW_LATENCY; |
263 buffer_size = 2048; | 273 buffer_size = 2048; |
264 } | 274 } |
265 #endif | 275 #endif |
266 | 276 |
267 audio_parameters_ = AudioParameters( | 277 audio_parameters_ = AudioParameters( |
268 format, channel_layout, sample_rate, bits_per_channel, buffer_size); | 278 format, channel_layout, sample_rate, bits_per_channel, buffer_size); |
269 | 279 |
280 state_ = kPaused; | |
281 | |
270 sink_->Initialize(audio_parameters_, this); | 282 sink_->Initialize(audio_parameters_, this); |
271 sink_->Start(); | 283 sink_->Start(); |
272 | 284 |
273 state_ = kPaused; | |
274 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); | 285 base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); |
275 } | 286 } |
276 | 287 |
277 void AudioRendererImpl::ResumeAfterUnderflow(bool buffer_more_audio) { | 288 void AudioRendererImpl::ResumeAfterUnderflow(bool buffer_more_audio) { |
289 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
278 base::AutoLock auto_lock(lock_); | 290 base::AutoLock auto_lock(lock_); |
279 if (state_ == kUnderflow) { | 291 if (state_ == kUnderflow) { |
280 // The "&& preroll_aborted_" is a hack. If preroll is aborted, then we | 292 // The "&& preroll_aborted_" is a hack. If preroll is aborted, then we |
281 // shouldn't even reach the kUnderflow state to begin with. But for now | 293 // shouldn't even reach the kUnderflow state to begin with. But for now |
282 // we're just making sure that the audio buffer capacity (i.e. the | 294 // we're just making sure that the audio buffer capacity (i.e. the |
283 // number of bytes that need to be buffered for preroll to complete) | 295 // number of bytes that need to be buffered for preroll to complete) |
284 // does not increase due to an aborted preroll. | 296 // does not increase due to an aborted preroll. |
285 // TODO(vrk): Fix this bug correctly! (crbug.com/151352) | 297 // TODO(vrk): Fix this bug correctly! (crbug.com/151352) |
286 if (buffer_more_audio && !preroll_aborted_) | 298 if (buffer_more_audio && !preroll_aborted_) |
287 algorithm_->IncreaseQueueCapacity(); | 299 algorithm_->IncreaseQueueCapacity(); |
288 | 300 |
289 state_ = kRebuffering; | 301 state_ = kRebuffering; |
290 } | 302 } |
291 } | 303 } |
292 | 304 |
293 void AudioRendererImpl::SetVolume(float volume) { | 305 void AudioRendererImpl::SetVolume(float volume) { |
306 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
294 if (stopped_) | 307 if (stopped_) |
295 return; | 308 return; |
296 sink_->SetVolume(volume); | 309 sink_->SetVolume(volume); |
297 } | 310 } |
298 | 311 |
299 AudioRendererImpl::~AudioRendererImpl() { | 312 AudioRendererImpl::~AudioRendererImpl() { |
300 // Stop() should have been called and |algorithm_| should have been destroyed. | 313 // Stop() should have been called and |algorithm_| should have been destroyed. |
301 DCHECK(state_ == kUninitialized || state_ == kStopped); | 314 DCHECK(state_ == kUninitialized || state_ == kStopped); |
302 DCHECK(!algorithm_.get()); | 315 DCHECK(!algorithm_.get()); |
303 } | 316 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
369 | 382 |
370 void AudioRendererImpl::ScheduleRead_Locked() { | 383 void AudioRendererImpl::ScheduleRead_Locked() { |
371 lock_.AssertAcquired(); | 384 lock_.AssertAcquired(); |
372 if (pending_read_ || state_ == kPaused) | 385 if (pending_read_ || state_ == kPaused) |
373 return; | 386 return; |
374 pending_read_ = true; | 387 pending_read_ = true; |
375 decoder_->Read(base::Bind(&AudioRendererImpl::DecodedAudioReady, this)); | 388 decoder_->Read(base::Bind(&AudioRendererImpl::DecodedAudioReady, this)); |
376 } | 389 } |
377 | 390 |
378 void AudioRendererImpl::SetPlaybackRate(float playback_rate) { | 391 void AudioRendererImpl::SetPlaybackRate(float playback_rate) { |
392 DCHECK(pipeline_thread_checker_.CalledOnValidThread()); | |
379 DCHECK_LE(0.0f, playback_rate); | 393 DCHECK_LE(0.0f, playback_rate); |
380 | 394 |
381 if (!stopped_) { | 395 if (!stopped_) { |
382 // We have two cases here: | 396 // We have two cases here: |
383 // Play: GetPlaybackRate() == 0.0 && playback_rate != 0.0 | 397 // Play: GetPlaybackRate() == 0.0 && playback_rate != 0.0 |
384 // Pause: GetPlaybackRate() != 0.0 && playback_rate == 0.0 | 398 // Pause: GetPlaybackRate() != 0.0 && playback_rate == 0.0 |
385 if (GetPlaybackRate() == 0.0f && playback_rate != 0.0f) { | 399 if (GetPlaybackRate() == 0.0f && playback_rate != 0.0f) { |
386 DoPlay(); | 400 DoPlay(); |
387 } else if (GetPlaybackRate() != 0.0f && playback_rate == 0.0f) { | 401 } else if (GetPlaybackRate() != 0.0f && playback_rate == 0.0f) { |
388 // Pause is easy, we can always pause. | 402 // Pause is easy, we can always pause. |
(...skipping 11 matching lines...) Expand all Loading... | |
400 } | 414 } |
401 | 415 |
402 bool AudioRendererImpl::IsBeforePrerollTime( | 416 bool AudioRendererImpl::IsBeforePrerollTime( |
403 const scoped_refptr<Buffer>& buffer) { | 417 const scoped_refptr<Buffer>& buffer) { |
404 return (state_ == kPrerolling) && buffer && !buffer->IsEndOfStream() && | 418 return (state_ == kPrerolling) && buffer && !buffer->IsEndOfStream() && |
405 (buffer->GetTimestamp() + buffer->GetDuration()) < preroll_timestamp_; | 419 (buffer->GetTimestamp() + buffer->GetDuration()) < preroll_timestamp_; |
406 } | 420 } |
407 | 421 |
408 int AudioRendererImpl::Render(AudioBus* audio_bus, | 422 int AudioRendererImpl::Render(AudioBus* audio_bus, |
409 int audio_delay_milliseconds) { | 423 int audio_delay_milliseconds) { |
410 if (stopped_ || GetPlaybackRate() == 0.0f) { | 424 if (stopped_ || GetPlaybackRate() == 0.0f) { |
Ami GONE FROM CHROMIUM
2012/11/01 19:57:01
Must not read stopped_ off pipeline thread.
Ami GONE FROM CHROMIUM
2012/11/01 19:57:01
GetPlaybackRate() locks lock_ for its duration, bu
DaleCurtis
2012/11/01 22:02:59
Done.
DaleCurtis
2012/11/01 22:02:59
Fixed. Read once and saved for the duration of Ren
| |
411 // Output silence if stopped. | 425 // Output silence if stopped. |
412 audio_bus->Zero(); | 426 audio_bus->Zero(); |
413 return 0; | 427 return 0; |
414 } | 428 } |
415 | 429 |
416 // Adjust the playback delay. | 430 // Adjust the playback delay. |
417 base::TimeDelta request_delay = | 431 base::TimeDelta request_delay = |
418 base::TimeDelta::FromMilliseconds(audio_delay_milliseconds); | 432 base::TimeDelta::FromMilliseconds(audio_delay_milliseconds); |
419 | 433 |
420 // Finally we need to adjust the delay according to playback rate. | 434 // Finally we need to adjust the delay according to playback rate. |
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
598 case kUnderflow: | 612 case kUnderflow: |
599 case kRebuffering: | 613 case kRebuffering: |
600 case kStopped: | 614 case kStopped: |
601 if (status != PIPELINE_OK) | 615 if (status != PIPELINE_OK) |
602 error_cb_.Run(status); | 616 error_cb_.Run(status); |
603 return; | 617 return; |
604 } | 618 } |
605 } | 619 } |
606 | 620 |
607 } // namespace media | 621 } // namespace media |
OLD | NEW |