summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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(