MediaCaptureFromElement: add support for audio captureStream().

This CL extends support for capturing the audio part of
a <video> or <audio> tags ( "capture" here means creating
a MediaStream out of the HTMLElement)

It introduces an HtmlAudioCapturerSource is-a AudioCapturerSource
wrapped into an ExternalMediaStreamAudioSource to produce data
towards the audio track.

HtmlAudioCapturerSource also plugs into the
WebMediaPlayer's WebAudioSourceProviderImpl to get
a copy of the audio being rendered.

Unit tests are added, and the existing LayouTests
revamped (and split into several files for clarity).

BUG=569976, 575492

TEST= run chromium with
 --enable-blink-features=MediaCaptureFromVideo
 against e.g.
https://rawgit.com/Miguelao/demos/master/videoelementcapture.html

Review-Url: https://codereview.chromium.org/1599533003
Cr-Commit-Position: refs/heads/master@{#395205}
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index 2e4eb60..5861d36 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -314,6 +314,8 @@
       'renderer/media/cdm/ppapi_decryptor.h',
       'renderer/media/cdm/render_cdm_factory.cc',
       'renderer/media/cdm/render_cdm_factory.h',
+      'renderer/media/html_audio_element_capturer_source.cc',
+      'renderer/media/html_audio_element_capturer_source.h',
       'renderer/media/external_media_stream_audio_source.cc',
       'renderer/media/external_media_stream_audio_source.h',
       'renderer/media/media_permission_dispatcher.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 12a4b1a..061b6476 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -722,6 +722,7 @@
       'renderer/media/android/media_info_loader_unittest.cc',
       'renderer/media/audio_message_filter_unittest.cc',
       'renderer/media/audio_renderer_mixer_manager_unittest.cc',
+      'renderer/media/html_audio_element_capturer_source_unittest.cc',
       'renderer/media/media_stream_audio_unittest.cc',
       'renderer/media/midi_message_filter_unittest.cc',
       'renderer/media/mock_audio_device_factory.cc',
diff --git a/content/renderer/media/html_audio_element_capturer_source.cc b/content/renderer/media/html_audio_element_capturer_source.cc
new file mode 100644
index 0000000..399c221
--- /dev/null
+++ b/content/renderer/media/html_audio_element_capturer_source.cc
@@ -0,0 +1,89 @@
+// Copyright 2016 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 "content/renderer/media/html_audio_element_capturer_source.h"
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/audio_renderer_sink.h"
+#include "media/blink/webaudiosourceprovider_impl.h"
+#include "media/blink/webmediaplayer_impl.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayer.h"
+
+namespace content {
+
+//static
+HtmlAudioElementCapturerSource*
+HtmlAudioElementCapturerSource::CreateFromWebMediaPlayerImpl(
+    blink::WebMediaPlayer* player) {
+  DCHECK(player);
+  return new HtmlAudioElementCapturerSource(
+      static_cast<media::WebAudioSourceProviderImpl*>(
+          player->getAudioSourceProvider()));
+}
+
+HtmlAudioElementCapturerSource::HtmlAudioElementCapturerSource(
+    media::WebAudioSourceProviderImpl* audio_source)
+    : MediaStreamAudioSource(true /* is_local_source */),
+      audio_source_(audio_source),
+      is_started_(false),
+      last_sample_rate_(0),
+      last_num_channels_(0),
+      last_bus_frames_(0) {
+  DCHECK(audio_source_);
+}
+
+HtmlAudioElementCapturerSource::~HtmlAudioElementCapturerSource() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  EnsureSourceIsStopped();
+}
+
+bool HtmlAudioElementCapturerSource::EnsureSourceIsStarted() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (audio_source_ && !is_started_) {
+    // base:Unretained() is safe here since EnsureSourceIsStopped() guarantees
+    // no more calls to OnAudioBus().
+    audio_source_->SetCopyAudioCallback(base::Bind(
+        &HtmlAudioElementCapturerSource::OnAudioBus, base::Unretained(this)));
+    is_started_ = true;
+  }
+  return is_started_;
+}
+
+void HtmlAudioElementCapturerSource::EnsureSourceIsStopped() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!is_started_)
+    return;
+
+  if (audio_source_) {
+    audio_source_->ClearCopyAudioCallback();
+    audio_source_ = nullptr;
+  }
+  is_started_ = false;
+}
+
+void HtmlAudioElementCapturerSource::OnAudioBus(
+    std::unique_ptr<media::AudioBus> audio_bus,
+    uint32_t delay_milliseconds,
+    int sample_rate) {
+  const base::TimeTicks capture_time =
+      base::TimeTicks::Now() -
+      base::TimeDelta::FromMilliseconds(delay_milliseconds);
+
+  if (sample_rate != last_sample_rate_ ||
+      audio_bus->channels() != last_num_channels_ ||
+      audio_bus->frames() != last_bus_frames_) {
+    MediaStreamAudioSource::SetFormat(
+        media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+                               media::GuessChannelLayout(audio_bus->channels()),
+                               sample_rate, 16, audio_bus->frames()));
+    last_sample_rate_ = sample_rate;
+    last_num_channels_ = audio_bus->channels();
+    last_bus_frames_ = audio_bus->frames();
+  }
+
+  MediaStreamAudioSource::DeliverDataToTracks(*audio_bus, capture_time);
+}
+
+}  // namespace content
diff --git a/content/renderer/media/html_audio_element_capturer_source.h b/content/renderer/media/html_audio_element_capturer_source.h
new file mode 100644
index 0000000..2bbfb3c
--- /dev/null
+++ b/content/renderer/media/html_audio_element_capturer_source.h
@@ -0,0 +1,63 @@
+// Copyright 2016 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.
+
+#ifndef CONTENT_RENDERER_MEDIA_HTML_AUDIO_ELEMENT_CAPTURER_SOURCE_H_
+#define CONTENT_RENDERER_MEDIA_HTML_AUDIO_ELEMENT_CAPTURER_SOURCE_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "content/common/content_export.h"
+#include "content/renderer/media/media_stream_audio_source.h"
+
+namespace blink {
+class WebMediaPlayer;
+}  // namespace blink
+
+namespace media {
+class AudioBus;
+class WebAudioSourceProviderImpl;
+}  // namespace media
+
+namespace content {
+
+// This class is a MediaStreamAudioSource that registers to the constructor-
+// passed weak WebAudioSourceProviderImpl to receive a copy of the audio data
+// intended for rendering. This copied data is received on OnAudioBus() and sent
+// to all the registered Tracks.
+class CONTENT_EXPORT HtmlAudioElementCapturerSource final
+    : NON_EXPORTED_BASE(public MediaStreamAudioSource) {
+ public:
+  static HtmlAudioElementCapturerSource*
+  CreateFromWebMediaPlayerImpl(blink::WebMediaPlayer* player);
+
+  explicit HtmlAudioElementCapturerSource(
+      media::WebAudioSourceProviderImpl* audio_source);
+  ~HtmlAudioElementCapturerSource() override;
+
+ private:
+  // MediaStreamAudioSource implementation.
+  bool EnsureSourceIsStarted() final;
+  void EnsureSourceIsStopped() final;
+
+  // To act as an WebAudioSourceProviderImpl::CopyAudioCB.
+  void OnAudioBus(std::unique_ptr<media::AudioBus> audio_bus,
+                  uint32_t delay_milliseconds,
+                  int sample_rate);
+
+  scoped_refptr<media::WebAudioSourceProviderImpl> audio_source_;
+
+  bool is_started_;
+  int last_sample_rate_;
+  int last_num_channels_;
+  int last_bus_frames_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(HtmlAudioElementCapturerSource);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_MEDIA_HTML_AUDIO_ELEMENT_CAPTURER_SOURCE_H_
diff --git a/content/renderer/media/html_audio_element_capturer_source_unittest.cc b/content/renderer/media/html_audio_element_capturer_source_unittest.cc
new file mode 100644
index 0000000..10d24ed
--- /dev/null
+++ b/content/renderer/media/html_audio_element_capturer_source_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2016 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/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/public/renderer/media_stream_audio_sink.h"
+#include "content/renderer/media/html_audio_element_capturer_source.h"
+#include "content/renderer/media/media_stream_audio_track.h"
+#include "media/audio/null_audio_sink.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/fake_audio_render_callback.h"
+#include "media/blink/webaudiosourceprovider_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/web/WebHeap.h"
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::InSequence;
+using ::testing::Mock;
+using ::testing::Property;
+
+namespace content {
+
+static const int kNumChannelsForTest = 1;
+static const int kBufferDurationMs = 10;
+
+static const int kAudioTrackSampleRate = 48000;
+static const int kAudioTrackSamplesPerBuffer =
+    kAudioTrackSampleRate * kBufferDurationMs /
+    base::Time::kMillisecondsPerSecond;
+
+ACTION_P(RunClosure, closure) {
+  closure.Run();
+}
+
+//
+class MockMediaStreamAudioSink final : public MediaStreamAudioSink {
+ public:
+  MockMediaStreamAudioSink() : MediaStreamAudioSink() {}
+  ~MockMediaStreamAudioSink() = default;
+
+  MOCK_METHOD1(OnSetFormat, void(const media::AudioParameters& params));
+  MOCK_METHOD2(OnData,
+               void(const media::AudioBus& audio_bus,
+                    base::TimeTicks estimated_capture_time));
+
+  DISALLOW_COPY_AND_ASSIGN(MockMediaStreamAudioSink);
+};
+
+// This test needs to bundle together plenty of objects, namely:
+// - a WebAudioSourceProviderImpl, which in turn needs an Audio Sink, in this
+//  case a NullAudioSink. This is needed to plug HTMLAudioElementCapturerSource
+//  and inject audio.
+// - a WebMediaStreamSource, that owns the HTMLAudioElementCapturerSource under
+//  test, and a WebMediaStreamAudioTrack, that the class under test needs to
+//  connect to in order to operate correctly. This class has an inner content
+//  MediaStreamAudioTrack.
+// - finally, a MockMediaStreamAudioSink to observe captured audio frames, and
+//  that plugs into the former MediaStreamAudioTrack.
+class HTMLAudioElementCapturerSourceTest : public testing::Test {
+ public:
+  HTMLAudioElementCapturerSourceTest()
+      : fake_callback_(0.1),
+        audio_source_(new media::WebAudioSourceProviderImpl(
+            new media::NullAudioSink(base::ThreadTaskRunnerHandle::Get()))) {}
+
+  void SetUp() final {
+    const media::AudioParameters params(
+        media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+        media::GuessChannelLayout(kNumChannelsForTest),
+        kAudioTrackSampleRate /* sample_rate */, 16 /* bits_per_sample */,
+        kAudioTrackSamplesPerBuffer /* frames_per_buffer */);
+    audio_source_->Initialize(params, &fake_callback_);
+
+    blink_audio_source_.initialize(blink::WebString::fromUTF8("audio_id"),
+                                   blink::WebMediaStreamSource::TypeAudio,
+                                   blink::WebString::fromUTF8("audio_track"),
+                                   false /* remote */);
+    blink_audio_track_.initialize(blink_audio_source_.id(),
+                                  blink_audio_source_);
+
+    // |blink_audio_source_| takes ownership of HtmlAudioElementCapturerSource.
+    blink_audio_source_.setExtraData(
+        new HtmlAudioElementCapturerSource(audio_source_.get()));
+    ASSERT_TRUE(source()->ConnectToTrack(blink_audio_track_));
+  }
+
+  void TearDown() override {
+    blink_audio_track_.reset();
+    blink_audio_source_.reset();
+    blink::WebHeap::collectAllGarbageForTesting();
+  }
+
+  HtmlAudioElementCapturerSource* source() const {
+    return static_cast<HtmlAudioElementCapturerSource*>(
+        MediaStreamAudioSource::From(blink_audio_source_));
+  }
+
+  MediaStreamAudioTrack* track() const {
+    return MediaStreamAudioTrack::From(blink_audio_track_);
+  }
+
+  int InjectAudio(media::AudioBus* audio_bus) {
+    return audio_source_->RenderForTesting(audio_bus);
+  }
+
+ protected:
+  const base::MessageLoop message_loop_;
+
+  blink::WebMediaStreamSource blink_audio_source_;
+  blink::WebMediaStreamTrack blink_audio_track_;
+
+  media::FakeAudioRenderCallback fake_callback_;
+  scoped_refptr<media::WebAudioSourceProviderImpl> audio_source_;
+};
+
+// Constructs and destructs all objects. This is a non trivial sequence.
+TEST_F(HTMLAudioElementCapturerSourceTest, ConstructAndDestruct) {
+}
+
+// This test verifies that Audio can be properly captured when injected in the
+// WebAudioSourceProviderImpl.
+TEST_F(HTMLAudioElementCapturerSourceTest, CaptureAudio) {
+  InSequence s;
+
+  base::RunLoop run_loop;
+  base::Closure quit_closure = run_loop.QuitClosure();
+
+  MockMediaStreamAudioSink sink;
+  track()->AddSink(&sink);
+  EXPECT_CALL(sink, OnSetFormat(_)).Times(1);
+  EXPECT_CALL(
+      sink,
+      OnData(AllOf(Property(&media::AudioBus::channels, kNumChannelsForTest),
+                   Property(&media::AudioBus::frames,
+                            kAudioTrackSamplesPerBuffer)),
+             _))
+      .Times(1)
+      .WillOnce(RunClosure(quit_closure));
+
+  std::unique_ptr<media::AudioBus> bus = media::AudioBus::Create(
+      kNumChannelsForTest, kAudioTrackSamplesPerBuffer);
+  InjectAudio(bus.get());
+  run_loop.Run();
+
+  track()->Stop();
+  track()->RemoveSink(&sink);
+}
+
+}  // namespace content
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 478cf81..e0cb4c1 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -8,6 +8,7 @@
 
 #include "base/command_line.h"
 #include "base/files/file_path.h"
+#include "base/guid.h"
 #include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/logging.h"
@@ -61,6 +62,7 @@
 #include "content/renderer/gamepad_shared_memory_reader.h"
 #include "content/renderer/media/audio_decoder.h"
 #include "content/renderer/media/canvas_capture_handler.h"
+#include "content/renderer/media/html_audio_element_capturer_source.h"
 #include "content/renderer/media/html_video_element_capturer_source.h"
 #include "content/renderer/media/image_capture_frame_grabber.h"
 #include "content/renderer/media/media_recorder_handler.h"
@@ -975,6 +977,33 @@
 #endif
 }
 
+void RendererBlinkPlatformImpl::createHTMLAudioElementCapturer(
+    WebMediaStream* web_media_stream,
+    WebMediaPlayer* web_media_player) {
+  DCHECK(web_media_stream);
+  DCHECK(web_media_player);
+
+  blink::WebMediaStreamSource web_media_stream_source;
+  blink::WebMediaStreamTrack web_media_stream_track;
+  const WebString track_id = WebString::fromUTF8(base::GenerateGUID());
+
+  web_media_stream_source.initialize(track_id,
+                                     blink::WebMediaStreamSource::TypeAudio,
+                                     track_id,
+                                     false /* is_remote */);
+  web_media_stream_track.initialize(web_media_stream_source);
+
+  MediaStreamAudioSource* const media_stream_source =
+      HtmlAudioElementCapturerSource::CreateFromWebMediaPlayerImpl(
+          web_media_player);
+
+  // Takes ownership of |media_stream_source|.
+  web_media_stream_source.setExtraData(media_stream_source);
+
+  media_stream_source->ConnectToTrack(web_media_stream_track);
+  web_media_stream->addTrack(web_media_stream_track);
+}
+
 //------------------------------------------------------------------------------
 
 WebImageCaptureFrameGrabber*
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index 85b8fb8b..dab5beff 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -177,6 +177,9 @@
   void createHTMLVideoElementCapturer(
       blink::WebMediaStream* web_media_stream,
       blink::WebMediaPlayer* web_media_player) override;
+  void createHTMLAudioElementCapturer(
+      blink::WebMediaStream* web_media_stream,
+      blink::WebMediaPlayer* web_media_player) override;
   blink::WebImageCaptureFrameGrabber* createImageCaptureFrameGrabber() override;
   blink::WebGraphicsContext3DProvider* createOffscreenGraphicsContext3DProvider(
       const blink::Platform::ContextAttributes& attributes,
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 698024c..3d88c79 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -728,6 +728,7 @@
                     ".",
                     "//content")
     deps += [
+      "//media/blink",
       "//third_party/libjingle:libjingle_webrtc",
       "//third_party/webrtc/base:rtc_base",
       "//third_party/webrtc/modules/desktop_capture:primitives",
diff --git a/media/blink/webaudiosourceprovider_impl.cc b/media/blink/webaudiosourceprovider_impl.cc
index 3f052db..31bb5aa 100644
--- a/media/blink/webaudiosourceprovider_impl.cc
+++ b/media/blink/webaudiosourceprovider_impl.cc
@@ -233,6 +233,10 @@
 void WebAudioSourceProviderImpl::SetCopyAudioCallback(
     const CopyAudioCB& callback) {
   DCHECK(!callback.is_null());
+
+  // Use |sink_lock_| to protect |tee_filter_| too since they go in lockstep.
+  base::AutoLock auto_lock(sink_lock_);
+
   DCHECK(tee_filter_);
   tee_filter_->set_copy_audio_bus_callback(callback);
 }
@@ -242,6 +246,10 @@
   tee_filter_->set_copy_audio_bus_callback(CopyAudioCB());
 }
 
+int WebAudioSourceProviderImpl::RenderForTesting(AudioBus* audio_bus) {
+  return tee_filter_->Render(audio_bus, 0, 0);
+}
+
 void WebAudioSourceProviderImpl::OnSetFormat() {
   base::AutoLock auto_lock(sink_lock_);
   if (!client_)
@@ -251,10 +259,6 @@
   client_->setFormat(tee_filter_->channels(), tee_filter_->sample_rate());
 }
 
-int WebAudioSourceProviderImpl::RenderForTesting(AudioBus* audio_bus) {
-  return tee_filter_->Render(audio_bus, 0, 0);
-}
-
 int WebAudioSourceProviderImpl::TeeFilter::Render(AudioBus* audio_bus,
                                                   uint32_t delay_milliseconds,
                                                   uint32_t frames_skipped) {
diff --git a/media/blink/webaudiosourceprovider_impl.h b/media/blink/webaudiosourceprovider_impl.h
index 6bf3b0a..eea08ca1 100644
--- a/media/blink/webaudiosourceprovider_impl.h
+++ b/media/blink/webaudiosourceprovider_impl.h
@@ -72,6 +72,8 @@
   void SetCopyAudioCallback(const CopyAudioCB& callback);
   void ClearCopyAudioCallback();
 
+  int RenderForTesting(AudioBus* audio_bus);
+
  private:
   friend class WebAudioSourceProviderImplTest;
   ~WebAudioSourceProviderImpl() override;
@@ -79,8 +81,6 @@
   // Calls setFormat() on |client_| from the Blink renderer thread.
   void OnSetFormat();
 
-  int RenderForTesting(AudioBus* audio_bus);
-
   // Used to keep the volume across reconfigurations.
   double volume_;
 
diff --git a/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-EME-content.html b/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-EME-content.html
new file mode 100644
index 0000000..0c2b0db
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-EME-content.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src=../../resources/testharness.js></script>
+<script src=../../resources/testharnessreport.js></script>
+<script>
+
+// Run createStream() on a <video> source with protected content.
+
+test(function() {
+  var video = document.createElement('video');
+
+  assert_equals(video.error, null);
+  assert_equals(video.mediaKeys, null);
+  video.onencrypted = this.step_func_done();
+
+  navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
+    return access.createMediaKeys();
+  }).then(function(mediaKeys) {
+    return video.setMediaKeys(mediaKeys);
+  }).then(function(result) {
+    video.src = "../../media/content/test-encrypted.webm";
+    assert_throws("NotSupportedError",
+                  function() { var stream = video.captureStream(); },
+                  "Cannot create a captureStream() out of a protected <video>");
+  });
+
+}, 'check <video> captureStream() fails on an encrypted/protected media');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-capture.html b/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-capture.html
new file mode 100644
index 0000000..120711b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-capture.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<script src=../../resources/testharness.js></script>
+<script src=../../resources/testharnessreport.js></script>
+<script>
+
+// Run captureStream() on different videos, and assert data is flowing.
+
+var makeAsyncTest = function(filename) {
+  async_test(function(test) {
+    var video = document.createElement('video');
+    video.src = "../../http/tests/media/resources/media-source/webm/" + filename;
+    video.onerror = this.unreached_func("<video> error");
+
+    video.onloadedmetadata = this.step_func(function() {
+      var stream = video.captureStream();
+      var recorder = new MediaRecorder(stream);
+        recorder.ondataavailable = test.step_func_done(function(event) {
+            assert_true(event.data.size > 0, 'Recorded data size should be > 0');
+      });
+
+      recorder.start();
+      video.play();
+
+    });
+
+    video.load();
+  }), "<video>.captureStream() and assert data flows.";
+};
+
+generate_tests(makeAsyncTest,
+               [[ "video-only", "test-v-128k-320x240-24fps-8kfr.webm"],
+                [ "audio-only", "test-a-128k-44100Hz-1ch.webm"],
+                [ "video+audio", "test.webm"]]);
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-creation.html b/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-creation.html
index d481fc8..a9be507 100644
--- a/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-creation.html
+++ b/third_party/WebKit/LayoutTests/fast/mediacapturefromelement/HTMLMediaElementCapture-creation.html
@@ -2,7 +2,8 @@
 <script src=../../resources/testharness.js></script>
 <script src=../../resources/testharnessreport.js></script>
 <script>
-// Run createStream() on <video>s and <audio>s.
+
+// Run captureStream() on <video>/<audio>s and inspect the generated Stream.
 
 test(function() {
   var video = document.createElement('video');
@@ -16,49 +17,30 @@
                 "captureStream() cannot be created out of a source-less <audio>" );
 }, 'check that captureStream() raises an exception on an <audio> with no source.');
 
-test(function() {
-  var video = document.createElement('video');
-  video.src = "file:///super_duper_videos/amazing_video.webm";
-  video.onloadstart = function() {
-    var stream = video.captureStream();
+var makeAsyncTest = function(filename, num_video_tracks, num_audio_tracks) {
+  async_test(function() {
+    var video = document.createElement('video');
+    video.src = "../../http/tests/media/resources/media-source/webm/" + filename;
+    video.onerror = this.unreached_func("<video> error");
 
-    assert_not_equals(stream, null);
-    assert_equals(1, stream.getVideoTracks().length);
-    assert_equals(0, stream.getAudioTracks().length);
-  }
-}, 'check <video> captureStream().');
+    video.onloadedmetadata = this.step_func_done(function() {
+      assert_equals(video.audioTracks.length, num_audio_tracks);
+      assert_equals(video.videoTracks.length, num_video_tracks);
 
-test(function() {
-  var audio = document.createElement('audio');
-  audio.src = "file:///super_duper_videos/amazing_audio_file.webm";
-  audio.onloadstart = function() {
-    var stream = audio.captureStream();
+      var stream = video.captureStream();
+      assert_not_equals(stream, null, "error generating stream");
 
-    // TODO(mcasas): http://crbug.com/575492, implement <audio>.captureStream().
-    assert_equals(stream, null);
-  };
-}, 'check <audio> captureStream().');
+      assert_equals(stream.getAudioTracks().length, num_audio_tracks);
+      assert_equals(stream.getVideoTracks().length, num_video_tracks);
+    });
 
-test(function() {
-  var video = document.createElement('video');
+    video.load();
+  }), "<video>.captureStream()";
+};
 
-  const onEncrypted = this.step_func_done();
-
-  assert_equals(null, video.error);
-  assert_equals(null, video.mediaKeys);
-  video.onencrypted = onEncrypted;
-
-  navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{}]).then(function(access) {
-      return access.createMediaKeys();
-  }).then(function(mediaKeys) {
-      return video.setMediaKeys(mediaKeys);
-  }).then(function(result) {
-      video.src = "../../media/content/test-encrypted.webm";
-      assert_throws("NotSupportedError",
-                    function() { var stream = video.captureStream(); },
-                    "Cannot create a captureStream() out of a protected <video>");
-  });
-
-}, 'check <video> captureStream() fails on an encrypted/protected media');
+generate_tests(makeAsyncTest,
+               [[ "video-only", "test-v-128k-320x240-24fps-8kfr.webm", 1, 0 ],
+                [ "audio-only", "test-a-128k-44100Hz-1ch.webm", 0, 1 ],
+                [ "video+audio", "test.webm", 1, 1 ]]);
 
 </script>
diff --git a/third_party/WebKit/Source/modules/mediacapturefromelement/HTMLMediaElementCapture.cpp b/third_party/WebKit/Source/modules/mediacapturefromelement/HTMLMediaElementCapture.cpp
index 478a74d..aad4dab 100644
--- a/third_party/WebKit/Source/modules/mediacapturefromelement/HTMLMediaElementCapture.cpp
+++ b/third_party/WebKit/Source/modules/mediacapturefromelement/HTMLMediaElementCapture.cpp
@@ -6,6 +6,8 @@
 
 #include "core/dom/ExceptionCode.h"
 #include "core/html/HTMLMediaElement.h"
+#include "core/html/track/AudioTrackList.h"
+#include "core/html/track/VideoTrackList.h"
 #include "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h"
 #include "modules/encryptedmedia/MediaKeys.h"
 #include "modules/mediastream/MediaStream.h"
@@ -37,17 +39,14 @@
         return MediaStream::create(element.getExecutionContext(), MediaStreamRegistry::registry().lookupMediaStreamDescriptor(element.currentSrc().getString()));
     }
 
-    // TODO(mcasas): Only <video> tags are supported at the moment.
-    if (element.isHTMLAudioElement()) {
-        NOTIMPLEMENTED();
-        return nullptr;
-    }
-
     WebMediaStream webStream;
     webStream.initialize(WebVector<WebMediaStreamTrack>(), WebVector<WebMediaStreamTrack>());
     MediaStreamCenter::instance().didCreateMediaStream(webStream);
 
-    Platform::current()->createHTMLVideoElementCapturer(&webStream, element.webMediaPlayer());
+    if (element.hasVideo())
+        Platform::current()->createHTMLVideoElementCapturer(&webStream, element.webMediaPlayer());
+    if (element.hasAudio())
+        Platform::current()->createHTMLAudioElementCapturer(&webStream, element.webMediaPlayer());
     return MediaStream::create(element.getExecutionContext(), webStream);
 }
 
diff --git a/third_party/WebKit/public/platform/Platform.h b/third_party/WebKit/public/platform/Platform.h
index d948c461..751a5fd0 100644
--- a/third_party/WebKit/public/platform/Platform.h
+++ b/third_party/WebKit/public/platform/Platform.h
@@ -515,7 +515,8 @@
 
     // Fills in the WebMediaStream to capture from the WebMediaPlayer identified
     // by the second parameter.
-    virtual void createHTMLVideoElementCapturer(WebMediaStream*, WebMediaPlayer*) {}
+    virtual void createHTMLVideoElementCapturer(WebMediaStream*, WebMediaPlayer*) { }
+    virtual void createHTMLAudioElementCapturer(WebMediaStream*, WebMediaPlayer*) { }
 
     // Creates a WebImageCaptureFrameGrabber to take a snapshot of a Video Tracks.
     // May return null if the functionality is not available.