forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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}
- Loading branch information
1 parent
c612179
commit 77d0d44
Showing
15 changed files
with
445 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
content/renderer/media/html_audio_element_capturer_source.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
63 changes: 63 additions & 0 deletions
63
content/renderer/media/html_audio_element_capturer_source.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
154 changes: 154 additions & 0 deletions
154
content/renderer/media/html_audio_element_capturer_source_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.