Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: media/base/pipeline_impl.cc

Issue 18546: Implementation of Pipeline and FilterHost interfaces. This is a large change... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « media/base/pipeline_impl.h ('k') | media/base/pipeline_impl_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2006-2009 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 "base/compiler_specific.h"
6 #include "media/base/filter_host_impl.h"
7 #include "media/base/media_format.h"
5 #include "media/base/pipeline_impl.h" 8 #include "media/base/pipeline_impl.h"
6 9
7 namespace media { 10 namespace media {
8 11
9 PipelineImpl::PipelineImpl() { 12 PipelineImpl::PipelineImpl() {
10 // TODO(ralphl): implement PipelineImpl constructor. 13 ResetState();
11 NOTIMPLEMENTED();
12 } 14 }
13 15
14 PipelineImpl::~PipelineImpl() { 16 PipelineImpl::~PipelineImpl() {
15 // TODO(ralphl): implement PipelineImpl destructor. 17 Stop();
16 NOTIMPLEMENTED();
17 } 18 }
18 19
19 bool PipelineImpl::IsInitialized() const { 20 bool PipelineImpl::IsInitialized() const {
20 // TODO(ralphl): implement IsInitialized. 21 return initialized_;
21 NOTIMPLEMENTED(); 22 }
23
24 base::TimeDelta PipelineImpl::GetDuration() const {
25 return duration_;
26 }
27
28 base::TimeDelta PipelineImpl::GetBufferedTime() const {
29 return buffered_time_;
30 }
31
32 int64 PipelineImpl::GetTotalBytes() const {
33 return total_bytes_;
34 }
35
36 int64 PipelineImpl::GetBufferedBytes() const {
37 return buffered_bytes_;
38 }
39
40 void PipelineImpl::GetVideoSize(size_t* width_out, size_t* height_out) const {
41 DCHECK(width_out);
42 DCHECK(height_out);
43 AutoLock auto_lock(const_cast<Lock&>(video_size_access_lock_));
44 *width_out = video_width_;
45 *height_out = video_height_;
46 }
47
48 float PipelineImpl::GetVolume() const {
49 return volume_;
50 }
51
52 float PipelineImpl::GetPlaybackRate() const {
53 return playback_rate_;
54 }
55
56 base::TimeDelta PipelineImpl::GetTime() const {
57 return time_;
58 }
59
60 PipelineError PipelineImpl::GetError() const {
61 return error_;
62 }
63
64 // Creates the PipelineThread and calls it's start method.
65 bool PipelineImpl::Start(FilterFactory* factory,
66 const std::string& url,
67 Callback1<bool>::Type* init_complete_callback) {
68 DCHECK(!pipeline_thread_);
69 DCHECK(factory);
70 DCHECK(!initialized_);
71 if (!pipeline_thread_ && factory) {
72 pipeline_thread_ = new PipelineThread(this);
73 if (pipeline_thread_) {
74 // TODO(ralphl): Does the callback get copied by these fancy templates?
75 // if so, then do I want to always delete it here???
76 if (pipeline_thread_->Start(factory, url, init_complete_callback)) {
77 return true;
78 }
79 pipeline_thread_ = NULL; // Releases reference to destroy thread
80 }
81 }
82 delete init_complete_callback;
22 return false; 83 return false;
23 } 84 }
24 85
25 int64 PipelineImpl::GetDuration() const { 86 // Stop the PipelineThread and return to a state identical to that of a newly
26 // TODO(ralphl): implement GetDuration. 87 // created PipelineImpl object.
27 NOTIMPLEMENTED(); 88 void PipelineImpl::Stop() {
28 return 0; 89 if (pipeline_thread_) {
29 } 90 pipeline_thread_->Stop();
30 91 }
31 int64 PipelineImpl::GetBufferedTime() const { 92 ResetState();
32 // TODO(ralphl): implement GetBufferedTime. 93 }
33 NOTIMPLEMENTED(); 94
34 return 0; 95
35 } 96
36 97 void PipelineImpl::SetPlaybackRate(float rate) {
37 int64 PipelineImpl::GetTotalBytes() const { 98 if (OkToCallThread() && rate >= 0.0f) {
38 // TODO(ralphl): implement GetTotalBytes. 99 pipeline_thread_->SetPlaybackRate(rate);
39 NOTIMPLEMENTED(); 100 } else {
40 return 0; 101 NOTREACHED();
41 } 102 }
42 103 }
43 int64 PipelineImpl::GetBufferedBytes() const { 104
44 // TODO(ralphl): implement GetBufferedBytes. 105 void PipelineImpl::Seek(base::TimeDelta time) {
45 NOTIMPLEMENTED(); 106 if (OkToCallThread()) {
46 return 0; 107 pipeline_thread_->Seek(time);
47 } 108 } else {
48 109 NOTREACHED();
49 void PipelineImpl::GetVideoSize(size_t* width_out, size_t* height_out) const { 110 }
50 // TODO(ralphl): implement GetVideoSize. 111 }
51 NOTIMPLEMENTED(); 112
52 width_out = 0; 113 void PipelineImpl::SetVolume(float volume) {
53 height_out = 0; 114 if (OkToCallThread() && volume >= 0.0f && volume <= 1.0f) {
54 } 115 pipeline_thread_->SetVolume(volume);
55 116 } else {
56 float PipelineImpl::GetVolume() const { 117 NOTREACHED();
57 // TODO(ralphl): implement GetVolume. 118 }
58 NOTIMPLEMENTED(); 119 }
59 return 0; 120
60 } 121 void PipelineImpl::ResetState() {
61 122 pipeline_thread_ = NULL;
62 float PipelineImpl::GetPlaybackRate() const { 123 initialized_ = false;
63 // TODO(ralphl): implement GetPlaybackRate. 124 duration_ = base::TimeDelta();
64 NOTIMPLEMENTED(); 125 buffered_time_ = base::TimeDelta();
65 return 0; 126 buffered_bytes_ = 0;
66 } 127 total_bytes_ = 0;
67 128 video_width_ = 0;
68 int64 PipelineImpl::GetTime() const { 129 video_height_ = 0;
69 // TODO(ralphl): implement GetTime. 130 volume_ = 0.0f;
70 NOTIMPLEMENTED(); 131 playback_rate_ = 0.0f;
71 return 0; 132 time_ = base::TimeDelta();
72 } 133 error_ = PIPELINE_OK;
73 134 }
74 PipelineError PipelineImpl::GetError() const { 135
75 // TODO(ralphl): implement GetError. 136 void PipelineImpl::SetVideoSize(size_t width, size_t height) {
76 NOTIMPLEMENTED(); 137 AutoLock auto_lock(video_size_access_lock_);
77 return PIPELINE_ERROR_INITIALIZATION_FAILED; 138 width = width;
78 } 139 height = height;
79 140 }
80 bool PipelineImpl::Start(FilterFactory* filter_factory, 141
81 const std::string& uri, 142 //-----------------------------------------------------------------------------
82 Callback1<bool>::Type* init_complete_callback) { 143
83 // TODO(ralphl): implement Start. 144 PipelineThread::PipelineThread(PipelineImpl* pipeline)
84 NOTIMPLEMENTED(); 145 : pipeline_(pipeline),
146 thread_("PipelineThread"),
147 time_update_callback_scheduled_(false),
148 host_initializing_(NULL) {
149 }
150
151 PipelineThread::~PipelineThread() {
152 Stop();
153 }
154
155 // This method is called on the client's thread. It starts the pipeline's
156 // dedicated thread and posts a task to call the StartTask method on that
157 // thread.
158 bool PipelineThread::Start(FilterFactory* filter_factory,
159 const std::string& url,
160 Callback1<bool>::Type* init_complete_callback) {
161 if (thread_.Start()) {
162 filter_factory->AddRef();
163 PostTask(NewRunnableMethod(this,
164 &PipelineThread::StartTask,
165 filter_factory,
166 url,
167 // TODO(ralphl): what happens to this callback?
168 // is it copied by NewRunnableTask? Just pointer
169 // or is the callback itself copied?
170 init_complete_callback));
171 return true;
172 }
85 return false; 173 return false;
86 } 174 }
87 175
88 void PipelineImpl::Stop() { 176 // Called on the client's thread. If the thread has been started, then posts
89 // TODO(ralphl): implement Stop. 177 // a task to call the StopTask method, then waits until the thread has stopped.
90 NOTIMPLEMENTED(); 178 // There is a critical section that wraps the entire duration of the StartTask
91 } 179 // method. This method waits for that Lock to be released so that we know
92 180 // that the thread is not executing a nested message loop. This way we know
93 bool PipelineImpl::SetPlaybackRate(float rate) { 181 // that that Thread::Stop call will quit the appropriate message loop.
94 // TODO(ralphl): implement SetPlaybackRate. 182 void PipelineThread::Stop() {
95 NOTIMPLEMENTED(); 183 if (thread_.IsRunning()) {
184 PostTask(NewRunnableMethod(this, &PipelineThread::StopTask));
185 AutoLock lock_crit(initialization_lock_);
186 thread_.Stop();
187 }
188 DCHECK(filter_hosts_.empty());
189 }
190
191 // Called on client's thread.
192 void PipelineThread::SetPlaybackRate(float rate) {
193 PostTask(NewRunnableMethod(this, &PipelineThread::SetPlaybackRateTask, rate));
194 }
195
196 // Called on client's thread.
197 void PipelineThread::Seek(base::TimeDelta time) {
198 PostTask(NewRunnableMethod(this, &PipelineThread::SeekTask, time));
199 }
200
201 // Called on client's thread.
202 void PipelineThread::SetVolume(float volume) {
203 PostTask(NewRunnableMethod(this, &PipelineThread::SetVolumeTask, volume));
204 }
205
206 // May be called on any thread, and therefore we always assume the worst
207 // possible race condition. This could, for example, be called from a filter's
208 // thread just as the pipeline thread is exiting the call to the filter's
209 // Initialize() method. Therefore, we make NO assumptions, and post work
210 // in every case, even the trivial one of a thread calling this method from
211 // within it's Initialize method. This means that we will always run a nested
212 // message loop, and the InitializationCompleteTask will Quit that loop
213 // immediately in the trivial case.
214 void PipelineThread::InitializationComplete(FilterHostImpl* host) {
215 DCHECK(host == host_initializing_);
216 PostTask(NewRunnableMethod(this,
217 &PipelineThread::InitializationCompleteTask,
218 host));
219 }
220
221 // Called from any thread. Updates the pipeline time and schedules a task to
222 // call back to filters that have registered a callback for time updates.
223 void PipelineThread::SetTime(base::TimeDelta time) {
224 pipeline()->time_ = time;
225 if (!time_update_callback_scheduled_) {
226 time_update_callback_scheduled_ = true;
227 PostTask(NewRunnableMethod(this, &PipelineThread::SetTimeTask));
228 }
229 }
230
231 // Called from any thread. Sets the pipeline error_ member and schedules a
232 // task to stop all the filters in the pipeline. Note that the thread will
233 // continue to run until the client calls Pipeline::Stop, but nothing will
234 // be processed since filters will not be able to post tasks.
235 void PipelineThread::Error(PipelineError error) {
236 DCHECK(PIPELINE_OK != error);
237 if (PIPELINE_OK == pipeline()->error_) {
238 pipeline()->error_ = error;
239 PostTask(NewRunnableMethod(this, &PipelineThread::StopTask));
240 }
241 }
242
243 // Called from any thread. Used by FilterHostImpl::PostTask method and used
244 // internally.
245 void PipelineThread::PostTask(Task* task) {
246 message_loop()->PostTask(FROM_HERE, task);
247 }
248
249
250 // Main initialization method called on the pipeline thread. This code attempts
251 // to use the specified filter factory to build a pipeline. It starts by
252 // creating a DataSource, connects it to a Demuxer, and then connects the
253 // Demuxer's audio stream to an AudioDecoder which is then connected to an
254 // AudioRenderer. If the media has video, then it connects a VideoDecoder to
255 // the Demuxer's video stream, and then connects the VideoDecoder to a
256 // VideoRenderer. When all required filters have been created and have called
257 // their FilterHost's InitializationComplete method, the pipeline's
258 // initialized_ member is set to true, and, if the client provided an
259 // init_complete_callback, it is called with "true".
260 // If initializatoin fails, the client's callback will still be called, but
261 // the bool parameter passed to it will be false.
262 //
263 // Note that at each step in this process, the initialization of any filter
264 // may require running the pipeline thread's message loop recursively. This is
265 // handled by the CreateFilter method.
266 void PipelineThread::StartTask(FilterFactory* filter_factory,
267 const std::string& url,
268 Callback1<bool>::Type* init_complete_callback) {
269 bool success = true;
270
271 // During the entire StartTask we hold the initialization_lock_ so that
272 // if the client calls the Pipeline::Stop method while we are running a
273 // nested message loop, we can correctly unwind out of it before calling
274 // the Thread::Stop method.
275 AutoLock auto_lock(initialization_lock_);
276
277 // Add ourselves as a destruction observer of the thread's message loop so
278 // we can delete filters at an appropriate time (when all tasks have been
279 // processed and the thread is about to be destroyed).
280 message_loop()->AddDestructionObserver(this);
281 success = CreateDataSource(filter_factory, url);
282 if (success) {
283 success = CreateAndConnect<Demuxer, DataSource>(filter_factory);
284 }
285 if (success) {
286 success = CreateDecoder<AudioDecoder>(filter_factory);
287 }
288 if (success) {
289 success = CreateAndConnect<AudioRenderer, AudioDecoder>(filter_factory);
290 }
291 if (success && HasVideo()) {
292 success = CreateDecoder<VideoDecoder>(filter_factory);
293 if (success) {
294 success = CreateAndConnect<VideoRenderer, VideoDecoder>(filter_factory);
295 }
296 }
297 if (success) {
298 pipeline_->initialized_ = true;
299 } else if (PIPELINE_OK == pipeline_->error_) {
300 Error(PIPELINE_ERROR_INITIALIZATION_FAILED);
301 }
302
303 // No matter what, we're done with the filter factory, and
304 // client callback so get rid of them.
305 filter_factory->Release();
306 if (init_complete_callback) {
307 init_complete_callback->Run(success);
308 delete init_complete_callback;
309 }
310 }
311
312 // This method is called as a result of the client calling Pipeline::Stop() or
313 // as the result of an error condition. If there is no error, then set the
314 // pipeline's error_ member to PIPELINE_STOPPING. We stop the filters in the
315 // reverse order.
316 void PipelineThread::StopTask() {
317 if (PIPELINE_OK == pipeline_->error_) {
318 pipeline_->error_ = PIPELINE_STOPPING;
319 }
320 FilterHostVector::reverse_iterator riter = filter_hosts_.rbegin();
321 while (riter != filter_hosts_.rend()) {
322 (*riter)->Stop();
323 ++riter;
324 }
325 if (host_initializing_) {
326 host_initializing_ = NULL;
327 message_loop()->Quit();
328 }
329 }
330
331 // Task runs as a result of a filter calling InitializationComplete. If for
332 // some reason StopTask has been executed prior to this, the host_initializing_
333 // member will be NULL, and the message loop will have been quit already, so
334 // we don't want to do it again.
335 void PipelineThread::InitializationCompleteTask(FilterHostImpl* host) {
336 if (host == host_initializing_) {
337 host_initializing_ = NULL;
338 message_loop()->Quit();
339 } else {
340 DCHECK(!host_initializing_);
341 }
342 }
343
344 void PipelineThread::SetPlaybackRateTask(float rate) {
345 pipeline_->playback_rate_ = rate;
346 FilterHostVector::iterator iter = filter_hosts_.begin();
347 while (iter != filter_hosts_.end()) {
348 (*iter)->media_filter()->SetPlaybackRate(rate);
349 ++iter;
350 }
351 }
352
353 void PipelineThread::SeekTask(base::TimeDelta time) {
354 FilterHostVector::iterator iter = filter_hosts_.begin();
355 while (iter != filter_hosts_.end()) {
356 (*iter)->media_filter()->Seek(time);
357 ++iter;
358 }
359 }
360
361 void PipelineThread::SetVolumeTask(float volume) {
362 pipeline_->volume_ = volume;
363 AudioRenderer* audio_renderer = GetFilter<AudioRenderer>();
364 if (audio_renderer) {
365 audio_renderer->SetVolume(volume);
366 }
367 }
368
369 void PipelineThread::SetTimeTask() {
370 time_update_callback_scheduled_ = false;
371 FilterHostVector::iterator iter = filter_hosts_.begin();
372 while (iter != filter_hosts_.end()) {
373 (*iter)->RunTimeUpdateCallback(pipeline_->time_);
374 ++iter;
375 }
376 }
377
378 template <class Filter>
379 Filter* PipelineThread::GetFilter() const {
380 Filter* filter = NULL;
381 FilterHostVector::const_iterator iter = filter_hosts_.begin();
382 while (iter != filter_hosts_.end() && NULL == filter) {
383 filter = (*iter)->GetFilter<Filter>();
384 ++iter;
385 }
386 return filter;
387 }
388
389 template <class NewFilter, class Source>
390 bool PipelineThread::CreateFilter(FilterFactory* filter_factory,
391 Source source,
392 const MediaFormat* source_media_format) {
393 NewFilter* new_filter;
394 bool success;
395 success = filter_factory->Create(source_media_format, &new_filter);
396 if (success) {
397 DCHECK(!host_initializing_);
398 host_initializing_ = new FilterHostImpl(this, new_filter);
399 if (!host_initializing_) {
400 success = false;
401 new_filter->AddRef();
402 new_filter->Release();
403 }
404 }
405 if (success) {
406 filter_hosts_.push_back(host_initializing_);
407 new_filter->SetFilterHost(host_initializing_);
408
409 // The filter must return true from initialize and there must still not
410 // be an error or it's not successful.
411 success = (new_filter->Initialize(source) &&
412 PIPELINE_OK == pipeline_->error_);
413 }
414 if (success) {
415 // Now we run the thread's message loop recursively. We want all
416 // pending tasks to be processed, so we set nestable tasks to be allowed
417 // and then run the loop. The only way we exit the loop is as the result
418 // of a call to FilterHost::InitializationComplete, FilterHost::Error, or
419 // Pipeline::Stop. In each of these cases, the corresponding task method
420 // sets host_initializing_ to NULL to signal that the message loop's Quit
421 // method has already been called, and then calls message_loop()->Quit().
422 // The setting of |host_initializing_| to NULL in the task prevents a
423 // subsequent task from accidentally quitting the wrong (non-nested) loop.
424 message_loop()->SetNestableTasksAllowed(true);
425 message_loop()->Run();
426 message_loop()->SetNestableTasksAllowed(false);
427 DCHECK(!host_initializing_);
428
429 // If an error occurred while we were in the nested Run state, then
430 // not successful. When stopping, the |error_| member is set to a value of
431 // PIPELINE_STOPPING so we will exit in that case also with false.
432 success = (PIPELINE_OK == pipeline_->error_);
433 }
434
435 // This could still be set if we never ran the message loop (for example,
436 // if the fiter returned false from it's Initialize method), so make sure
437 // to reset it.
438 host_initializing_ = NULL;
439
440 // If this method fails, but no error set, then indicate a general
441 // initialization failure.
442 if (PIPELINE_OK == pipeline_->error_ && (!success)) {
443 Error(PIPELINE_ERROR_INITIALIZATION_FAILED);
444 }
445 return success;
446 }
447
448 bool PipelineThread::CreateDataSource(FilterFactory* filter_factory,
449 const std::string& url) {
450 MediaFormat url_format;
451 url_format.SetAsString(MediaFormat::kMimeType, mime_type::kURL);
452 url_format.SetAsString(MediaFormat::kURL, url);
453 return CreateFilter<DataSource>(filter_factory, url, &url_format);
454 }
455
456 template <class Decoder>
457 bool PipelineThread::CreateDecoder(FilterFactory* filter_factory) {
458 Demuxer* demuxer = GetFilter<Demuxer>();
459 if (demuxer) {
460 int num_outputs = demuxer->GetNumberOfStreams();
461 for (int i = 0; i < num_outputs; ++i) {
462 DemuxerStream* stream = demuxer->GetStream(i);
463 const MediaFormat* stream_format = stream->GetMediaFormat();
464 if (IsMajorMimeType(stream_format, Decoder::major_mime_type())) {
465 return CreateFilter<Decoder>(filter_factory, stream, stream_format);
466 }
467 }
468 }
96 return false; 469 return false;
97 } 470 }
98 471
99 bool PipelineImpl::Seek(int64 time) { 472 template <class NewFilter, class SourceFilter>
100 // TODO(ralphl): implement Seek. 473 bool PipelineThread::CreateAndConnect(FilterFactory* filter_factory) {
101 NOTIMPLEMENTED(); 474 SourceFilter* source_filter = GetFilter<SourceFilter>();
475 bool success = (source_filter &&
476 CreateFilter<NewFilter>(filter_factory,
477 source_filter,
478 source_filter->GetMediaFormat()));
479 return success;
480 }
481
482 // TODO(ralphl): Consider making this part of the demuxer interface.
483 bool PipelineThread::HasVideo() const {
484 Demuxer* demuxer = GetFilter<Demuxer>();
485 if (demuxer) {
486 int num_outputs = demuxer->GetNumberOfStreams();
487 for (int i = 0; i < num_outputs; ++i) {
488 if (IsMajorMimeType(demuxer->GetStream(i)->GetMediaFormat(),
489 mime_type::kMajorTypeVideo)) {
490 return true;
491 }
492 }
493 }
102 return false; 494 return false;
103 } 495 }
104 496
105 bool PipelineImpl::SetVolume(float volume) { 497 bool PipelineThread::IsMajorMimeType(const MediaFormat* media_format,
106 // TODO(ralphl): implement SetVolume. 498 const std::string& major_mime_type) const {
107 NOTIMPLEMENTED(); 499 std::string value;
500 if (media_format->GetAsString(MediaFormat::kMimeType, &value)) {
501 return (0 == value.compare(0, major_mime_type.length(), major_mime_type));
502 }
108 return false; 503 return false;
109 } 504 }
110 505
506 // Called as a result of destruction of the thread.
507 void PipelineThread::WillDestroyCurrentMessageLoop() {
508 while (!filter_hosts_.empty()) {
509 delete filter_hosts_.back();
510 filter_hosts_.pop_back();
511 }
512 }
513
111 } // namespace media 514 } // namespace media
OLDNEW
« no previous file with comments | « media/base/pipeline_impl.h ('k') | media/base/pipeline_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698