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

Unified Diff: content/renderer/media/media_stream_audio_source.cc

Issue 1647773002: MediaStream audio sourcing: Bypass audio processing for non-WebRTC cases. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: First attempt Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/renderer/media/media_stream_audio_source.cc
diff --git a/content/renderer/media/media_stream_audio_source.cc b/content/renderer/media/media_stream_audio_source.cc
index c643140e4dbdb6ef5d3d294477a3b7f3cb4cd785..db8fff6ca7914550c8bb20904124f5f13fa5bf01 100644
--- a/content/renderer/media/media_stream_audio_source.cc
+++ b/content/renderer/media/media_stream_audio_source.cc
@@ -4,52 +4,435 @@
#include "content/renderer/media/media_stream_audio_source.h"
+#include <algorithm>
+
+#include "content/public/renderer/media_stream_audio_sink.h"
+#include "content/renderer/media/audio_device_factory.h"
+#include "content/renderer/media/media_stream_audio_processor.h"
#include "content/renderer/render_frame_impl.h"
+#include "media/audio/audio_input_device.h"
+#include "media/audio/audio_parameters.h"
+#include "third_party/WebKit/public/platform/WebMediaStreamSource.h"
namespace content {
+namespace {
+
+// If |frames_per_buffer| is invalid, compute 10 ms worth of frames based on the
+// |sample_rate|.
+int FramesPerBufferOrDefault(int frames_per_buffer, int sample_rate) {
+ if (frames_per_buffer > 0)
+ return frames_per_buffer;
+
+ frames_per_buffer = sample_rate / 100;
+ DCHECK_GT(frames_per_buffer, 0);
+ return frames_per_buffer;
+}
+
+// Returns a media::AudioParameters initialized from the parameters found in
+// |device_info|.
+media::AudioParameters GetParametersFromStreamDeviceInfo(
+ const StreamDeviceInfo& device_info) {
+ const MediaStreamDevice::AudioDeviceParameters& input_params =
+ device_info.device.input;
+ DCHECK_GT(input_params.sample_rate, 0);
+ DCHECK_GT(input_params.channel_layout, 1);
+ return media::AudioParameters(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ static_cast<media::ChannelLayout>(input_params.channel_layout),
+ input_params.sample_rate,
+ 16, // Legacy parameter (data is always in 32-bit float format).
+ FramesPerBufferOrDefault(input_params.frames_per_buffer,
+ input_params.sample_rate));
+}
+
+} // namespace
+
+class MediaStreamAudioSource::AudioTee : public MediaStreamAudioTrack {
+ public:
+ explicit AudioTee(base::WeakPtr<MediaStreamAudioSource> source)
+ : MediaStreamAudioTrack(!source->is_remote_),
+ params_(source->params_),
+ source_(source),
+ is_enabled_(true) {}
+
+ ~AudioTee() final { Stop(); }
+
+ void AddSink(MediaStreamAudioSink* sink) final {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(sink);
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(std::find(sinks_.begin(), sinks_.end(), sink) == sinks_.end());
+ DCHECK(std::find(added_sinks_.begin(), added_sinks_.end(), sink) ==
+ added_sinks_.end());
+ added_sinks_.push_back(sink);
+ }
+ sink->OnReadyStateChanged(blink::WebMediaStreamSource::ReadyStateLive);
+ }
+
+ void RemoveSink(MediaStreamAudioSink* sink) final {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ {
+ base::AutoLock auto_lock(lock_);
+ auto it = std::find(added_sinks_.begin(), added_sinks_.end(), sink);
+ if (it != added_sinks_.end()) {
+ added_sinks_.erase(it);
+ } else {
+ it = std::find(sinks_.begin(), sinks_.end(), sink);
+ if (it != sinks_.end())
+ sinks_.erase(it);
+ }
+ }
+ sink->OnReadyStateChanged(blink::WebMediaStreamSource::ReadyStateEnded);
+ }
+
+ webrtc::AudioTrackInterface* GetAudioAdapter() final { return nullptr; }
+
+ media::AudioParameters GetOutputFormat() const final { return params_; }
+
+ void SetEnabled(bool enabled) final {
+ base::AutoLock auto_lock(lock_);
+ is_enabled_ = enabled;
+ }
+
+ void Stop() final {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (source_)
+ source_->StopAudioDeliveryTo(this);
+
+ std::vector<MediaStreamAudioSink*> zombies;
+ {
+ base::AutoLock auto_lock(lock_);
+ zombies.swap(sinks_);
+ zombies.insert(zombies.end(), added_sinks_.begin(), added_sinks_.end());
+ added_sinks_.clear();
+ }
+ for (MediaStreamAudioSink* zombie : zombies)
+ zombie->OnReadyStateChanged(blink::WebMediaStreamSource::ReadyStateEnded);
+ }
+
+ // Called by the MediaStreamAudioSource to deliver audio data to this track,
+ // which in turn delivers the audio to one or more MediaStreamAudioSinks.
+ void DeliverDataToSinks(const media::AudioBus& audio_bus,
+ base::TimeTicks reference_time) {
+ // Lock sink lists while this audio thread is manipulating the lists and
+ // invoking the OnData() callback for each sink.
+ base::AutoLock auto_lock(lock_);
+
+ // If audio delivery is currently disabled, take no actions.
+ if (!is_enabled_)
+ return;
+
+ // For all newly-added sinks, call OnSetFormat() and move them to the active
+ // sink list.
+ if (!added_sinks_.empty()) {
+ for (MediaStreamAudioSink* sink : added_sinks_)
+ sink->OnSetFormat(params_);
+ sinks_.insert(sinks_.end(), added_sinks_.begin(), added_sinks_.end());
+ added_sinks_.clear();
+ }
+
+ // Deliver the audio data to each sink.
+ for (MediaStreamAudioSink* sink : sinks_)
+ sink->OnData(audio_bus, reference_time);
+ }
+
+ private:
+ // Source audio parameters.
+ const media::AudioParameters params_;
+
+ // A weak reference is held to notify the source when this instance is
+ // stopped.
+ const base::WeakPtr<MediaStreamAudioSource> source_;
+
+ // In debug builds, check that MediaStreamAudioTrack methods are being called
+ // on the same thread.
+ base::ThreadChecker thread_checker_;
+
+ // Lock protects concurrent access to the sink lists below and the
+ // |is_enabled_| state, between the main thread and the audio thread.
+ base::Lock lock_;
+
+ // Sinks added via AddSink() that need to have an initial OnSetFormat() call
+ // on the audio thread before audio data is first delivered.
+ std::vector<MediaStreamAudioSink*> added_sinks_;
+
+ // Sinks that have had OnSetFormat() called and are receiving audio data. On
+ // the audio thread, sinks are taken from |added_sinks_| and added to
+ // |sinks_|.
+ std::vector<MediaStreamAudioSink*> sinks_;
+
+ // When false, delivery of audio data is temporarily halted to the sinks.
+ bool is_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioTee);
+};
+
+MediaStreamAudioSource::MediaStreamAudioSource()
+ : params_(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_STEREO,
+ media::AudioParameters::kAudioCDSampleRate,
+ 16, // Legacy parameter (data is always in 32-bit float format).
+ FramesPerBufferOrDefault(0,
+ media::AudioParameters::kAudioCDSampleRate)),
+ is_remote_(false),
+ consumer_render_frame_id_(-1),
+ current_state_(NULL_SOURCE_NOT_STARTED),
+ pc_factory_(nullptr),
+ weak_factory_(this) {}
+
MediaStreamAudioSource::MediaStreamAudioSource(
- int render_frame_id,
- const StreamDeviceInfo& device_info,
- const SourceStoppedCallback& stop_callback,
- PeerConnectionDependencyFactory* factory)
- : render_frame_id_(render_frame_id), factory_(factory) {
+ int consumer_render_frame_id,
+ const StreamDeviceInfo& device_info)
+ : params_(GetParametersFromStreamDeviceInfo(device_info)),
+ is_remote_(false),
+ consumer_render_frame_id_(consumer_render_frame_id),
+ current_state_(INPUT_DEVICE_NOT_STARTED),
+ pc_factory_(nullptr),
+ weak_factory_(this) {
+ DCHECK(params_.IsValid());
+ DCHECK(RenderFrameImpl::FromRoutingID(consumer_render_frame_id_));
SetDeviceInfo(device_info);
- SetStopCallback(stop_callback);
}
-MediaStreamAudioSource::MediaStreamAudioSource()
- : render_frame_id_(-1), factory_(NULL) {
+MediaStreamAudioSource::MediaStreamAudioSource(
+ const scoped_refptr<media::AudioCapturerSource>& source,
+ int sample_rate,
+ media::ChannelLayout channel_layout,
+ int frames_per_buffer,
+ bool is_remote)
+ : params_(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ channel_layout,
+ sample_rate,
+ 16, // Legacy parameter (data is always in 32-bit float format).
+ FramesPerBufferOrDefault(frames_per_buffer, sample_rate)),
+ is_remote_(is_remote),
+ consumer_render_frame_id_(-1),
+ current_state_(SOURCE_NOT_STARTED),
+ source_(source),
+ pc_factory_(nullptr),
+ weak_factory_(this) {
+ DCHECK(params_.IsValid());
}
-MediaStreamAudioSource::~MediaStreamAudioSource() {}
+MediaStreamAudioSource::~MediaStreamAudioSource() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (current_state_ != STOPPED)
+ StopSource();
+ DCHECK_EQ(current_state_, STOPPED);
+}
+
+bool MediaStreamAudioSource::ConnectToTrack(
+ const blink::WebMediaStreamTrack& track,
+ const blink::WebMediaConstraints& constraints) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!track.isNull());
+
+ // Sanity-check that there is not already a MediaStreamAudioTrack instance
+ // associated with |track|.
+ if (MediaStreamAudioTrack::GetTrack(track)) {
+ NOTREACHED()
+ << "Attempting to connect another source to a WebMediaStreamTrack.";
+ return false;
+ }
+
+ if (!EnsureSourceIsStarted(constraints))
+ return false;
+
+ // Delegate track creation/connection to the WebRTC audio pipeline, if
+ // necessary.
+ if (current_state_ == STARTED_WEBRTC_PIPELINE) {
+ DCHECK(pc_factory_);
+ pc_factory_->CreateLocalAudioTrack(track);
+ return true;
+ }
+
+ // Create an AudioTee to deliver audio data directly from the |source_| to all
+ // sinks managed by the AudioTee. Pass ownership of it to the
+ // WebMediaStreamTrack.
+ DCHECK_EQ(current_state_, STARTED_DEFAULT_PIPELINE);
+ AudioTee* const tee = new AudioTee(weak_factory_.GetWeakPtr());
+ tee->SetEnabled(track.isEnabled());
+ blink::WebMediaStreamTrack mutable_track = track;
+ mutable_track.setExtraData(tee); // Takes ownership.
+ {
+ base::AutoLock auto_lock(lock_);
+ audio_tees_.push_back(tee);
+ }
+ return true;
+}
void MediaStreamAudioSource::DoStopSource() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (current_state_ == STOPPED)
+ return;
+ current_state_ = STOPPED;
+
+ if (source_)
+ source_->Stop();
+
if (audio_capturer_.get())
audio_capturer_->Stop();
}
-void MediaStreamAudioSource::AddTrack(
- const blink::WebMediaStreamTrack& track,
- const blink::WebMediaConstraints& constraints,
- const ConstraintsCallback& callback) {
- // TODO(xians): Properly implement for audio sources.
- if (!local_audio_source_.get()) {
- if (!factory_->InitializeMediaStreamAudioSource(render_frame_id_,
+bool MediaStreamAudioSource::EnsureSourceIsStarted(
+ const blink::WebMediaConstraints& constraints) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ switch (current_state_) {
+ case NULL_SOURCE_NOT_STARTED:
+ if (pc_factory_) {
+ StartWebRtcPipeline(constraints);
+ } else {
+ StartDefaultPipeline();
+ }
+
+ if (current_state_ == STOPPED) {
+ VLOG(1) << "Failed to start null source.";
+ } else {
+ VLOG(1) << "Started null source using "
+ << (current_state_ == STARTED_DEFAULT_PIPELINE ? "default"
+ : "WebRTC")
+ << " audio pipeline, audio parameters={"
+ << params_.AsHumanReadableString() << "}.";
+ }
+ break;
+
+ case INPUT_DEVICE_NOT_STARTED:
+ // Sanity-check that the consuming RenderFrame still exists. This is
+ // required by AudioDeviceFactory.
+ if (!RenderFrameImpl::FromRoutingID(consumer_render_frame_id_))
+ break;
+
+ // If there is no PeerConnectionFactory instance, or if the audio
+ // processing pipeline should not be used, create a direct route for
+ // unmodified audio data from the local source.
+ if (!pc_factory_ ||
+ IsContentCaptureMediaType(device_info().device.type) ||
+ !MediaStreamAudioProcessor::ShouldRouteAudioThroughProcessor(
+ constraints, device_info().device.input.effects)) {
+ source_ = AudioDeviceFactory::NewInputDevice(consumer_render_frame_id_);
+ StartDefaultPipeline();
+ } else if (pc_factory_) {
+ StartWebRtcPipeline(constraints);
+ } else {
+ NOTREACHED() << "Failed to determine which audio pipeline to use.";
+ StopSource();
+ }
+
+ if (current_state_ == STOPPED) {
+ VLOG(1) << "Failed to start input device.";
+ } else {
+ VLOG(1) << "Started input device and using "
+ << (current_state_ == STARTED_DEFAULT_PIPELINE ? "default"
+ : "WebRTC")
+ << " audio pipeline, session_id=" << device_info().session_id
+ << ", audio parameters={" << params_.AsHumanReadableString()
+ << "}.";
+ }
+ break;
+
+ case SOURCE_NOT_STARTED:
+ StartDefaultPipeline();
+ DCHECK_EQ(current_state_, STARTED_DEFAULT_PIPELINE);
+ VLOG(1) << "Started externally-provided "
+ << (is_remote_ ? "remote" : "local")
+ << " source and using default audio pipeline with audio "
+ "parameters={"
+ << params_.AsHumanReadableString() << "}.";
+ break;
+
+ case STARTED_DEFAULT_PIPELINE:
+ case STARTED_WEBRTC_PIPELINE:
+ case STOPPED:
+ break;
+ }
+
+ return current_state_ == STARTED_DEFAULT_PIPELINE ||
+ current_state_ == STARTED_WEBRTC_PIPELINE;
+}
+
+void MediaStreamAudioSource::StartDefaultPipeline() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (source_) {
+ const int session_id = current_state_ == INPUT_DEVICE_NOT_STARTED
+ ? device_info().session_id
+ : -1;
+ source_->Initialize(params_, this, session_id);
+ source_->Start();
+ }
+
+ current_state_ = STARTED_DEFAULT_PIPELINE;
+}
+
+void MediaStreamAudioSource::StartWebRtcPipeline(
+ const blink::WebMediaConstraints& constraints) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(pc_factory_);
+
+ // TODO(xians): Constraints should only apply to the track instead of the
+ // source. See TODO comments in
+ // ./webrtc/peer_connection_dependency_factory.cc.
+ if (pc_factory_->InitializeMediaStreamAudioSource(consumer_render_frame_id_,
constraints, this)) {
- // The source failed to start.
- // UserMediaClientImpl rely on the |stop_callback| to be triggered when
- // the last track is removed from the source. But in this case, the
- // source is is not even started. So we need to fail both adding the
- // track and trigger |stop_callback|.
- callback.Run(this, MEDIA_DEVICE_TRACK_START_FAILURE, "");
- StopSource();
- return;
- }
+ DCHECK(audio_capturer_.get());
+ current_state_ = STARTED_WEBRTC_PIPELINE;
+ } else {
+ // The source failed to start.
+ StopSource();
+ }
+}
+
+void MediaStreamAudioSource::StopAudioDeliveryTo(
+ MediaStreamAudioSource::AudioTee* tee) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(current_state_, STARTED_WEBRTC_PIPELINE);
+
+ // Remove |tee| from the list of AudioTees. After the last AudioTee is
+ // removed, stop the source.
+ bool should_stop_source = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ const auto it = std::find(audio_tees_.begin(), audio_tees_.end(), tee);
+ if (it != audio_tees_.end())
+ audio_tees_.erase(it);
+ should_stop_source = audio_tees_.empty();
}
+ if (should_stop_source)
+ StopSource();
+}
+
+void MediaStreamAudioSource::Capture(const media::AudioBus* audio_bus,
+ int audio_delay_milliseconds,
+ double volume,
+ bool key_pressed) {
+ // TODO(miu): Plumbing is needed to determine the actual capture timestamp
+ // of the audio, instead of just snapshotting TimeTicks::Now(), for proper
+ // audio/video sync. http://crbug.com/335335
+ const base::TimeTicks reference_time =
+ base::TimeTicks::Now() -
+ base::TimeDelta::FromMilliseconds(audio_delay_milliseconds);
+
+ // Deliver the audio data to each tee.
+ base::AutoLock auto_lock(lock_);
+ for (AudioTee* tee : audio_tees_) {
+ // As of this writing, |volume| is only used for an "automatic gain control"
+ // feature that does not apply here. Thus, assume the volume is always 1.0,
+ // which means the audio data should be passed through unmodified.
+ tee->DeliverDataToSinks(*audio_bus, reference_time);
+ }
+}
- factory_->CreateLocalAudioTrack(track);
- callback.Run(this, MEDIA_DEVICE_OK, "");
+void MediaStreamAudioSource::OnCaptureError(const std::string& message) {
+ // As of this writing, this method doesn't get called for anything uselful,
+ // and all other implementors just log the message, but don't disconnect sinks
+ // or take any other action. So, just log the error.
+ LOG(ERROR) << message;
}
} // namespace content

Powered by Google App Engine
This is Rietveld 408576698