From 9f41a27e586a6b557fc171d2f03b332c1338e8d2 Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Tue, 26 Feb 2019 13:23:17 -0500 Subject: [PATCH] Add ability to capture audio output to buffer (see issue #2381) --- BUILD.gn | 4 + cef_paths.gypi | 8 +- cef_paths2.gypi | 1 + include/capi/cef_audio_handler_capi.h | 110 +++ include/capi/cef_client_capi.h | 9 +- include/cef_audio_handler.h | 98 +++ include/cef_client.h | 7 + include/internal/cef_types.h | 111 +++ libcef/browser/audio_mirror_destination.cc | 107 +++ libcef/browser/audio_mirror_destination.h | 80 ++ libcef/browser/audio_push_sink.cc | 172 ++++ libcef/browser/audio_push_sink.h | 54 ++ libcef/browser/browser_host_impl.cc | 28 + libcef/browser/browser_host_impl.h | 7 + libcef_dll/cpptoc/audio_handler_cpptoc.cc | 123 +++ libcef_dll/cpptoc/audio_handler_cpptoc.h | 36 + libcef_dll/cpptoc/client_cpptoc.cc | 20 +- libcef_dll/ctocpp/audio_handler_ctocpp.cc | 115 +++ libcef_dll/ctocpp/audio_handler_ctocpp.h | 51 ++ libcef_dll/ctocpp/client_ctocpp.cc | 18 +- libcef_dll/ctocpp/client_ctocpp.h | 3 +- libcef_dll/libcef_dll.cc | 4 +- libcef_dll/wrapper/libcef_dll_wrapper.cc | 4 +- libcef_dll/wrapper_types.h | 3 +- tests/ceftests/audio_output_unittest.cc | 895 +++++++++++++++++++++ tools/cef_parser.py | 1 + 26 files changed, 2061 insertions(+), 8 deletions(-) create mode 100644 include/capi/cef_audio_handler_capi.h create mode 100644 include/cef_audio_handler.h create mode 100644 libcef/browser/audio_mirror_destination.cc create mode 100644 libcef/browser/audio_mirror_destination.h create mode 100644 libcef/browser/audio_push_sink.cc create mode 100644 libcef/browser/audio_push_sink.h create mode 100644 libcef_dll/cpptoc/audio_handler_cpptoc.cc create mode 100644 libcef_dll/cpptoc/audio_handler_cpptoc.h create mode 100644 libcef_dll/ctocpp/audio_handler_ctocpp.cc create mode 100644 libcef_dll/ctocpp/audio_handler_ctocpp.h create mode 100644 tests/ceftests/audio_output_unittest.cc diff --git a/BUILD.gn b/BUILD.gn index 05a24e0ec..d244eeb11 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -285,6 +285,10 @@ if (is_win) { static_library("libcef_static") { sources = gypi_paths2.includes_common + gypi_paths.autogen_cpp_includes + [ + "libcef/browser/audio_mirror_destination.cc", + "libcef/browser/audio_mirror_destination.h", + "libcef/browser/audio_push_sink.cc", + "libcef/browser/audio_push_sink.h", "libcef/browser/browser_context.cc", "libcef/browser/browser_context.h", "libcef/browser/browser_context_impl.cc", diff --git a/cef_paths.gypi b/cef_paths.gypi index 13ef80dd1..59ce1ec04 100644 --- a/cef_paths.gypi +++ b/cef_paths.gypi @@ -8,7 +8,7 @@ # by hand. See the translator.README.txt file in the tools directory for # more information. # -# $hash=ebb10ec8232ea37044d01a4522248b5055f5c1fc$ +# $hash=61795daa573f965b6b94dea5b8220eeca37f62a0$ # { @@ -16,6 +16,7 @@ 'autogen_cpp_includes': [ 'include/cef_accessibility_handler.h', 'include/cef_app.h', + 'include/cef_audio_handler.h', 'include/cef_auth_callback.h', 'include/cef_browser.h', 'include/cef_browser_process_handler.h', @@ -107,6 +108,7 @@ 'autogen_capi_includes': [ 'include/capi/cef_accessibility_handler_capi.h', 'include/capi/cef_app_capi.h', + 'include/capi/cef_audio_handler_capi.h', 'include/capi/cef_auth_callback_capi.h', 'include/capi/cef_browser_capi.h', 'include/capi/cef_browser_process_handler_capi.h', @@ -200,6 +202,8 @@ 'libcef_dll/ctocpp/accessibility_handler_ctocpp.h', 'libcef_dll/ctocpp/app_ctocpp.cc', 'libcef_dll/ctocpp/app_ctocpp.h', + 'libcef_dll/ctocpp/audio_handler_ctocpp.cc', + 'libcef_dll/ctocpp/audio_handler_ctocpp.h', 'libcef_dll/cpptoc/auth_callback_cpptoc.cc', 'libcef_dll/cpptoc/auth_callback_cpptoc.h', 'libcef_dll/cpptoc/before_download_callback_cpptoc.cc', @@ -482,6 +486,8 @@ 'libcef_dll/cpptoc/accessibility_handler_cpptoc.h', 'libcef_dll/cpptoc/app_cpptoc.cc', 'libcef_dll/cpptoc/app_cpptoc.h', + 'libcef_dll/cpptoc/audio_handler_cpptoc.cc', + 'libcef_dll/cpptoc/audio_handler_cpptoc.h', 'libcef_dll/ctocpp/auth_callback_ctocpp.cc', 'libcef_dll/ctocpp/auth_callback_ctocpp.h', 'libcef_dll/ctocpp/before_download_callback_ctocpp.cc', diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 371c67412..c36ce3358 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -443,6 +443,7 @@ 'tests/cefsimple/simple_handler_linux.cc', ], 'ceftests_sources_common': [ + 'tests/ceftests/audio_output_unittest.cc', 'tests/ceftests/browser_info_map_unittest.cc', 'tests/ceftests/command_line_unittest.cc', 'tests/ceftests/cookie_unittest.cc', diff --git a/include/capi/cef_audio_handler_capi.h b/include/capi/cef_audio_handler_capi.h new file mode 100644 index 000000000..920b7e1e3 --- /dev/null +++ b/include/capi/cef_audio_handler_capi.h @@ -0,0 +1,110 @@ +// Copyright (c) 2019 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// +// $hash=0385a38b6761c5dec07bb89a95a007ad3c11bea6$ +// + +#ifndef CEF_INCLUDE_CAPI_CEF_AUDIO_HANDLER_CAPI_H_ +#define CEF_INCLUDE_CAPI_CEF_AUDIO_HANDLER_CAPI_H_ +#pragma once + +#include "include/capi/cef_base_capi.h" +#include "include/capi/cef_browser_capi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// +// Implement this structure to handle audio events All functions will be called +// on the UI thread +/// +typedef struct _cef_audio_handler_t { + /// + // Base structure. + /// + cef_base_ref_counted_t base; + + /// + // Called when the stream identified by |audio_stream_id| has started. + // |audio_stream_id| will uniquely identify the stream across all future + // cef_audio_handler_t callbacks. OnAudioSteamStopped will always be called + // after OnAudioStreamStarted; both functions may be called multiple times for + // the same stream. |channels| is the number of channels, |channel_layout| is + // the layout of the channels and |sample_rate| is the stream sample rate. + // |frames_per_buffer| is the maximum number of frames that will occur in the + // PCM packet passed to OnAudioStreamPacket. + /// + void(CEF_CALLBACK* on_audio_stream_started)( + struct _cef_audio_handler_t* self, + struct _cef_browser_t* browser, + int audio_stream_id, + int channels, + cef_channel_layout_t channel_layout, + int sample_rate, + int frames_per_buffer); + + /// + // Called when a PCM packet is received for the stream identified by + // |audio_stream_id|. |data| is an array representing the raw PCM data as a + // floating point type, i.e. 4-byte value(s). |frames| is the number of frames + // in the PCM packet. |pts| is the presentation timestamp (in milliseconds + // since the Unix Epoch) and represents the time at which the decompressed + // packet should be presented to the user. Based on |frames| and the + // |channel_layout| value passed to OnAudioStreamStarted you can calculate the + // size of the |data| array in bytes. + /// + void(CEF_CALLBACK* on_audio_stream_packet)(struct _cef_audio_handler_t* self, + struct _cef_browser_t* browser, + int audio_stream_id, + const float** data, + int frames, + int64 pts); + + /// + // Called when the stream identified by |audio_stream_id| has stopped. + // OnAudioSteamStopped will always be called after OnAudioStreamStarted; both + // functions may be called multiple times for the same stream. + /// + void(CEF_CALLBACK* on_audio_stream_stopped)(struct _cef_audio_handler_t* self, + struct _cef_browser_t* browser, + int audio_stream_id); +} cef_audio_handler_t; + +#ifdef __cplusplus +} +#endif + +#endif // CEF_INCLUDE_CAPI_CEF_AUDIO_HANDLER_CAPI_H_ diff --git a/include/capi/cef_client_capi.h b/include/capi/cef_client_capi.h index bcefe4982..802ed79d4 100644 --- a/include/capi/cef_client_capi.h +++ b/include/capi/cef_client_capi.h @@ -33,13 +33,14 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=318ad8428256c2ef980f2f594ace2469001e59bd$ +// $hash=485de431252b72e5516f3f7f16a8d6f416801dfd$ // #ifndef CEF_INCLUDE_CAPI_CEF_CLIENT_CAPI_H_ #define CEF_INCLUDE_CAPI_CEF_CLIENT_CAPI_H_ #pragma once +#include "include/capi/cef_audio_handler_capi.h" #include "include/capi/cef_base_capi.h" #include "include/capi/cef_context_menu_handler_capi.h" #include "include/capi/cef_dialog_handler_capi.h" @@ -69,6 +70,12 @@ typedef struct _cef_client_t { /// cef_base_ref_counted_t base; + /// + // Return the handler for audio rendering events. + /// + struct _cef_audio_handler_t*(CEF_CALLBACK* get_audio_handler)( + struct _cef_client_t* self); + /// // Return the handler for context menus. If no handler is provided the default // implementation will be used. diff --git a/include/cef_audio_handler.h b/include/cef_audio_handler.h new file mode 100644 index 000000000..a60990635 --- /dev/null +++ b/include/cef_audio_handler.h @@ -0,0 +1,98 @@ +// Copyright (c) 2018 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// The contents of this file must follow a specific format in order to +// support the CEF translator tool. See the translator.README.txt file in the +// tools directory for more information. +// + +#ifndef CEF_INCLUDE_CEF_AUDIO_HANDLER_H_ +#define CEF_INCLUDE_CEF_AUDIO_HANDLER_H_ +#pragma once + +#include "include/cef_base.h" +#include "include/cef_browser.h" + +/// +// Implement this interface to handle audio events +// All methods will be called on the UI thread +/// +/*--cef(source=client)--*/ +class CefAudioHandler : public virtual CefBaseRefCounted { + public: + typedef cef_channel_layout_t ChannelLayout; + + /// + // Called when the stream identified by |audio_stream_id| has started. + // |audio_stream_id| will uniquely identify the stream across all future + // CefAudioHandler callbacks. OnAudioSteamStopped will always be called after + // OnAudioStreamStarted; both methods may be called multiple times for the + // same stream. |channels| is the number of channels, |channel_layout| is the + // layout of the channels and |sample_rate| is the stream sample rate. + // |frames_per_buffer| is the maximum number of frames that will occur in the + // PCM packet passed to OnAudioStreamPacket. + /// + /*--cef()--*/ + virtual void OnAudioStreamStarted(CefRefPtr browser, + int audio_stream_id, + int channels, + ChannelLayout channel_layout, + int sample_rate, + int frames_per_buffer) = 0; + + /// + // Called when a PCM packet is received for the stream identified by + // |audio_stream_id|. |data| is an array representing the raw PCM data as a + // floating point type, i.e. 4-byte value(s). |frames| is the number of frames + // in the PCM packet. |pts| is the presentation timestamp (in milliseconds + // since the Unix Epoch) and represents the time at which the decompressed + // packet should be presented to the user. Based on |frames| and the + // |channel_layout| value passed to OnAudioStreamStarted you can calculate the + // size of the |data| array in bytes. + /// + /*--cef()--*/ + virtual void OnAudioStreamPacket(CefRefPtr browser, + int audio_stream_id, + const float** data, + int frames, + int64 pts) = 0; + + /// + // Called when the stream identified by |audio_stream_id| has stopped. + // OnAudioSteamStopped will always be called after OnAudioStreamStarted; both + // methods may be called multiple times for the same stream. + /// + /*--cef()--*/ + virtual void OnAudioStreamStopped(CefRefPtr browser, + int audio_stream_id) = 0; +}; + +#endif // CEF_INCLUDE_CEF_AUDIO_HANDLER_H_ diff --git a/include/cef_client.h b/include/cef_client.h index 59173c778..986d44ae1 100644 --- a/include/cef_client.h +++ b/include/cef_client.h @@ -38,6 +38,7 @@ #define CEF_INCLUDE_CEF_CLIENT_H_ #pragma once +#include "include/cef_audio_handler.h" #include "include/cef_base.h" #include "include/cef_context_menu_handler.h" #include "include/cef_dialog_handler.h" @@ -60,6 +61,12 @@ /*--cef(source=client,no_debugct_check)--*/ class CefClient : public virtual CefBaseRefCounted { public: + /// + // Return the handler for audio rendering events. + /// + /*--cef()--*/ + virtual CefRefPtr GetAudioHandler() { return NULL; } + /// // Return the handler for context menus. If no handler is provided the default // implementation will be used. diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index a1d162bb6..11df1b34f 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -2993,6 +2993,117 @@ typedef struct _cef_composition_underline_t { int thick; } cef_composition_underline_t; +/// +// Enumerates the various representations of the ordering of audio channels. +// Logged to UMA, so never reuse a value, always add new/greater ones! +// See media\base\channel_layout.h +/// +typedef enum { + CEF_CHANNEL_LAYOUT_NONE = 0, + CEF_CHANNEL_LAYOUT_UNSUPPORTED = 1, + + // Front C + CEF_CHANNEL_LAYOUT_MONO = 2, + + // Front L, Front R + CEF_CHANNEL_LAYOUT_STEREO = 3, + + // Front L, Front R, Back C + CEF_CHANNEL_LAYOUT_2_1 = 4, + + // Front L, Front R, Front C + CEF_CHANNEL_LAYOUT_SURROUND = 5, + + // Front L, Front R, Front C, Back C + CEF_CHANNEL_LAYOUT_4_0 = 6, + + // Front L, Front R, Side L, Side R + CEF_CHANNEL_LAYOUT_2_2 = 7, + + // Front L, Front R, Back L, Back R + CEF_CHANNEL_LAYOUT_QUAD = 8, + + // Front L, Front R, Front C, Side L, Side R + CEF_CHANNEL_LAYOUT_5_0 = 9, + + // Front L, Front R, Front C, LFE, Side L, Side R + CEF_CHANNEL_LAYOUT_5_1 = 10, + + // Front L, Front R, Front C, Back L, Back R + CEF_CHANNEL_LAYOUT_5_0_BACK = 11, + + // Front L, Front R, Front C, LFE, Back L, Back R + CEF_CHANNEL_LAYOUT_5_1_BACK = 12, + + // Front L, Front R, Front C, Side L, Side R, Back L, Back R + CEF_CHANNEL_LAYOUT_7_0 = 13, + + // Front L, Front R, Front C, LFE, Side L, Side R, Back L, Back R + CEF_CHANNEL_LAYOUT_7_1 = 14, + + // Front L, Front R, Front C, LFE, Side L, Side R, Front LofC, Front RofC + CEF_CHANNEL_LAYOUT_7_1_WIDE = 15, + + // Stereo L, Stereo R + CEF_CHANNEL_LAYOUT_STEREO_DOWNMIX = 16, + + // Stereo L, Stereo R, LFE + CEF_CHANNEL_LAYOUT_2POINT1 = 17, + + // Stereo L, Stereo R, Front C, LFE + CEF_CHANNEL_LAYOUT_3_1 = 18, + + // Stereo L, Stereo R, Front C, Rear C, LFE + CEF_CHANNEL_LAYOUT_4_1 = 19, + + // Stereo L, Stereo R, Front C, Side L, Side R, Back C + CEF_CHANNEL_LAYOUT_6_0 = 20, + + // Stereo L, Stereo R, Side L, Side R, Front LofC, Front RofC + CEF_CHANNEL_LAYOUT_6_0_FRONT = 21, + + // Stereo L, Stereo R, Front C, Rear L, Rear R, Rear C + CEF_CHANNEL_LAYOUT_HEXAGONAL = 22, + + // Stereo L, Stereo R, Front C, LFE, Side L, Side R, Rear Center + CEF_CHANNEL_LAYOUT_6_1 = 23, + + // Stereo L, Stereo R, Front C, LFE, Back L, Back R, Rear Center + CEF_CHANNEL_LAYOUT_6_1_BACK = 24, + + // Stereo L, Stereo R, Side L, Side R, Front LofC, Front RofC, LFE + CEF_CHANNEL_LAYOUT_6_1_FRONT = 25, + + // Front L, Front R, Front C, Side L, Side R, Front LofC, Front RofC + CEF_CHANNEL_LAYOUT_7_0_FRONT = 26, + + // Front L, Front R, Front C, LFE, Back L, Back R, Front LofC, Front RofC + CEF_CHANNEL_LAYOUT_7_1_WIDE_BACK = 27, + + // Front L, Front R, Front C, Side L, Side R, Rear L, Back R, Back C. + CEF_CHANNEL_LAYOUT_OCTAGONAL = 28, + + // Channels are not explicitly mapped to speakers. + CEF_CHANNEL_LAYOUT_DISCRETE = 29, + + // Front L, Front R, Front C. Front C contains the keyboard mic audio. This + // layout is only intended for input for WebRTC. The Front C channel + // is stripped away in the WebRTC audio input pipeline and never seen outside + // of that. + CEF_CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC = 30, + + // Front L, Front R, Side L, Side R, LFE + CEF_CHANNEL_LAYOUT_4_1_QUAD_SIDE = 31, + + // Actual channel layout is specified in the bitstream and the actual channel + // count is unknown at Chromium media pipeline level (useful for audio + // pass-through mode). + CEF_CHANNEL_LAYOUT_BITSTREAM = 32, + + // Max value, must always equal the largest entry ever logged. + CEF_CHANNEL_LAYOUT_MAX = CEF_CHANNEL_LAYOUT_BITSTREAM +} cef_channel_layout_t; + #ifdef __cplusplus } #endif diff --git a/libcef/browser/audio_mirror_destination.cc b/libcef/browser/audio_mirror_destination.cc new file mode 100644 index 000000000..020d146cc --- /dev/null +++ b/libcef/browser/audio_mirror_destination.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2018 The Chromium Embedded Framework Authors. +// Portions 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 "libcef/browser/audio_mirror_destination.h" +#include "libcef/browser/audio_push_sink.h" + +#include "base/task/post_task.h" +#include "content/public/browser/browser_task_traits.h" +#include "media/base/bind_to_current_loop.h" + +CefAudioMirrorDestination::CefAudioMirrorDestination( + CefRefPtr browser, + CefRefPtr cef_audio_handler, + content::AudioMirroringManager* mirroring_manager) + : browser_(browser), + cef_audio_handler_(cef_audio_handler), + mirroring_manager_(mirroring_manager) { + DCHECK(mirroring_manager_); + + thread_checker_.DetachFromThread(); +} + +void CefAudioMirrorDestination::Start() { + DCHECK(thread_checker_.CalledOnValidThread()); + + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&content::AudioMirroringManager::StartMirroring, + base::Unretained(mirroring_manager_), + base::Unretained(this))); +} + +void CefAudioMirrorDestination::Stop() { + DCHECK(thread_checker_.CalledOnValidThread()); + + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&content::AudioMirroringManager::StopMirroring, + base::Unretained(mirroring_manager_), + base::Unretained(this))); +} + +// Asynchronously query whether this MirroringDestination wants to consume +// audio sourced from each of the |candidates|. |results_callback| is run +// to indicate which of them (or none) should have audio routed to this +// MirroringDestination. The second parameter of |results_callback| +// indicates whether the MirroringDestination wants either: 1) exclusive +// access to a diverted audio flow versus 2) a duplicate copy of the audio +// flow. |results_callback| must be run on the same thread as the one that +// called QueryForMatches(). +void CefAudioMirrorDestination::QueryForMatches( + const std::set& candidates, + MatchesCallback results_callback) { + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce(&CefAudioMirrorDestination::QueryForMatchesOnUIThread, + base::Unretained(this), candidates, + media::BindToCurrentLoop(std::move(results_callback)))); +} + +void CefAudioMirrorDestination::QueryForMatchesOnUIThread( + const std::set& candidates, + MatchesCallback results_callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + std::set matches; + for (auto& candidate : candidates) { + CefRefPtr browser = + CefBrowserHostImpl::GetBrowserForFrame(candidate.child_id, + candidate.frame_routing_id); + if (browser == browser_) { + matches.insert(candidate); + } + } + + std::move(results_callback).Run(matches, true); +} + +// Create a consumer of audio data in the format specified by |params|, and +// connect it as an input to mirroring. This is used to provide +// MirroringDestination with exclusive access to pull the audio flow from +// the source. When Close() is called on the returned AudioOutputStream, the +// input is disconnected and the object becomes invalid. +media::AudioOutputStream* CefAudioMirrorDestination::AddInput( + const media::AudioParameters& params) { + // TODO Check and add usage on CEF + return nullptr; +} + +// Create a consumer of audio data in the format specified by |params|, and +// connect it as an input to mirroring. This is used to provide +// MirroringDestination with duplicate audio data, which is pushed from the +// main audio flow. When Close() is called on the returned AudioPushSink, +// the input is disconnected and the object becomes invalid. +media::AudioPushSink* CefAudioMirrorDestination::AddPushInput( + const media::AudioParameters& params) { + return new CefAudioPushSink( + params, browser_, cef_audio_handler_, + base::Bind(&CefAudioMirrorDestination::ReleasePushInput, + base::Unretained(this))); +} + +void CefAudioMirrorDestination::ReleasePushInput(CefAudioPushSink* sink) { + delete sink; +} diff --git a/libcef/browser/audio_mirror_destination.h b/libcef/browser/audio_mirror_destination.h new file mode 100644 index 000000000..68dc93047 --- /dev/null +++ b/libcef/browser/audio_mirror_destination.h @@ -0,0 +1,80 @@ +// Copyright (c) 2018 The Chromium Embedded Framework Authors. +// Portions 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. + +#ifndef CEF_LIBCEF_BROWSER_AUDIO_MIRROR_DESTINATION_H_ +#define CEF_LIBCEF_BROWSER_AUDIO_MIRROR_DESTINATION_H_ +#pragma once + +#include "include/cef_audio_handler.h" +#include "include/cef_base.h" +#include "libcef/browser/browser_host_impl.h" + +#include "base/callback.h" +#include "base/threading/thread_checker.h" +#include "content/browser/media/capture/audio_mirroring_manager.h" +#include "media/audio/audio_io.h" +#include "media/base/audio_converter.h" +#include "media/base/audio_parameters.h" + +class CefAudioPushSink; +class CefBrowserHostImpl; + +class CefAudioMirrorDestination + : public content::AudioMirroringManager::MirroringDestination { + public: + CefAudioMirrorDestination(CefRefPtr browser, + CefRefPtr cef_audio_handler, + content::AudioMirroringManager* mirroring_manager); + + virtual ~CefAudioMirrorDestination() = default; + + // Start mirroring. This needs to be triggered on the IO thread. + void Start(); + + // Stop mirroring. This needs to be triggered on the IO thread. + void Stop(); + + // Asynchronously query whether this MirroringDestination wants to consume + // audio sourced from each of the |candidates|. |results_callback| is run + // to indicate which of them (or none) should have audio routed to this + // MirroringDestination. The second parameter of |results_callback| + // indicates whether the MirroringDestination wants either: 1) exclusive + // access to a diverted audio flow versus 2) a duplicate copy of the audio + // flow. |results_callback| must be run on the same thread as the one that + // called QueryForMatches(). + void QueryForMatches( + const std::set& candidates, + MatchesCallback results_callback) override; + + // Create a consumer of audio data in the format specified by |params|, and + // connect it as an input to mirroring. This is used to provide + // MirroringDestination with exclusive access to pull the audio flow from + // the source. When Close() is called on the returned AudioOutputStream, the + // input is disconnected and the object becomes invalid. + media::AudioOutputStream* AddInput( + const media::AudioParameters& params) override; + + // Create a consumer of audio data in the format specified by |params|, and + // connect it as an input to mirroring. This is used to provide + // MirroringDestination with duplicate audio data, which is pushed from the + // main audio flow. When Close() is called on the returned AudioPushSink, + // the input is disconnected and the object becomes invalid. + media::AudioPushSink* AddPushInput( + const media::AudioParameters& params) override; + + private: + void QueryForMatchesOnUIThread( + const std::set& candidates, + MatchesCallback results_callback); + void ReleasePushInput(CefAudioPushSink* sink); + + CefRefPtr browser_; + CefRefPtr cef_audio_handler_; + content::AudioMirroringManager* mirroring_manager_; + + base::ThreadChecker thread_checker_; +}; + +#endif // CEF_LIBCEF_BROWSER_AUDIO_MIRROR_DESTINATION_H_ diff --git a/libcef/browser/audio_push_sink.cc b/libcef/browser/audio_push_sink.cc new file mode 100644 index 000000000..1777b3713 --- /dev/null +++ b/libcef/browser/audio_push_sink.cc @@ -0,0 +1,172 @@ +// Copyright (c) 2018 The Chromium Embedded Framework Authors. +// Portions 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 "libcef/browser/audio_push_sink.h" +#include "libcef/browser/thread_util.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "media/base/audio_sample_types.h" +#include "media/base/data_buffer.h" + +using namespace media; + +namespace { +cef_channel_layout_t TranslateChannelLayout(ChannelLayout channel) { + switch (channel) { + case CHANNEL_LAYOUT_UNSUPPORTED: + return CEF_CHANNEL_LAYOUT_UNSUPPORTED; + case CHANNEL_LAYOUT_MONO: + return CEF_CHANNEL_LAYOUT_MONO; + case CHANNEL_LAYOUT_STEREO: + return CEF_CHANNEL_LAYOUT_STEREO; + case CHANNEL_LAYOUT_2_1: + return CEF_CHANNEL_LAYOUT_2_1; + case CHANNEL_LAYOUT_SURROUND: + return CEF_CHANNEL_LAYOUT_SURROUND; + case CHANNEL_LAYOUT_4_0: + return CEF_CHANNEL_LAYOUT_4_0; + case CHANNEL_LAYOUT_2_2: + return CEF_CHANNEL_LAYOUT_2_2; + case CHANNEL_LAYOUT_QUAD: + return CEF_CHANNEL_LAYOUT_QUAD; + case CHANNEL_LAYOUT_5_0: + return CEF_CHANNEL_LAYOUT_5_0; + case CHANNEL_LAYOUT_5_1: + return CEF_CHANNEL_LAYOUT_5_1; + case CHANNEL_LAYOUT_5_0_BACK: + return CEF_CHANNEL_LAYOUT_5_0_BACK; + case CHANNEL_LAYOUT_5_1_BACK: + return CEF_CHANNEL_LAYOUT_5_1_BACK; + case CHANNEL_LAYOUT_7_0: + return CEF_CHANNEL_LAYOUT_7_0; + case CHANNEL_LAYOUT_7_1: + return CEF_CHANNEL_LAYOUT_7_1; + case CHANNEL_LAYOUT_7_1_WIDE: + return CEF_CHANNEL_LAYOUT_7_1_WIDE; + case CHANNEL_LAYOUT_STEREO_DOWNMIX: + return CEF_CHANNEL_LAYOUT_STEREO_DOWNMIX; + case CHANNEL_LAYOUT_2POINT1: + return CEF_CHANNEL_LAYOUT_2POINT1; + case CHANNEL_LAYOUT_3_1: + return CEF_CHANNEL_LAYOUT_3_1; + case CHANNEL_LAYOUT_4_1: + return CEF_CHANNEL_LAYOUT_4_1; + case CHANNEL_LAYOUT_6_0: + return CEF_CHANNEL_LAYOUT_6_0; + case CHANNEL_LAYOUT_6_0_FRONT: + return CEF_CHANNEL_LAYOUT_6_0_FRONT; + case CHANNEL_LAYOUT_HEXAGONAL: + return CEF_CHANNEL_LAYOUT_HEXAGONAL; + case CHANNEL_LAYOUT_6_1: + return CEF_CHANNEL_LAYOUT_6_1; + case CHANNEL_LAYOUT_6_1_BACK: + return CEF_CHANNEL_LAYOUT_6_1_BACK; + case CHANNEL_LAYOUT_6_1_FRONT: + return CEF_CHANNEL_LAYOUT_6_1_FRONT; + case CHANNEL_LAYOUT_7_0_FRONT: + return CEF_CHANNEL_LAYOUT_7_0_FRONT; + case CHANNEL_LAYOUT_7_1_WIDE_BACK: + return CEF_CHANNEL_LAYOUT_7_1_WIDE_BACK; + case CHANNEL_LAYOUT_OCTAGONAL: + return CEF_CHANNEL_LAYOUT_OCTAGONAL; + case CHANNEL_LAYOUT_DISCRETE: + return CEF_CHANNEL_LAYOUT_DISCRETE; + case CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC: + return CEF_CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC; + case CHANNEL_LAYOUT_4_1_QUAD_SIDE: + return CEF_CHANNEL_LAYOUT_4_1_QUAD_SIDE; + case CHANNEL_LAYOUT_BITSTREAM: + return CEF_CHANNEL_LAYOUT_BITSTREAM; + case CHANNEL_LAYOUT_NONE: + return CEF_CHANNEL_LAYOUT_NONE; + } + return CEF_CHANNEL_LAYOUT_NONE; +} +} // namespace + +int CefAudioPushSink::audio_stream_id = 0; + +CefAudioPushSink::CefAudioPushSink(const AudioParameters& params, + CefRefPtr browser, + CefRefPtr cef_audio_handler, + const CloseCallback& callback) + : params_(params), + browser_(browser), + cef_audio_handler_(cef_audio_handler), + close_callback_(callback), + stop_stream_(false), + audio_stream_id_(++audio_stream_id) { + // Verify that our enum matches Chromium's values. + static_assert( + static_cast(CEF_CHANNEL_LAYOUT_MAX) == + static_cast(CHANNEL_LAYOUT_MAX), + "cef_channel_layout_t must match the ChannelLayout enum in Chromium"); + + DCHECK(params_.IsValid()); + DCHECK(browser); + DCHECK(cef_audio_handler); + + // VAOS can be constructed on any thread, but will DCHECK that all + // AudioOutputStream methods are called from the same thread. + thread_checker_.DetachFromThread(); + + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce(&CefAudioPushSink::InitOnUIThread, + base::Unretained(this))); +} + +CefAudioPushSink::~CefAudioPushSink() = default; + +void CefAudioPushSink::InitOnUIThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + + cef_audio_handler_->OnAudioStreamStarted( + browser_.get(), audio_stream_id_, params_.channels(), + TranslateChannelLayout(params_.channel_layout()), params_.sample_rate(), + params_.frames_per_buffer()); +} + +void CefAudioPushSink::OnData(std::unique_ptr source, + base::TimeTicks reference_time) { + // early exit if stream already stopped + if (stop_stream_) + return; + + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefAudioPushSink::OnData, + base::Unretained(this), + std::move(source), reference_time)); + return; + } + + const int channels = source->channels(); + std::vector data(channels); + for (int c = 0; c < channels; ++c) { + data[c] = source->channel(c); + } + // Add the packet to the buffer. + base::TimeDelta pts = reference_time - base::TimeTicks::UnixEpoch(); + cef_audio_handler_->OnAudioStreamPacket(browser_.get(), audio_stream_id_, + data.data(), source->frames(), + pts.InMilliseconds()); +} + +void CefAudioPushSink::Close() { + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefAudioPushSink::Close, + base::Unretained(this))); + return; + } + + if (!stop_stream_) { + stop_stream_ = true; + cef_audio_handler_->OnAudioStreamStopped(browser_.get(), audio_stream_id_); + + const CloseCallback& cb = base::ResetAndReturn(&close_callback_); + if (!cb.is_null()) + cb.Run(this); + } +} diff --git a/libcef/browser/audio_push_sink.h b/libcef/browser/audio_push_sink.h new file mode 100644 index 000000000..097c12729 --- /dev/null +++ b/libcef/browser/audio_push_sink.h @@ -0,0 +1,54 @@ +// Copyright (c) 2018 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 CEF_LIBCEF_BROWSER_AUDIO_PUSH_SINK_H_ +#define CEF_LIBCEF_BROWSER_AUDIO_PUSH_SINK_H_ +#pragma once + +#include "include/cef_audio_handler.h" +#include "include/cef_base.h" +#include "libcef/browser/browser_host_impl.h" + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_checker.h" +#include "media/audio/audio_io.h" +#include "media/base/audio_converter.h" +#include "media/base/audio_parameters.h" +#include "media/base/channel_layout.h" + +class CefAudioPushSink : public media::AudioPushSink { + public: + typedef base::Callback CloseCallback; + + CefAudioPushSink(const media::AudioParameters& params, + CefRefPtr browser, + CefRefPtr cef_audio_handler, + const CloseCallback& callback); + virtual ~CefAudioPushSink(); + + void OnData(std::unique_ptr source, + base::TimeTicks reference_time) override; + void Close() override; + + private: + void InitOnUIThread(); + + const media::AudioParameters params_; + CefRefPtr browser_; + CefRefPtr cef_audio_handler_; + CloseCallback close_callback_; + + base::ThreadChecker thread_checker_; + + bool stop_stream_; + int audio_stream_id_; + + static int audio_stream_id; + + DISALLOW_COPY_AND_ASSIGN(CefAudioPushSink); +}; + +#endif // CEF_LIBCEF_BROWSER_AUDIO_PUSH_SINK_H_ diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 019f26fda..1b9e5c798 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -8,6 +8,7 @@ #include #include +#include "libcef/browser/audio_mirror_destination.h" #include "libcef/browser/browser_context_impl.h" #include "libcef/browser/browser_info.h" #include "libcef/browser/browser_info_manager.h" @@ -49,6 +50,7 @@ #include "chrome/browser/ui/prefs/prefs_tab_helper.h" #include "components/zoom/zoom_controller.h" #include "content/browser/gpu/compositor_util.h" +#include "content/browser/media/capture/audio_mirroring_manager.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/widget_messages.h" #include "content/public/browser/desktop_media_id.h" @@ -1582,6 +1584,8 @@ void CefBrowserHostImpl::DestroyBrowser() { menu_manager_->Destroy(); DestroyExtensionHost(); + StopAudioMirroring(); + // Notify any observers that may have state associated with this browser. for (auto& observer : observers_) observer.OnBrowserDestroyed(this); @@ -3038,6 +3042,28 @@ bool CefBrowserHostImpl::HasObserver(Observer* observer) const { return observers_.HasObserver(observer); } +bool CefBrowserHostImpl::StartAudioMirroring() { + if (client_.get()) { + CefRefPtr audio_handler = client_->GetAudioHandler(); + if (audio_handler.get()) { + audio_mirror_destination_.reset(new CefAudioMirrorDestination( + this, audio_handler, content::AudioMirroringManager::GetInstance())); + audio_mirror_destination_->Start(); + return true; + } + } + return false; +} + +bool CefBrowserHostImpl::StopAudioMirroring() { + if (audio_mirror_destination_.get()) { + audio_mirror_destination_->Stop(); + audio_mirror_destination_.reset(); + return true; + } + return false; +} + CefBrowserHostImpl::NavigationLock::NavigationLock( CefRefPtr browser) : browser_(browser) { @@ -3290,6 +3316,8 @@ CefBrowserHostImpl::CefBrowserHostImpl( // Make sure RenderViewCreated is called at least one time. RenderViewCreated(web_contents->GetRenderViewHost()); + StartAudioMirroring(); + // Associate the platform delegate with this browser. platform_delegate_->BrowserCreated(this); } diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 8e10b876d..b14e36dc3 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -16,6 +16,7 @@ #include "include/cef_client.h" #include "include/cef_frame.h" #include "include/views/cef_browser_view.h" +#include "libcef/browser/audio_mirror_destination.h" #include "libcef/browser/browser_info.h" #include "libcef/browser/file_dialog_manager.h" #include "libcef/browser/frame_host_impl.h" @@ -56,6 +57,7 @@ class Widget; struct Cef_DraggableRegion_Params; struct Cef_Request_Params; struct Cef_Response_Params; +class CefAudioMirrorDestination; class CefBrowserInfo; class CefBrowserPlatformDelegate; class CefDevToolsFrontend; @@ -526,6 +528,8 @@ class CefBrowserHostImpl : public CefBrowserHost, void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); bool HasObserver(Observer* observer) const; + bool StartAudioMirroring(); + bool StopAudioMirroring(); class NavigationLock final { private: @@ -765,6 +769,9 @@ class CefBrowserHostImpl : public CefBrowserHost, CefRefPtr extension_; bool is_background_host_ = false; + // Used to mirror audio streams + std::unique_ptr audio_mirror_destination_; + // Used with auto-resize. bool auto_resize_enabled_ = false; gfx::Size auto_resize_min_; diff --git a/libcef_dll/cpptoc/audio_handler_cpptoc.cc b/libcef_dll/cpptoc/audio_handler_cpptoc.cc new file mode 100644 index 000000000..959d92e1f --- /dev/null +++ b/libcef_dll/cpptoc/audio_handler_cpptoc.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=adcf6805d35059945c09825ebb73b55434727d97$ +// + +#include "libcef_dll/cpptoc/audio_handler_cpptoc.h" +#include "libcef_dll/ctocpp/browser_ctocpp.h" + +namespace { + +// MEMBER FUNCTIONS - Body may be edited by hand. + +void CEF_CALLBACK +audio_handler_on_audio_stream_started(struct _cef_audio_handler_t* self, + struct _cef_browser_t* browser, + int audio_stream_id, + int channels, + cef_channel_layout_t channel_layout, + int sample_rate, + int frames_per_buffer) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + + // Execute + CefAudioHandlerCppToC::Get(self)->OnAudioStreamStarted( + CefBrowserCToCpp::Wrap(browser), audio_stream_id, channels, + channel_layout, sample_rate, frames_per_buffer); +} + +void CEF_CALLBACK +audio_handler_on_audio_stream_packet(struct _cef_audio_handler_t* self, + struct _cef_browser_t* browser, + int audio_stream_id, + const float** data, + int frames, + int64 pts) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + // Verify param: data; type: simple_byaddr + DCHECK(data); + if (!data) + return; + + // Execute + CefAudioHandlerCppToC::Get(self)->OnAudioStreamPacket( + CefBrowserCToCpp::Wrap(browser), audio_stream_id, data, frames, pts); +} + +void CEF_CALLBACK +audio_handler_on_audio_stream_stopped(struct _cef_audio_handler_t* self, + struct _cef_browser_t* browser, + int audio_stream_id) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + + // Execute + CefAudioHandlerCppToC::Get(self)->OnAudioStreamStopped( + CefBrowserCToCpp::Wrap(browser), audio_stream_id); +} + +} // namespace + +// CONSTRUCTOR - Do not edit by hand. + +CefAudioHandlerCppToC::CefAudioHandlerCppToC() { + GetStruct()->on_audio_stream_started = audio_handler_on_audio_stream_started; + GetStruct()->on_audio_stream_packet = audio_handler_on_audio_stream_packet; + GetStruct()->on_audio_stream_stopped = audio_handler_on_audio_stream_stopped; +} + +template <> +CefRefPtr CefCppToCRefCounted< + CefAudioHandlerCppToC, + CefAudioHandler, + cef_audio_handler_t>::UnwrapDerived(CefWrapperType type, + cef_audio_handler_t* s) { + NOTREACHED() << "Unexpected class type: " << type; + return NULL; +} + +#if DCHECK_IS_ON() +template <> +base::AtomicRefCount CefCppToCRefCounted::DebugObjCt + ATOMIC_DECLARATION; +#endif + +template <> +CefWrapperType CefCppToCRefCounted::kWrapperType = + WT_AUDIO_HANDLER; diff --git a/libcef_dll/cpptoc/audio_handler_cpptoc.h b/libcef_dll/cpptoc/audio_handler_cpptoc.h new file mode 100644 index 000000000..1e6d23807 --- /dev/null +++ b/libcef_dll/cpptoc/audio_handler_cpptoc.h @@ -0,0 +1,36 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=7b72089e8ee155c1b2dee881fb63ab65f6c0fc71$ +// + +#ifndef CEF_LIBCEF_DLL_CPPTOC_AUDIO_HANDLER_CPPTOC_H_ +#define CEF_LIBCEF_DLL_CPPTOC_AUDIO_HANDLER_CPPTOC_H_ +#pragma once + +#if !defined(WRAPPING_CEF_SHARED) +#error This file can be included wrapper-side only +#endif + +#include "include/capi/cef_audio_handler_capi.h" +#include "include/cef_audio_handler.h" +#include "libcef_dll/cpptoc/cpptoc_ref_counted.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed wrapper-side only. +class CefAudioHandlerCppToC : public CefCppToCRefCounted { + public: + CefAudioHandlerCppToC(); +}; + +#endif // CEF_LIBCEF_DLL_CPPTOC_AUDIO_HANDLER_CPPTOC_H_ diff --git a/libcef_dll/cpptoc/client_cpptoc.cc b/libcef_dll/cpptoc/client_cpptoc.cc index d466a90b1..5cb1ebb6c 100644 --- a/libcef_dll/cpptoc/client_cpptoc.cc +++ b/libcef_dll/cpptoc/client_cpptoc.cc @@ -9,10 +9,11 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=9a46adab7d328c9d33c759cc02f28d15fb23e2c2$ +// $hash=8c46367b04ebcf4fd178da887dd9cb691d3d0e27$ // #include "libcef_dll/cpptoc/client_cpptoc.h" +#include "libcef_dll/cpptoc/audio_handler_cpptoc.h" #include "libcef_dll/cpptoc/context_menu_handler_cpptoc.h" #include "libcef_dll/cpptoc/dialog_handler_cpptoc.h" #include "libcef_dll/cpptoc/display_handler_cpptoc.h" @@ -33,6 +34,22 @@ namespace { // MEMBER FUNCTIONS - Body may be edited by hand. +cef_audio_handler_t* CEF_CALLBACK +client_get_audio_handler(struct _cef_client_t* self) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return NULL; + + // Execute + CefRefPtr _retval = + CefClientCppToC::Get(self)->GetAudioHandler(); + + // Return type: refptr_same + return CefAudioHandlerCppToC::Wrap(_retval); +} + struct _cef_context_menu_handler_t* CEF_CALLBACK client_get_context_menu_handler(struct _cef_client_t* self) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING @@ -274,6 +291,7 @@ client_on_process_message_received(struct _cef_client_t* self, // CONSTRUCTOR - Do not edit by hand. CefClientCppToC::CefClientCppToC() { + GetStruct()->get_audio_handler = client_get_audio_handler; GetStruct()->get_context_menu_handler = client_get_context_menu_handler; GetStruct()->get_dialog_handler = client_get_dialog_handler; GetStruct()->get_display_handler = client_get_display_handler; diff --git a/libcef_dll/ctocpp/audio_handler_ctocpp.cc b/libcef_dll/ctocpp/audio_handler_ctocpp.cc new file mode 100644 index 000000000..983273603 --- /dev/null +++ b/libcef_dll/ctocpp/audio_handler_ctocpp.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=280f9ca344ead60b5e6bde70bdd9b155f0a8060a$ +// + +#include "libcef_dll/ctocpp/audio_handler_ctocpp.h" +#include "libcef_dll/cpptoc/browser_cpptoc.h" + +// VIRTUAL METHODS - Body may be edited by hand. + +NO_SANITIZE("cfi-icall") +void CefAudioHandlerCToCpp::OnAudioStreamStarted(CefRefPtr browser, + int audio_stream_id, + int channels, + ChannelLayout channel_layout, + int sample_rate, + int frames_per_buffer) { + cef_audio_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_audio_stream_started)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Execute + _struct->on_audio_stream_started(_struct, CefBrowserCppToC::Wrap(browser), + audio_stream_id, channels, channel_layout, + sample_rate, frames_per_buffer); +} + +NO_SANITIZE("cfi-icall") +void CefAudioHandlerCToCpp::OnAudioStreamPacket(CefRefPtr browser, + int audio_stream_id, + const float** data, + int frames, + int64 pts) { + cef_audio_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_audio_stream_packet)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + // Verify param: data; type: simple_byaddr + DCHECK(data); + if (!data) + return; + + // Execute + _struct->on_audio_stream_packet(_struct, CefBrowserCppToC::Wrap(browser), + audio_stream_id, data, frames, pts); +} + +NO_SANITIZE("cfi-icall") +void CefAudioHandlerCToCpp::OnAudioStreamStopped(CefRefPtr browser, + int audio_stream_id) { + cef_audio_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_audio_stream_stopped)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Execute + _struct->on_audio_stream_stopped(_struct, CefBrowserCppToC::Wrap(browser), + audio_stream_id); +} + +// CONSTRUCTOR - Do not edit by hand. + +CefAudioHandlerCToCpp::CefAudioHandlerCToCpp() {} + +template <> +cef_audio_handler_t* +CefCToCppRefCounted::UnwrapDerived(CefWrapperType type, + CefAudioHandler* c) { + NOTREACHED() << "Unexpected class type: " << type; + return NULL; +} + +#if DCHECK_IS_ON() +template <> +base::AtomicRefCount CefCToCppRefCounted::DebugObjCt + ATOMIC_DECLARATION; +#endif + +template <> +CefWrapperType CefCToCppRefCounted::kWrapperType = + WT_AUDIO_HANDLER; diff --git a/libcef_dll/ctocpp/audio_handler_ctocpp.h b/libcef_dll/ctocpp/audio_handler_ctocpp.h new file mode 100644 index 000000000..3ccd408e4 --- /dev/null +++ b/libcef_dll/ctocpp/audio_handler_ctocpp.h @@ -0,0 +1,51 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=59b7d7d50125a15f90e67e28a6e004da957cc0f9$ +// + +#ifndef CEF_LIBCEF_DLL_CTOCPP_AUDIO_HANDLER_CTOCPP_H_ +#define CEF_LIBCEF_DLL_CTOCPP_AUDIO_HANDLER_CTOCPP_H_ +#pragma once + +#if !defined(BUILDING_CEF_SHARED) +#error This file can be included DLL-side only +#endif + +#include "include/capi/cef_audio_handler_capi.h" +#include "include/cef_audio_handler.h" +#include "libcef_dll/ctocpp/ctocpp_ref_counted.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed DLL-side only. +class CefAudioHandlerCToCpp : public CefCToCppRefCounted { + public: + CefAudioHandlerCToCpp(); + + // CefAudioHandler methods. + void OnAudioStreamStarted(CefRefPtr browser, + int audio_stream_id, + int channels, + ChannelLayout channel_layout, + int sample_rate, + int frames_per_buffer) override; + void OnAudioStreamPacket(CefRefPtr browser, + int audio_stream_id, + const float** data, + int frames, + int64 pts) override; + void OnAudioStreamStopped(CefRefPtr browser, + int audio_stream_id) override; +}; + +#endif // CEF_LIBCEF_DLL_CTOCPP_AUDIO_HANDLER_CTOCPP_H_ diff --git a/libcef_dll/ctocpp/client_ctocpp.cc b/libcef_dll/ctocpp/client_ctocpp.cc index 819668b10..c5741f0e7 100644 --- a/libcef_dll/ctocpp/client_ctocpp.cc +++ b/libcef_dll/ctocpp/client_ctocpp.cc @@ -9,12 +9,13 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=206c9d8f26cdcf1d7a879aab7c92712c3c10781c$ +// $hash=f86cfe6f6a72ed0b619bc2bd0980e74c38210737$ // #include "libcef_dll/ctocpp/client_ctocpp.h" #include "libcef_dll/cpptoc/browser_cpptoc.h" #include "libcef_dll/cpptoc/process_message_cpptoc.h" +#include "libcef_dll/ctocpp/audio_handler_ctocpp.h" #include "libcef_dll/ctocpp/context_menu_handler_ctocpp.h" #include "libcef_dll/ctocpp/dialog_handler_ctocpp.h" #include "libcef_dll/ctocpp/display_handler_ctocpp.h" @@ -31,6 +32,21 @@ // VIRTUAL METHODS - Body may be edited by hand. +NO_SANITIZE("cfi-icall") +CefRefPtr CefClientCToCpp::GetAudioHandler() { + cef_client_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, get_audio_handler)) + return NULL; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + cef_audio_handler_t* _retval = _struct->get_audio_handler(_struct); + + // Return type: refptr_same + return CefAudioHandlerCToCpp::Wrap(_retval); +} + NO_SANITIZE("cfi-icall") CefRefPtr CefClientCToCpp::GetContextMenuHandler() { cef_client_t* _struct = GetStruct(); diff --git a/libcef_dll/ctocpp/client_ctocpp.h b/libcef_dll/ctocpp/client_ctocpp.h index 8e88c3a43..2d9a890de 100644 --- a/libcef_dll/ctocpp/client_ctocpp.h +++ b/libcef_dll/ctocpp/client_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=bf7c09d3ce6778f188b0ded7c16bde93b3875a8b$ +// $hash=cef382050baf759eb067510de43a2772739a68e1$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_CLIENT_CTOCPP_H_ @@ -32,6 +32,7 @@ class CefClientCToCpp CefClientCToCpp(); // CefClient methods. + CefRefPtr GetAudioHandler() override; CefRefPtr GetContextMenuHandler() override; CefRefPtr GetDialogHandler() override; CefRefPtr GetDisplayHandler() override; diff --git a/libcef_dll/libcef_dll.cc b/libcef_dll/libcef_dll.cc index 7ad81ccc5..f93541a76 100644 --- a/libcef_dll/libcef_dll.cc +++ b/libcef_dll/libcef_dll.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=824a78e502417eccd79d65691b35b9a965d72778$ +// $hash=36b0b2964486a8b1412209b1146f1b4338255a80$ // #include "include/capi/cef_app_capi.h" @@ -114,6 +114,7 @@ #include "libcef_dll/cpptoc/zip_reader_cpptoc.h" #include "libcef_dll/ctocpp/accessibility_handler_ctocpp.h" #include "libcef_dll/ctocpp/app_ctocpp.h" +#include "libcef_dll/ctocpp/audio_handler_ctocpp.h" #include "libcef_dll/ctocpp/browser_process_handler_ctocpp.h" #include "libcef_dll/ctocpp/completion_callback_ctocpp.h" #include "libcef_dll/ctocpp/context_menu_handler_ctocpp.h" @@ -243,6 +244,7 @@ CEF_EXPORT void cef_shutdown() { // Check that all wrapper objects have been destroyed DCHECK( base::AtomicRefCountIsZero(&CefAccessibilityHandlerCToCpp::DebugObjCt)); + DCHECK(base::AtomicRefCountIsZero(&CefAudioHandlerCToCpp::DebugObjCt)); DCHECK(base::AtomicRefCountIsZero(&CefAuthCallbackCppToC::DebugObjCt)); DCHECK( base::AtomicRefCountIsZero(&CefBeforeDownloadCallbackCppToC::DebugObjCt)); diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.cc b/libcef_dll/wrapper/libcef_dll_wrapper.cc index 296505f7b..a25fa893e 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.cc +++ b/libcef_dll/wrapper/libcef_dll_wrapper.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=dfa41e1f1e43a693fb48a8de17a8a7cddba8c60a$ +// $hash=1bd14a5fc8a82ee6b7e3ba80afb4eb031bf0d11d$ // #include "include/capi/cef_app_capi.h" @@ -43,6 +43,7 @@ #include "include/test/cef_test_helpers.h" #include "libcef_dll/cpptoc/accessibility_handler_cpptoc.h" #include "libcef_dll/cpptoc/app_cpptoc.h" +#include "libcef_dll/cpptoc/audio_handler_cpptoc.h" #include "libcef_dll/cpptoc/browser_process_handler_cpptoc.h" #include "libcef_dll/cpptoc/completion_callback_cpptoc.h" #include "libcef_dll/cpptoc/context_menu_handler_cpptoc.h" @@ -236,6 +237,7 @@ NO_SANITIZE("cfi-icall") CEF_GLOBAL void CefShutdown() { // Check that all wrapper objects have been destroyed DCHECK( base::AtomicRefCountIsZero(&CefAccessibilityHandlerCppToC::DebugObjCt)); + DCHECK(base::AtomicRefCountIsZero(&CefAudioHandlerCppToC::DebugObjCt)); DCHECK(base::AtomicRefCountIsZero(&CefAuthCallbackCToCpp::DebugObjCt)); DCHECK( base::AtomicRefCountIsZero(&CefBeforeDownloadCallbackCToCpp::DebugObjCt)); diff --git a/libcef_dll/wrapper_types.h b/libcef_dll/wrapper_types.h index 9e3ae8317..e10f97fc4 100644 --- a/libcef_dll/wrapper_types.h +++ b/libcef_dll/wrapper_types.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=2465514c5a784838334475fd06fb3c1bf1755fd6$ +// $hash=babd63d2abc0812c13f7228094f601851ea0679f$ // #ifndef CEF_LIBCEF_DLL_WRAPPER_TYPES_H_ @@ -21,6 +21,7 @@ enum CefWrapperType { WT_BASE_SCOPED, WT_ACCESSIBILITY_HANDLER, WT_APP, + WT_AUDIO_HANDLER, WT_AUTH_CALLBACK, WT_BEFORE_DOWNLOAD_CALLBACK, WT_BINARY_VALUE, diff --git a/tests/ceftests/audio_output_unittest.cc b/tests/ceftests/audio_output_unittest.cc new file mode 100644 index 000000000..f937a14fd --- /dev/null +++ b/tests/ceftests/audio_output_unittest.cc @@ -0,0 +1,895 @@ +// Copyright (c) 2018 The Chromium Embedded Framework 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 "include/base/cef_bind.h" +#include "include/wrapper/cef_closure_task.h" +#include "tests/ceftests/test_handler.h" +#include "tests/gtest/include/gtest/gtest.h" + +namespace { + +const int kNumChannels = 2; +const CefAudioHandler::ChannelLayout kChannelLayout = CEF_CHANNEL_LAYOUT_STEREO; + +const char kTestUrl[] = "http://tests/audiooutputtest"; + +// Taken from: +// http://www.iandevlin.com/blog/2012/09/html5/html5-media-and-data-uri/ +const char kTestHtml[] = + "

TEST

"; + +// a common base class for audio output tests +class AudioOutputTestHandler : public TestHandler, public CefAudioHandler { + public: + AudioOutputTestHandler() : audio_stream_id_(-1), frames_per_buffer_(-1) {} + + void RunTest() override { + // Add the resource + AddResource(kTestUrl, kTestHtml, "text/html"); + + // Create the browser. + CreateBrowser(kTestUrl); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + CefRefPtr GetAudioHandler() override { return this; } + + void OnAudioStreamStarted(CefRefPtr browser, + int audio_stream_id, + int channels, + ChannelLayout channel_layout, + int sample_rate, + int frames_per_buffer) override { + EXPECT_FALSE(got_on_audio_stream_started_); + EXPECT_TRUE(GetBrowser()->IsSame(browser)); + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_EQ(channels, kNumChannels); + EXPECT_EQ(channel_layout, kChannelLayout); + EXPECT_GE(sample_rate, 22050); + EXPECT_LE(sample_rate, 48000); + EXPECT_GE(frames_per_buffer, 441); + EXPECT_LE(frames_per_buffer, 1280); + audio_stream_id_ = audio_stream_id; + frames_per_buffer_ = frames_per_buffer; + got_on_audio_stream_started_.yes(); + } + + void OnAudioStreamPacket(CefRefPtr browser, + int audio_stream_id, + const float** data, + int frames, + int64 pts) override { + if (!got_on_audio_stream_packet_.isSet()) { + EXPECT_TRUE(got_on_audio_stream_started_); + EXPECT_EQ(audio_stream_id_, audio_stream_id); + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_EQ(frames, frames_per_buffer_); + + browser->GetMainFrame()->ExecuteJavaScript( + "var ifr = document.getElementById(\"audio_output_frame\"); " + "ifr.parentNode.removeChild(ifr);", + CefString(), 0); + + got_on_audio_stream_packet_.yes(); + } + } + + void OnAudioStreamStopped(CefRefPtr browser, + int audio_stream_id) override { + EXPECT_FALSE(got_on_audio_stream_stopped_); + EXPECT_TRUE(got_on_audio_stream_started_); + EXPECT_EQ(audio_stream_id_, audio_stream_id); + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + got_on_audio_stream_stopped_.yes(); + DestroyTest(); + } + + protected: + void DestroyTest() override { + EXPECT_TRUE(got_on_audio_stream_started_); + EXPECT_TRUE(got_on_audio_stream_packet_); + EXPECT_TRUE(got_on_audio_stream_stopped_); + TestHandler::DestroyTest(); + } + + int audio_stream_id_; + int frames_per_buffer_; + + TrackCallback got_on_audio_stream_started_; + TrackCallback got_on_audio_stream_packet_; + TrackCallback got_on_audio_stream_stopped_; + + IMPLEMENT_REFCOUNTING(AudioOutputTestHandler); +}; +} // namespace + +// Test audio output callbacks called on valid threads +TEST(AudioOutputTest, AudioOutputTest) { + CefRefPtr handler = new AudioOutputTestHandler(); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} diff --git a/tools/cef_parser.py b/tools/cef_parser.py index c2e21e9a1..684879005 100644 --- a/tools/cef_parser.py +++ b/tools/cef_parser.py @@ -360,6 +360,7 @@ _simpletypes = { 'uint64': ['uint64', '0'], 'double': ['double', '0'], 'float': ['float', '0'], + 'float*': ['float*', 'NULL'], 'long': ['long', '0'], 'unsigned long': ['unsigned long', '0'], 'long long': ['long long', '0'],