| Index: content/browser/media/audio_debug_recording_impl.cc
|
| diff --git a/content/browser/media/audio_debug_recording_impl.cc b/content/browser/media/audio_debug_recording_impl.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7af28019e671f86ce9f1d421e3164da9882a7610
|
| --- /dev/null
|
| +++ b/content/browser/media/audio_debug_recording_impl.cc
|
| @@ -0,0 +1,214 @@
|
| +// Copyright 2015 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/browser/media/audio_debug_recording_impl.h"
|
| +
|
| +#include <utility>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/files/file.h"
|
| +#include "base/logging.h"
|
| +#include "base/memory/weak_ptr.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "content/browser/media/webrtc_internals.h"
|
| +#include "content/browser/renderer_host/media/audio_input_renderer_host.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/browser/render_process_host.h"
|
| +#include "content/public/common/child_process_host.h"
|
| +#include "mojo/public/cpp/system/handle.h"
|
| +#include "third_party/mojo/src/mojo/edk/embedder/embedder.h"
|
| +
|
| +namespace content {
|
| +
|
| +namespace {
|
| +
|
| +const base::FilePath::CharType kAecDumpFileNameAddition[] =
|
| + FILE_PATH_LITERAL("aec_dump");
|
| +
|
| +// Creates a file used for diagnostic echo canceller recordings for handing
|
| +// over to the renderer.
|
| +mojo::ScopedHandle CreateAecDumpFile(base::FilePath file_path) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::FILE);
|
| + base::File dump_file(file_path,
|
| + base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
|
| + if (!dump_file.IsValid()) {
|
| + VLOG(1) << "Could not open AEC dump file, error="
|
| + << dump_file.error_details();
|
| + return mojo::ScopedHandle();
|
| + }
|
| +
|
| + mojo::embedder::PlatformHandle platform_handle_wrapper(
|
| + dump_file.TakePlatformFile());
|
| + mojo::embedder::ScopedPlatformHandle scoped_platform_handle(
|
| + platform_handle_wrapper);
|
| + MojoHandle mojo_handle = MOJO_HANDLE_INVALID;
|
| + MojoResult create_result = mojo::embedder::CreatePlatformHandleWrapper(
|
| + std::move(scoped_platform_handle), &mojo_handle);
|
| + if (create_result != MOJO_RESULT_OK)
|
| + return mojo::ScopedHandle();
|
| +
|
| + return MakeScopedHandle(mojo::Handle(mojo_handle));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +class AudioDebugRecordingImpl::DumpObserver {
|
| + public:
|
| + DumpObserver(AudioDebugRecordingImpl* dump_impl,
|
| + int32_t id,
|
| + AudioDebugRecordingObserverPtr service)
|
| + : audio_debug_recording_impl_(dump_impl),
|
| + id_(id),
|
| + observer_service_(std::move(service)),
|
| + weak_factory_(this) {
|
| + observer_service_.set_connection_error_handler(
|
| + base::Bind(&DumpObserver::OnConnectionError, base::Unretained(this)));
|
| + }
|
| +
|
| +#if defined(OS_WIN)
|
| +#define IntToStringType base::IntToString16
|
| +#else
|
| +#define IntToStringType base::IntToString
|
| +#endif
|
| +
|
| + void Enable(const base::FilePath& file) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + RenderProcessHost* rph =
|
| + RenderProcessHost::FromID(audio_debug_recording_impl_->render_host_id_);
|
| +
|
| + // Process is dead. We're likely to receive a connection error soon.
|
| + if (!rph)
|
| + return;
|
| +
|
| + base::FilePath unique_file =
|
| + file.AddExtension(IntToStringType(base::GetProcId(rph->GetHandle())))
|
| + .AddExtension(kAecDumpFileNameAddition)
|
| + .AddExtension(IntToStringType(id_));
|
| + BrowserThread::PostTaskAndReplyWithResult(
|
| + BrowserThread::FILE, FROM_HERE,
|
| + base::Bind(&CreateAecDumpFile, unique_file),
|
| + base::Bind(&DumpObserver::SendEnable, weak_factory_.GetWeakPtr()));
|
| + }
|
| +
|
| +#undef IntToStringType
|
| +
|
| + void Disable() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + // Posting on the FILE thread and then replying back on the UI thread is
|
| + // only for avoiding races between enable and disable. Nothing is done on
|
| + // the FILE thread.
|
| + BrowserThread::PostTaskAndReply(
|
| + BrowserThread::FILE, FROM_HERE,
|
| + base::Bind(&base::DoNothing),
|
| + base::Bind(&DumpObserver::SendDisable, weak_factory_.GetWeakPtr()));
|
| + }
|
| +
|
| + private:
|
| + void OnConnectionError() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + // Deletes |this|.
|
| + audio_debug_recording_impl_->RemoveObserver(id_);
|
| + }
|
| +
|
| + void SendEnable(mojo::ScopedHandle handle) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + if (!handle.is_valid())
|
| + return;
|
| + observer_service_->Enable(std::move(handle));
|
| + }
|
| +
|
| + void SendDisable() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + observer_service_->Disable();
|
| + }
|
| +
|
| + AudioDebugRecordingImpl* audio_debug_recording_impl_;
|
| + const int32_t id_;
|
| + AudioDebugRecordingObserverPtr observer_service_;
|
| + base::ThreadChecker thread_checker_;
|
| +
|
| + base::WeakPtrFactory<DumpObserver> weak_factory_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DumpObserver);
|
| +};
|
| +
|
| +AudioDebugRecordingImpl::AudioDebugRecordingImpl(
|
| + int render_host_id,
|
| + const scoped_refptr<AudioInputRendererHost>& audio_input_renderer_host,
|
| + const DisconnectCallback& disconnect_cb,
|
| + mojo::InterfaceRequest<AudioDebugRecording> request)
|
| + : render_host_id_(render_host_id),
|
| + audio_input_renderer_host_(audio_input_renderer_host),
|
| + disconnect_cb_(disconnect_cb),
|
| + binding_(this) {
|
| + DCHECK_NE(render_host_id_, ChildProcessHost::kInvalidUniqueID);
|
| + DCHECK(!disconnect_cb_.is_null());
|
| + binding_.Bind(std::move(request));
|
| + binding_.set_connection_error_handler(base::Bind(
|
| + &AudioDebugRecordingImpl::OnConnectionError, base::Unretained(this)));
|
| +}
|
| +
|
| +AudioDebugRecordingImpl::~AudioDebugRecordingImpl() {
|
| +}
|
| +
|
| +void AudioDebugRecordingImpl::EnableAudioDebugRecording(
|
| + const base::FilePath& file) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // Enable AEC dumping for all registered obsevers.
|
| + for (auto& observer : observers_)
|
| + observer.second->Enable(file);
|
| +
|
| + // Enable mic input recording. AudioInputRendererHost is reference counted,
|
| + // so its lifetime is guarantueed during the lifetime of the closure.
|
| + BrowserThread::PostTask(
|
| + BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&AudioInputRendererHost::EnableDebugRecording,
|
| + audio_input_renderer_host_, file));
|
| +}
|
| +
|
| +void AudioDebugRecordingImpl::DisableAudioDebugRecording() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // Disable AEC dumping for all registered obsevers.
|
| + for (auto& observer : observers_)
|
| + observer.second->Disable();
|
| +
|
| + BrowserThread::PostTask(
|
| + BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&AudioInputRendererHost::DisableDebugRecording,
|
| + audio_input_renderer_host_));
|
| +}
|
| +
|
| +void AudioDebugRecordingImpl::RegisterObserver(
|
| + int32_t id,
|
| + AudioDebugRecordingObserverPtr observer) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + DumpObserver* dump_observer = new DumpObserver(this, id, std::move(observer));
|
| + bool inserted = observers_.insert(
|
| + std::make_pair(id, make_scoped_ptr(dump_observer))).second;
|
| + DCHECK(inserted);
|
| +
|
| + if (WebRTCInternals::GetInstance()->IsAudioDebugRecordingsEnabled()) {
|
| + dump_observer->Enable(
|
| + WebRTCInternals::GetInstance()->GetAudioDebugRecordingsFilePath());
|
| + }
|
| +}
|
| +
|
| +void AudioDebugRecordingImpl::OnConnectionError() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + // May delete |this|.
|
| + disconnect_cb_.Run(this);
|
| +}
|
| +
|
| +void AudioDebugRecordingImpl::RemoveObserver(int32_t observer_id) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + auto it = observers_.find(observer_id);
|
| + DCHECK(it != observers_.end());
|
| + observers_.erase(it);
|
| +}
|
| +
|
| +} // namespace content
|
|
|