summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorfischman@chromium.org <fischman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-29 22:05:33 +0000
committerfischman@chromium.org <fischman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-29 22:05:33 +0000
commitbdca921e4dd8e9dd4eebc42489b7fc8d73a246ed (patch)
tree3fda5f74a5e590ecbaca0cde1182556ea61a1b90 /media
parent58b169d57ed6445c11795c075144605bf840439a (diff)
downloadchromium_src-bdca921e4dd8e9dd4eebc42489b7fc8d73a246ed.zip
chromium_src-bdca921e4dd8e9dd4eebc42489b7fc8d73a246ed.tar.gz
chromium_src-bdca921e4dd8e9dd4eebc42489b7fc8d73a246ed.tar.bz2
AdaptiveDemuxer{,Factory,Stream} are born!
This family of classes enable presenting media combined from multiple URLs (e.g. pulling audio from one URL and video from another). In the future it will also enable switching sources during playback, but this isn't implemented yet. player_{x11,wtl} have been updated to use these new classes, but webmediaplayer_impl hasn't yet, because I don't want to expose my hacky alternative scheme to the world. BUG=none TEST=trybots Review URL: http://codereview.chromium.org/6769001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@79750 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/media_format.cc4
-rw-r--r--media/base/media_format.h2
-rw-r--r--media/filters/adaptive_demuxer.cc397
-rw-r--r--media/filters/adaptive_demuxer.h135
-rw-r--r--media/filters/adaptive_demuxer_unittest.cc74
-rw-r--r--media/filters/ffmpeg_demuxer.h4
-rw-r--r--media/filters/ffmpeg_demuxer_factory.h3
-rw-r--r--media/media.gyp3
-rw-r--r--media/tools/player_wtl/movie.cc6
-rw-r--r--media/tools/player_x11/player_x11.cc7
10 files changed, 628 insertions, 7 deletions
diff --git a/media/base/media_format.cc b/media/base/media_format.cc
index 45df6b6..242cfef 100644
--- a/media/base/media_format.cc
+++ b/media/base/media_format.cc
@@ -86,4 +86,8 @@ void MediaFormat::ReleaseValue(const std::string& key) {
}
}
+bool MediaFormat::operator==(MediaFormat const& other) const {
+ return value_map_ == other.value_map_;
+}
+
} // namespace media
diff --git a/media/base/media_format.h b/media/base/media_format.h
index 781afd4..419488b 100644
--- a/media/base/media_format.h
+++ b/media/base/media_format.h
@@ -52,6 +52,8 @@ class MediaFormat {
bool GetAsReal(const std::string& key, double* out_value) const;
bool GetAsString(const std::string& key, std::string* out_value) const;
+ bool operator==(MediaFormat const& other) const;
+
private:
// Helper to return a value.
Value* GetValue(const std::string& key) const;
diff --git a/media/filters/adaptive_demuxer.cc b/media/filters/adaptive_demuxer.cc
new file mode 100644
index 0000000..a533b4a
--- /dev/null
+++ b/media/filters/adaptive_demuxer.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "media/filters/adaptive_demuxer.h"
+
+namespace media {
+
+//
+// AdaptiveDemuxerStream
+//
+AdaptiveDemuxerStream::AdaptiveDemuxerStream(
+ StreamVector const& streams, int initial_stream)
+ : streams_(streams), current_stream_index_(initial_stream),
+ bitstream_converter_enabled_(false) {
+ DCheckSanity();
+}
+
+void AdaptiveDemuxerStream::DCheckSanity() {
+ // We only carry out sanity checks in debug mode.
+ if (!logging::DEBUG_MODE)
+ return;
+ DCHECK(streams_[current_stream_index_].get());
+
+ bool non_null_stream_seen = false;
+ Type type;
+ const MediaFormat* media_format;
+ for (size_t i = 0; i < streams_.size(); ++i) {
+ if (!streams_[i])
+ continue;
+
+ if (!non_null_stream_seen) {
+ non_null_stream_seen = true;
+ type = streams_[i]->type();
+ media_format = &streams_[i]->media_format();
+ } else {
+ DCHECK_EQ(streams_[i]->type(), type);
+ DCHECK(streams_[i]->media_format() == *media_format);
+ }
+ }
+}
+
+AdaptiveDemuxerStream::~AdaptiveDemuxerStream() {
+ base::AutoLock auto_lock(lock_);
+ current_stream_index_ = -1;
+ streams_.clear();
+}
+
+DemuxerStream* AdaptiveDemuxerStream::current_stream() {
+ base::AutoLock auto_lock(lock_);
+ return streams_[current_stream_index_];
+}
+
+void AdaptiveDemuxerStream::Read(Callback1<Buffer*>::Type* read_callback) {
+ current_stream()->Read(read_callback);
+}
+
+AVStream* AdaptiveDemuxerStream::GetAVStream() {
+ return current_stream()->GetAVStream();
+}
+
+DemuxerStream::Type AdaptiveDemuxerStream::type() {
+ return current_stream()->type();
+}
+
+const MediaFormat& AdaptiveDemuxerStream::media_format() {
+ return current_stream()->media_format();
+}
+
+void AdaptiveDemuxerStream::EnableBitstreamConverter() {
+ {
+ base::AutoLock auto_lock(lock_);
+ bitstream_converter_enabled_ = true;
+ }
+ current_stream()->EnableBitstreamConverter();
+}
+
+void AdaptiveDemuxerStream::ChangeCurrentStream(int index) {
+ bool needs_bitstream_converter_enabled;
+ {
+ base::AutoLock auto_lock(lock_);
+ current_stream_index_ = index;
+ DCHECK(streams_[current_stream_index_]);
+ needs_bitstream_converter_enabled = bitstream_converter_enabled_;
+ }
+ if (needs_bitstream_converter_enabled)
+ EnableBitstreamConverter();
+}
+
+//
+// AdaptiveDemuxer
+//
+
+AdaptiveDemuxer::AdaptiveDemuxer(DemuxerVector const& demuxers,
+ int initial_audio_demuxer_index,
+ int initial_video_demuxer_index)
+ : demuxers_(demuxers),
+ current_audio_demuxer_index_(initial_audio_demuxer_index),
+ current_video_demuxer_index_(initial_video_demuxer_index) {
+ DCHECK(!demuxers_.empty());
+ DCHECK_GE(current_audio_demuxer_index_, -1);
+ DCHECK_GE(current_video_demuxer_index_, -1);
+ DCHECK_LT(current_audio_demuxer_index_, static_cast<int>(demuxers_.size()));
+ DCHECK_LT(current_video_demuxer_index_, static_cast<int>(demuxers_.size()));
+ DCHECK(current_audio_demuxer_index_ != -1 ||
+ current_video_demuxer_index_ != -1);
+ AdaptiveDemuxerStream::StreamVector audio_streams, video_streams;
+ for (size_t i = 0; i < demuxers_.size(); ++i) {
+ audio_streams.push_back(demuxers_[i]->GetStream(DemuxerStream::AUDIO));
+ video_streams.push_back(demuxers_[i]->GetStream(DemuxerStream::VIDEO));
+ }
+ if (current_audio_demuxer_index_ >= 0) {
+ audio_stream_ = new AdaptiveDemuxerStream(
+ audio_streams, current_audio_demuxer_index_);
+ }
+ if (current_video_demuxer_index_ >= 0) {
+ video_stream_ = new AdaptiveDemuxerStream(
+ video_streams, current_video_demuxer_index_);
+ }
+
+ // TODO(fischman): any streams in the underlying demuxers that aren't being
+ // consumed currently need to be sent to /dev/null or else FFmpegDemuxer will
+ // hold data for them in memory, waiting for them to get drained by a
+ // non-existent decoder.
+}
+
+AdaptiveDemuxer::~AdaptiveDemuxer() {}
+
+void AdaptiveDemuxer::ChangeCurrentDemuxer(int audio_index, int video_index) {
+ // TODO(fischman): this is currently broken because when a new Demuxer is to
+ // be used we need to set_host(host()) it, and we need to set_host(NULL) the
+ // current Demuxer if it's no longer being used.
+ // TODO(fischman): remember to Stop active demuxers that are being abandoned.
+ base::AutoLock auto_lock(lock_);
+ current_audio_demuxer_index_ = audio_index;
+ current_video_demuxer_index_ = video_index;
+ if (audio_stream_)
+ audio_stream_->ChangeCurrentStream(audio_index);
+ if (video_stream_)
+ video_stream_->ChangeCurrentStream(video_index);
+}
+
+Demuxer* AdaptiveDemuxer::current_demuxer(DemuxerStream::Type type) {
+ base::AutoLock auto_lock(lock_);
+ switch (type) {
+ case DemuxerStream::AUDIO:
+ return (current_audio_demuxer_index_ < 0) ? NULL :
+ demuxers_[current_audio_demuxer_index_];
+ case DemuxerStream::VIDEO:
+ return (current_video_demuxer_index_ < 0) ? NULL :
+ demuxers_[current_video_demuxer_index_];
+ default:
+ LOG(DFATAL) << "Unexpected type: " << type;
+ return NULL;
+ }
+}
+
+// Helper class that wraps a FilterCallback and expects to get called a set
+// number of times, after which the wrapped callback is fired (and deleted).
+class CountingCallback {
+ public:
+ CountingCallback(int count, FilterCallback* orig_cb)
+ : remaining_count_(count), orig_cb_(orig_cb) {
+ DCHECK_GT(remaining_count_, 0);
+ DCHECK(orig_cb);
+ }
+
+ FilterCallback* GetACallback() {
+ return NewCallback(this, &CountingCallback::OnChildCallbackDone);
+ }
+
+ private:
+ void OnChildCallbackDone() {
+ bool fire_orig_cb = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ if (--remaining_count_ == 0)
+ fire_orig_cb = true;
+ }
+ if (fire_orig_cb) {
+ orig_cb_->Run();
+ delete this;
+ }
+ }
+
+ base::Lock lock_;
+ int remaining_count_;
+ scoped_ptr<FilterCallback> orig_cb_;
+};
+
+void AdaptiveDemuxer::Stop(FilterCallback* callback) {
+ Demuxer* audio = current_demuxer(DemuxerStream::AUDIO);
+ Demuxer* video = current_demuxer(DemuxerStream::VIDEO);
+ int count = (audio ? 1 : 0) + (video && audio != video ? 1 : 0);
+ CountingCallback* wrapper = new CountingCallback(count, callback);
+ if (audio)
+ audio->Stop(wrapper->GetACallback());
+ if (video && audio != video)
+ video->Stop(wrapper->GetACallback());
+}
+
+void AdaptiveDemuxer::Seek(base::TimeDelta time, FilterCallback* callback) {
+ Demuxer* audio = current_demuxer(DemuxerStream::AUDIO);
+ Demuxer* video = current_demuxer(DemuxerStream::VIDEO);
+ int count = (audio ? 1 : 0) + (video && audio != video ? 1 : 0);
+ CountingCallback* wrapper = new CountingCallback(count, callback);
+ if (audio)
+ audio->Seek(time, wrapper->GetACallback());
+ if (video && audio != video)
+ video->Seek(time, wrapper->GetACallback());
+}
+
+void AdaptiveDemuxer::OnAudioRendererDisabled() {
+ Demuxer* audio = current_demuxer(DemuxerStream::AUDIO);
+ Demuxer* video = current_demuxer(DemuxerStream::VIDEO);
+ if (audio) audio->OnAudioRendererDisabled();
+ if (video && audio != video) video->OnAudioRendererDisabled();
+ // TODO(fischman): propagate to other demuxers if/when they're selected.
+}
+
+void AdaptiveDemuxer::set_host(FilterHost* filter_host) {
+ Demuxer* audio = current_demuxer(DemuxerStream::AUDIO);
+ Demuxer* video = current_demuxer(DemuxerStream::VIDEO);
+ if (audio) audio->set_host(filter_host);
+ if (video && audio != video) video->set_host(filter_host);
+}
+
+scoped_refptr<DemuxerStream> AdaptiveDemuxer::GetStream(
+ DemuxerStream::Type type) {
+ switch (type) {
+ case DemuxerStream::AUDIO:
+ return audio_stream_;
+ case DemuxerStream::VIDEO:
+ return video_stream_;
+ default:
+ LOG(DFATAL) << "Unexpected type " << type;
+ return NULL;
+ }
+}
+
+//
+// AdaptiveDemuxerFactory
+//
+
+AdaptiveDemuxerFactory::AdaptiveDemuxerFactory(
+ DemuxerFactory* delegate_factory)
+ : delegate_factory_(delegate_factory) {
+ DCHECK(delegate_factory);
+}
+
+AdaptiveDemuxerFactory::~AdaptiveDemuxerFactory() {}
+
+DemuxerFactory* AdaptiveDemuxerFactory::Clone() const {
+ return new AdaptiveDemuxerFactory(delegate_factory_->Clone());
+}
+
+// See AdaptiveDemuxerFactory's class-level comment for |url|'s format.
+bool ParseAdaptiveUrl(
+ const std::string& url, int* audio_index, int* video_index,
+ std::vector<std::string>* urls) {
+ urls->clear();
+
+ if (url.empty())
+ return false;
+ if (!StartsWithASCII(url, "x-adaptive:", false)) {
+ return ParseAdaptiveUrl(
+ "x-adaptive:0:0:" + url, audio_index, video_index, urls);
+ }
+
+ std::vector<std::string> parts;
+ base::SplitStringDontTrim(url, ':', &parts);
+ if (parts.size() < 4 ||
+ parts[0] != "x-adaptive" ||
+ !base::StringToInt(parts[1], audio_index) ||
+ !base::StringToInt(parts[2], video_index) ||
+ *audio_index < -1 || *video_index < -1) {
+ return false;
+ }
+
+ std::string::size_type first_url_pos =
+ parts[0].size() + 1 + parts[1].size() + 1 + parts[2].size() + 1;
+ std::string urls_str = url.substr(first_url_pos);
+ if (urls_str.empty())
+ return false;
+ base::SplitStringDontTrim(urls_str, '^', urls);
+ if (urls->empty() ||
+ *audio_index >= static_cast<int>(urls->size()) ||
+ *video_index >= static_cast<int>(urls->size())) {
+ return false;
+ }
+
+ return true;
+}
+
+// Wrapper for a BuildCallback which accumulates the Demuxer's returned by a
+// number of DemuxerFactory::Build() calls and eventually constructs an
+// AdaptiveDemuxer and returns it to the |orig_cb| (or errors out if any
+// individual Demuxer fails construction).
+class DemuxerAccumulator {
+ public:
+ // Takes ownership of |orig_cb|.
+ DemuxerAccumulator(int audio_index, int video_index,
+ int count, DemuxerFactory::BuildCallback* orig_cb)
+ : audio_index_(audio_index), video_index_(video_index),
+ remaining_count_(count), orig_cb_(orig_cb),
+ demuxers_(count, static_cast<Demuxer*>(NULL)),
+ statuses_(count, PIPELINE_OK) {
+ DCHECK_GT(remaining_count_, 0);
+ DCHECK(orig_cb_.get());
+ }
+
+ DemuxerFactory::BuildCallback* GetNthCallback(int n) {
+ return new IndividualCallback(this, n);
+ }
+
+ private:
+ // Wrapper for a BuildCallback that can carry one extra piece of data: the
+ // index of this callback in the original list of outstanding requests.
+ struct IndividualCallback : public DemuxerFactory::BuildCallback {
+ IndividualCallback(DemuxerAccumulator* accumulator, int index)
+ : accumulator_(accumulator), index_(index) {}
+
+ virtual void RunWithParams(const Tuple2<PipelineStatus, Demuxer*>& params) {
+ accumulator_->Run(index_, params.a, params.b);
+ }
+ DemuxerAccumulator* accumulator_;
+ int index_;
+ };
+
+ // When an individual callback is fired, it calls this method.
+ void Run(int index, PipelineStatus status, Demuxer* demuxer) {
+ bool fire_orig_cb = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(!demuxers_[index]);
+ demuxers_[index] = demuxer;
+ statuses_[index] = status;
+ if (--remaining_count_ == 0)
+ fire_orig_cb = true;
+ }
+ if (fire_orig_cb)
+ DoneAccumulating();
+ }
+
+ void DoneAccumulating() {
+ PipelineStatus overall_status = PIPELINE_OK;
+ for (size_t i = 0; i < statuses_.size(); ++i) {
+ if (statuses_[i] != PIPELINE_OK) {
+ overall_status = statuses_[i];
+ break;
+ }
+ }
+ if (overall_status == PIPELINE_OK) {
+ orig_cb_->Run(
+ PIPELINE_OK,
+ new AdaptiveDemuxer(demuxers_, audio_index_, video_index_));
+ } else {
+ orig_cb_->Run(overall_status, static_cast<Demuxer*>(NULL));
+ }
+
+ delete this;
+ }
+
+ // Self-delete in DoneAccumulating() only.
+ ~DemuxerAccumulator() {}
+
+ int audio_index_;
+ int video_index_;
+ int remaining_count_;
+ scoped_ptr<DemuxerFactory::BuildCallback> orig_cb_;
+ base::Lock lock_; // Guards vectors of results below.
+ AdaptiveDemuxer::DemuxerVector demuxers_;
+ std::vector<PipelineStatus> statuses_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DemuxerAccumulator);
+};
+
+void AdaptiveDemuxerFactory::Build(const std::string& url, BuildCallback* cb) {
+ std::vector<std::string> urls;
+ int audio_index, video_index;
+ if (!ParseAdaptiveUrl(url, &audio_index, &video_index, &urls)) {
+ cb->Run(Tuple2<PipelineStatus, Demuxer*>(
+ DEMUXER_ERROR_COULD_NOT_OPEN, NULL));
+ delete cb;
+ return;
+ }
+ DemuxerAccumulator* accumulator = new DemuxerAccumulator(
+ audio_index, video_index, urls.size(), cb);
+ for (size_t i = 0; i < urls.size(); ++i)
+ delegate_factory_->Build(urls[i], accumulator->GetNthCallback(i));
+}
+
+} // namespace media
diff --git a/media/filters/adaptive_demuxer.h b/media/filters/adaptive_demuxer.h
new file mode 100644
index 0000000..23cd570
--- /dev/null
+++ b/media/filters/adaptive_demuxer.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Implements a Demuxer that can switch among different data sources mid-stream.
+// Uses FFmpegDemuxer under the covers, so see the caveats at the top of
+// ffmpeg_demuxer.h.
+
+#ifndef MEDIA_FILTERS_ADAPTIVE_DEMUXER_H_
+#define MEDIA_FILTERS_ADAPTIVE_DEMUXER_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/synchronization/lock.h"
+#include "media/base/buffers.h"
+#include "media/base/filter_factories.h"
+#include "media/base/filters.h"
+#include "media/base/pipeline.h"
+#include "media/base/media_format.h"
+
+namespace media {
+
+class AdaptiveDemuxer;
+
+class AdaptiveDemuxerStream : public DemuxerStream {
+ public:
+ typedef std::vector<scoped_refptr<DemuxerStream> > StreamVector;
+
+ // Keeps references to the passed-in streams. |streams| must be non-empty and
+ // all the streams in it must agree on type and media_format (or be NULL).
+ // |initial_stream| must be a valid index into |streams| and specifies the
+ // current stream on construction.
+ AdaptiveDemuxerStream(StreamVector const& streams, int initial_stream);
+ virtual ~AdaptiveDemuxerStream();
+
+ // Change the stream to satisfy subsequent Read() requests from. The
+ // referenced pointer must not be NULL.
+ void ChangeCurrentStream(int index);
+
+ // DemuxerStream methods.
+ virtual void Read(Callback1<Buffer*>::Type* read_callback);
+ virtual Type type();
+ virtual const MediaFormat& media_format();
+ virtual void EnableBitstreamConverter();
+ virtual AVStream* GetAVStream();
+
+ private:
+ // Returns a pointer to the current stream.
+ DemuxerStream* current_stream();
+
+ // DEBUG_MODE-only CHECK that the data members are in a reasonable state.
+ void DCheckSanity();
+
+ StreamVector streams_;
+ // Guards the members below. Only held for simple variable reads/writes, not
+ // during async operation.
+ base::Lock lock_;
+ int current_stream_index_;
+ bool bitstream_converter_enabled_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveDemuxerStream);
+};
+
+class AdaptiveDemuxer : public Demuxer {
+ public:
+ typedef std::vector<scoped_refptr<Demuxer> > DemuxerVector;
+
+ // |demuxers| must be non-empty, and the index arguments must be valid indexes
+ // into |demuxers|, or -1 to indicate no demuxer is serving that type.
+ AdaptiveDemuxer(DemuxerVector const& demuxers,
+ int initial_audio_demuxer_index,
+ int initial_video_demuxer_index);
+ virtual ~AdaptiveDemuxer();
+
+ // Change which demuxers the streams will use.
+ void ChangeCurrentDemuxer(int audio_index, int video_index);
+
+ // Filter implementation.
+ virtual void Stop(FilterCallback* callback);
+ virtual void Seek(base::TimeDelta time, FilterCallback* callback);
+ virtual void OnAudioRendererDisabled();
+ virtual void set_host(FilterHost* filter_host);
+ // TODO(fischman): add support for SetPlaybackRate().
+
+ // Demuxer implementation.
+ virtual scoped_refptr<DemuxerStream> GetStream(DemuxerStream::Type type);
+
+ private:
+ // Returns a pointer to the currently active demuxer of the given type.
+ Demuxer* current_demuxer(DemuxerStream::Type type);
+
+ DemuxerVector demuxers_;
+ scoped_refptr<AdaptiveDemuxerStream> audio_stream_;
+ scoped_refptr<AdaptiveDemuxerStream> video_stream_;
+ // Guards the members below. Only held for simple variable reads/writes, not
+ // during async operation.
+ base::Lock lock_;
+ int current_audio_demuxer_index_;
+ int current_video_demuxer_index_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveDemuxer);
+};
+
+// AdaptiveDemuxerFactory wraps an underlying DemuxerFactory that knows how to
+// build demuxers for a single URL, and implements a primitive (for now) version
+// of multi-file manifests. The manifest is encoded in the |url| parameter to
+// Build() as:
+// x-adaptive:<initial_audio_index>:<initial_video_index>:<URL>[^<URL>]* where
+// <URL>'s are "real" media URLs which are passed to the underlying
+// DemuxerFactory's Build() method individually. For backward-compatibility,
+// the manifest URL may also simply be a regular URL in which case an implicit
+// "x-adaptive:0:0:" is prepended.
+class AdaptiveDemuxerFactory : public DemuxerFactory {
+ public:
+ // Takes a reference to |demuxer_factory|.
+ AdaptiveDemuxerFactory(DemuxerFactory* delegate_factory);
+ virtual ~AdaptiveDemuxerFactory();
+
+ // DemuxerFactory methods.
+ // If any of the underlying Demuxers encounter construction errors, only the
+ // first one (in manifest order) will get reported back in |cb|.
+ virtual void Build(const std::string& url, BuildCallback* cb);
+ virtual DemuxerFactory* Clone() const;
+
+ private:
+ scoped_ptr<DemuxerFactory> delegate_factory_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveDemuxerFactory);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_ADAPTIVE_DEMUXER_H_
diff --git a/media/filters/adaptive_demuxer_unittest.cc b/media/filters/adaptive_demuxer_unittest.cc
new file mode 100644
index 0000000..3d8b31a
--- /dev/null
+++ b/media/filters/adaptive_demuxer_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/string_number_conversions.h"
+#include "media/filters/adaptive_demuxer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+bool ParseAdaptiveUrl(
+ const std::string& url, int* audio_index, int* video_index,
+ std::vector<std::string>* urls);
+
+TEST(ParseAdaptiveUrlTest, BackwardsCompatible) {
+ std::string manifest = "http://youtube.com/video.webm";
+ int audio_index;
+ int video_index;
+ std::vector<std::string> urls;
+ ASSERT_TRUE(ParseAdaptiveUrl(manifest, &audio_index, &video_index, &urls));
+ EXPECT_EQ(audio_index, 0);
+ EXPECT_EQ(video_index, 0);
+ ASSERT_EQ(urls.size(), 1u);
+ EXPECT_EQ(urls[0], manifest);
+}
+
+TEST(ParseAdaptiveUrlTest, CombinedManifest) {
+ std::string video1 = "http://youtube.com/video1.webm";
+ std::string video2 = "http://youtube.com/video2.webm";
+ for (int a = 0; a <= 1; ++a) {
+ for (int v = 0; v <= 1; ++v) {
+ std::string manifest = "x-adaptive:" +
+ base::IntToString(a) + ":" + base::IntToString(v) + ":" +
+ video1 + "^" + video2;
+ int audio_index;
+ int video_index;
+ std::vector<std::string> urls;
+ ASSERT_TRUE(ParseAdaptiveUrl(
+ manifest, &audio_index, &video_index, &urls));
+ EXPECT_EQ(audio_index, a);
+ EXPECT_EQ(video_index, v);
+ ASSERT_EQ(urls.size(), 2u);
+ EXPECT_EQ(urls[0], video1);
+ EXPECT_EQ(urls[1], video2);
+ }
+ }
+}
+
+TEST(ParseAdaptiveUrlTest, Errors) {
+ int audio_index;
+ int video_index;
+ std::vector<std::string> urls;
+ // Indexes are required on x-adaptive URLs.
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "x-adaptive:file.webm", &audio_index, &video_index, &urls));
+ // Empty URL list is not allowed.
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "x-adaptive:0:0:", &audio_index, &video_index, &urls));
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "x-adaptive:0:0", &audio_index, &video_index, &urls));
+ // Empty URL is not allowed.
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "", &audio_index, &video_index, &urls));
+ // Malformed indexes parameter.
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "x-adaptive:0:file.webm", &audio_index, &video_index, &urls));
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "x-adaptive::0:file.webm", &audio_index, &video_index, &urls));
+ // Invalid index for URL list.
+ EXPECT_FALSE(ParseAdaptiveUrl(
+ "x-adaptive:1:0:file.webm", &audio_index, &video_index, &urls));
+}
+
+} // namespace media
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index 57e1a3d..9dc5800 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -50,8 +50,8 @@ class ScopedPtrAVFree;
class FFmpegDemuxerStream : public DemuxerStream {
public:
- // Maintains a reference to |demuxer| and initializes itself using information
- // inside |stream|.
+ // Keeps a copy of |demuxer| and initializes itself using information
+ // inside |stream|. Both parameters must outlive |this|.
FFmpegDemuxerStream(FFmpegDemuxer* demuxer, AVStream* stream);
// Returns true is this stream has pending reads, false otherwise.
diff --git a/media/filters/ffmpeg_demuxer_factory.h b/media/filters/ffmpeg_demuxer_factory.h
index 2306df5..b467d75 100644
--- a/media/filters/ffmpeg_demuxer_factory.h
+++ b/media/filters/ffmpeg_demuxer_factory.h
@@ -22,6 +22,7 @@ class FFmpegDemuxerFactory : public DemuxerFactory {
MessageLoop* loop);
virtual ~FFmpegDemuxerFactory();
+ // DemuxerFactory methods.
virtual void Build(const std::string& url, BuildCallback* cb);
virtual DemuxerFactory* Clone() const;
@@ -29,7 +30,7 @@ class FFmpegDemuxerFactory : public DemuxerFactory {
scoped_ptr<DataSourceFactory> data_source_factory_;
MessageLoop* loop_; // Unowned.
- DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerFactory);
+ DISALLOW_IMPLICIT_CONSTRUCTORS(FFmpegDemuxerFactory);
};
} // namespace media
diff --git a/media/media.gyp b/media/media.gyp
index 7b560676..c5b457e 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -125,6 +125,8 @@
'ffmpeg/ffmpeg_common.h',
'ffmpeg/file_protocol.cc',
'ffmpeg/file_protocol.h',
+ 'filters/adaptive_demuxer.cc',
+ 'filters/adaptive_demuxer.h',
'filters/audio_file_reader.cc',
'filters/audio_file_reader.h',
'filters/audio_renderer_algorithm_base.cc',
@@ -374,6 +376,7 @@
'base/state_matrix_unittest.cc',
'base/video_frame_unittest.cc',
'base/yuv_convert_unittest.cc',
+ 'filters/adaptive_demuxer_unittest.cc',
'filters/audio_renderer_algorithm_ola_unittest.cc',
'filters/audio_renderer_base_unittest.cc',
'filters/bitstream_converter_unittest.cc',
diff --git a/media/tools/player_wtl/movie.cc b/media/tools/player_wtl/movie.cc
index 3090a48..62da5e5 100644
--- a/media/tools/player_wtl/movie.cc
+++ b/media/tools/player_wtl/movie.cc
@@ -10,6 +10,7 @@
#include "media/base/filter_collection.h"
#include "media/base/message_loop_factory_impl.h"
#include "media/base/pipeline_impl.h"
+#include "media/filters/adaptive_demuxer.h"
#include "media/filters/audio_renderer_impl.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#include "media/filters/ffmpeg_demuxer_factory.h"
@@ -18,6 +19,7 @@
#include "media/filters/null_audio_renderer.h"
#include "media/tools/player_wtl/wtl_renderer.h"
+using media::AdaptiveDemuxerFactory;
using media::AudioRendererImpl;
using media::FFmpegAudioDecoder;
using media::FFmpegDemuxerFactory;
@@ -69,8 +71,8 @@ bool Movie::Open(const wchar_t* url, WtlVideoRenderer* video_renderer) {
// Create filter collection.
scoped_ptr<FilterCollection> collection(new FilterCollection());
- collection->SetDemuxerFactory(new FFmpegDemuxerFactory(
- new FileDataSourceFactory(), pipeline_loop));
+ collection->SetDemuxerFactory(new AdaptiveDemuxerFactory(
+ new FFmpegDemuxerFactory(new FileDataSourceFactory(), pipeline_loop)));
collection->AddAudioDecoder(new FFmpegAudioDecoder(
message_loop_factory_->GetMessageLoop("AudioDecoderThread")));
collection->AddVideoDecoder(new FFmpegVideoDecoder(
diff --git a/media/tools/player_x11/player_x11.cc b/media/tools/player_x11/player_x11.cc
index 9624330..ea841aa 100644
--- a/media/tools/player_x11/player_x11.cc
+++ b/media/tools/player_x11/player_x11.cc
@@ -19,6 +19,7 @@
#include "media/base/media_switches.h"
#include "media/base/message_loop_factory_impl.h"
#include "media/base/pipeline_impl.h"
+#include "media/filters/adaptive_demuxer.h"
#include "media/filters/audio_renderer_impl.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#include "media/filters/ffmpeg_demuxer_factory.h"
@@ -111,8 +112,10 @@ bool InitPipeline(MessageLoop* message_loop,
// Create our filter factories.
scoped_ptr<media::FilterCollection> collection(
new media::FilterCollection());
- collection->SetDemuxerFactory(new media::FFmpegDemuxerFactory(
- new media::FileDataSourceFactory(), message_loop));
+ collection->SetDemuxerFactory(
+ new media::AdaptiveDemuxerFactory(
+ new media::FFmpegDemuxerFactory(
+ new media::FileDataSourceFactory(), message_loop)));
collection->AddAudioDecoder(new media::FFmpegAudioDecoder(
message_loop_factory->GetMessageLoop("AudioDecoderThread")));
if (CommandLine::ForCurrentProcess()->HasSwitch(