renderer_factory_client_.set_disconnect_handler(base::BindOnce(
&AudioInputStreamBroker::ClientBindingLost, base::Unretained(this)));
- NotifyProcessHostOfStartedStream(render_process_id);
+ NotifyProcessHostOfStartedStream(render_process_id, render_frame_id);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUseFakeDeviceForMediaStream)) {
if (user_input_monitor_)
user_input_monitor_->DisableKeyPressMonitoring();
- NotifyProcessHostOfStoppedStream(render_process_id());
+ NotifyProcessHostOfStoppedStream(render_process_id(), render_frame_id());
// TODO(https://crbug.com/829317) update tab recording indicator.
// Notify the source that we are capturing from it.
source_->AddLoopbackSink(this);
- NotifyProcessHostOfStartedStream(render_process_id);
+ NotifyProcessHostOfStartedStream(render_process_id, render_frame_id);
}
AudioLoopbackStreamBroker::~AudioLoopbackStreamBroker() {
if (source_)
source_->RemoveLoopbackSink(this);
- NotifyProcessHostOfStoppedStream(render_process_id());
+ NotifyProcessHostOfStoppedStream(render_process_id(), render_frame_id());
}
void AudioLoopbackStreamBroker::CreateStream(
// a valid FrameSinkId.
DCHECK(frame_sink_id_.is_valid());
+#if BUILDFLAG(IS_TIZEN_TV)
+ mic_updater_ =
+ std::make_unique<MicrophoneStateUpdaterTizenEfl>(&web_contents);
+#endif
+
CreateDelegatedFrameHostClient();
host()->SetView(this);
if (text_input_manager_)
text_input_manager_->RemoveObserver(this);
+
+#if BUILDFLAG(IS_TIZEN_TV)
+ if (mic_updater_)
+ mic_updater_->Release();
+#endif
}
void RenderWidgetHostViewAura::CreateAuraWindow(aura::client::WindowType type) {
}
}
+#if BUILDFLAG(IS_TIZEN_TV)
+void RenderWidgetHostViewBase::OnMediaStreamAdded() {
+ if (mic_updater_)
+ mic_updater_->UpdateMicrophoneState(true);
+}
+
+void RenderWidgetHostViewBase::OnMediaStreamRemoved() {
+ if (mic_updater_)
+ mic_updater_->UpdateMicrophoneState(false);
+}
+#endif // IS_TIZEN_TV
+
TextInputManager* RenderWidgetHostViewBase::GetTextInputManager() {
if (text_input_manager_)
return text_input_manager_;
#include "ui/gfx/range/range.h"
#include "ui/surface/transport_dib.h"
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "content/browser/renderer_host/microphone_state_updater.h"
+#endif
+
namespace blink {
class WebMouseEvent;
class WebMouseWheelEvent;
) {
}
+#if BUILDFLAG(IS_TIZEN_TV)
+ // to notify RenderWidgetHostViewAura audio input is on/off
+ void OnMediaStreamAdded();
+ void OnMediaStreamRemoved();
+#endif
+
// Requests to start stylus writing and returns true if successful.
virtual bool RequestStartStylusWriting();
// to all displays.
gfx::Size system_cursor_size_;
+#if BUILDFLAG(IS_TIZEN_TV)
+ std::unique_ptr<MicrophoneStateUpdaterTizenEfl> mic_updater_;
+#endif
+
private:
FRIEND_TEST_ALL_PREFIXES(
BrowserSideFlingBrowserTest,
}
}
+#if BUILDFLAG(IS_TIZEN_TV)
+void WebContentsImpl::ShowMicOpenedNotification(bool show) {
+ if (delegate_)
+ delegate_->ShowMicOpenedNotification(show);
+}
+
+void WebContentsImpl::OnMediaStreamAdded() {
+ RenderWidgetHostViewBase* rwhv =
+ static_cast<RenderWidgetHostViewBase*>(GetRenderWidgetHostView());
+ if (rwhv)
+ rwhv->OnMediaStreamAdded();
+}
+
+void WebContentsImpl::OnMediaStreamRemoved() {
+ RenderWidgetHostViewBase* rwhv =
+ static_cast<RenderWidgetHostViewBase*>(GetRenderWidgetHostView());
+ if (rwhv)
+ rwhv->OnMediaStreamRemoved();
+}
+#endif
+
const RenderFrameHostImpl* WebContentsImpl::GetPrimaryMainFrame() const {
return primary_frame_tree_.root()->current_frame_host();
}
// WebContents ------------------------------------------------------
WebContentsDelegate* GetDelegate() override;
void SetDelegate(WebContentsDelegate* delegate) override;
+#if BUILDFLAG(IS_TIZEN_TV)
+ void ShowMicOpenedNotification(bool show) override;
+ void OnMediaStreamAdded() override;
+ void OnMediaStreamRemoved() override;
+#endif
NavigationControllerImpl& GetController() override;
BrowserContext* GetBrowserContext() override;
base::WeakPtr<WebContents> GetWeakPtr() override;
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "content/public/browser/web_contents.h"
+#endif
+
namespace content {
AudioStreamBroker::LoopbackSink::LoopbackSink() = default;
AudioStreamBroker::~AudioStreamBroker() = default;
// static
-void AudioStreamBroker::NotifyProcessHostOfStartedStream(
- int render_process_id) {
+void AudioStreamBroker::NotifyProcessHostOfStartedStream(int render_process_id,
+ int render_frame_id) {
auto impl = [](int id) {
if (auto* process_host = RenderProcessHost::FromID(id))
process_host->OnMediaStreamAdded();
};
+#if BUILDFLAG(IS_TIZEN_TV)
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id));
+ if (web_contents)
+ web_contents->OnMediaStreamAdded();
+#endif
GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
base::BindOnce(impl, render_process_id));
}
// static
-void AudioStreamBroker::NotifyProcessHostOfStoppedStream(
- int render_process_id) {
+void AudioStreamBroker::NotifyProcessHostOfStoppedStream(int render_process_id,
+ int render_frame_id) {
auto impl = [](int id) {
if (auto* process_host = RenderProcessHost::FromID(id))
process_host->OnMediaStreamRemoved();
};
+#if BUILDFLAG(IS_TIZEN_TV)
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id));
+ if (web_contents)
+ web_contents->OnMediaStreamRemoved();
+#endif
GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
base::BindOnce(impl, render_process_id));
}
// |render_process_id| of a started stream to ensure that the renderer is not
// backgrounded. Must be paired with a later call to
// NotifyRenderProcessOfStoppedStream()
- static void NotifyProcessHostOfStartedStream(int render_process_id);
- static void NotifyProcessHostOfStoppedStream(int render_process_id);
+ static void NotifyProcessHostOfStartedStream(int render_process_id,
+ int render_frame_id);
+ static void NotifyProcessHostOfStoppedStream(int render_process_id,
+ int render_frame_id);
int render_process_id() const { return render_process_id_; }
int render_frame_id() const { return render_frame_id_; }
// Gets/Sets the delegate.
virtual WebContentsDelegate* GetDelegate() = 0;
virtual void SetDelegate(WebContentsDelegate* delegate) = 0;
-
+#if BUILDFLAG(IS_TIZEN_TV)
+ virtual void ShowMicOpenedNotification(bool show) = 0;
+ virtual void OnMediaStreamAdded() = 0;
+ virtual void OnMediaStreamRemoved() = 0;
+#endif
// Gets the NavigationController for primary frame tree of this WebContents.
// See comments on NavigationController for more details.
virtual NavigationController& GetController() = 0;
#if BUILDFLAG(IS_TIZEN_TV)
virtual void DidEdgeScrollBy(const gfx::Point& offset, bool handled) {}
virtual void MoveFocusToBrowser(int direction) {}
- virtual void ShowMicOpenedNotification(bool show) {}
#endif
virtual void UpdateTooltipUnderCursor(const std::u16string& text) {}
virtual void NotifyPESData(const std::string& buf,
unsigned int len,
int media_position) {}
+ virtual void ShowMicOpenedNotification(bool show) {}
#endif
#if BUILDFLAG(IS_ANDROID)
BuildRequires: pkgconfig(capi-stt-wrapper-tv)
BuildRequires: pkgconfig(capi-system-display-rotator)
BuildRequires: pkgconfig(capi-appfw-app-manager)
+BuildRequires: pkgconfig(capi-network-bluetooth)
+BuildRequires: pkgconfig(capi-network-bluetooth-tv)
BuildRequires: pkgconfig(cynara-client)
BuildRequires: pkgconfig(security-privilege-manager)
BuildRequires: pkgconfig(drmdecrypt)
}
}
+config("capi-network-bluetooth") {
+ if (tizen_product_tv) {
+ ldflags = [ "-lcapi-network-bluetooth" ]
+ }
+}
+
+tizen_pkg_config("libcapi-network-bluetooth") {
+ packages = []
+ if (tizen_product_tv) {
+ packages = [ "capi-network-bluetooth" ]
+ }
+}
+
+config("capi-network-bluetooth-tv") {
+ if (tizen_product_tv) {
+ ldflags = [ "-lcapi-network-bluetooth-tv" ]
+ }
+}
+
+tizen_pkg_config("libcapi-network-bluetooth-tv") {
+ packages = []
+ if (tizen_product_tv) {
+ packages = [ "capi-network-bluetooth-tv" ]
+ }
+}
+
config("capi-media-player") {
if (is_tizen) {
#ldflags = [ "-capi-media-player" ]
"$deps_include_path/gstreamer-1.0/gst/video",
"$deps_include_path/libsoup-2.4",
"$deps_include_path/libsoup-2.4/libsoup",
+ "$deps_include_path/network",
"$deps_include_path/orc-0.4",
"$deps_include_path/orc-0.4/orc",
"$deps_include_path/orc-0.4/orc-test",
"//tizen_src/chromium_impl/content/browser/speech/tts_platform_impl_tizen.h",
]
}
+
+if (tizen_product_tv) {
+ external_content_browser_efl_sources += [
+ "//tizen_src/chromium_impl/content/browser/renderer_host/microphone_state_updater.cc",
+ "//tizen_src/chromium_impl/content/browser/renderer_host/microphone_state_updater.h",
+ ]
+}
--- /dev/null
+// Copyright 2022 Samsung Electronics Inc. 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/renderer_host/microphone_state_updater.h"
+
+#include "base/logging.h"
+#include "content/browser/web_contents/web_contents_impl_efl.h"
+#include "ecore_x_wayland_wrapper.h"
+#include "tizen_src/ewk/efl_integration/common/application_type.h"
+#include "tizen_src/ewk/efl_integration/ewk_privilege_checker.h"
+
+namespace content {
+
+constexpr auto kKeyBtVoice = "XF86BTVoice";
+
+// static
+int MicrophoneStateUpdaterTizenEfl::keygrab_set_count_ = 0;
+
+MicrophoneStateUpdaterTizenEfl::MicrophoneStateUpdaterTizenEfl(
+ content::WebContents* web_contents)
+ : web_contents_(web_contents) {
+ LOG(INFO) << __func__ << " this:" << this;
+
+ if (!web_contents_) {
+ LOG(ERROR) << "There is no web contents!";
+ state_ = State::kInvalid;
+ return;
+ }
+
+ if (!content::IsTIZENWRT()) {
+ LOG(ERROR) << "Only Grab BT voice key in WRT APP";
+ state_ = State::kInvalid;
+ return;
+ }
+
+ if (!CheckMicrophonePrivilege()) {
+ LOG(ERROR) << "Do not have micrphone privilege!";
+ state_ = State::kInvalid;
+ return;
+ }
+
+ if (!GetEflWindow()) {
+ LOG(ERROR) << "Get window failed!";
+ state_ = State::kInvalid;
+ return;
+ }
+
+ if (keygrab_set_count_ == 0) {
+ auto ret = ecore_wl2_window_keygrab_set(wl_win_, kKeyBtVoice, 0, 0, 0,
+ ECORE_WL2_WINDOW_KEYGRAB_TOPMOST);
+ if (!ret) {
+ LOG(ERROR) << "ecore_wl2_window_keygrab_set failed:" << ret;
+ state_ = State::kInvalid;
+ return;
+ }
+ }
+ keygrab_set_count_++;
+ is_keygrab_set_ = true;
+
+ keydown_hander_ =
+ ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, OnKeyDown, this);
+ keyup_hander_ = ecore_event_handler_add(ECORE_EVENT_KEY_UP, OnKeyUp, this);
+
+ state_ = State::kGranted;
+}
+
+MicrophoneStateUpdaterTizenEfl::~MicrophoneStateUpdaterTizenEfl() {
+ LOG(INFO) << __func__ << " this:" << this;
+}
+
+void MicrophoneStateUpdaterTizenEfl::Release() {
+ LOG(INFO) << __func__ << " this:" << this;
+ state_ = State::kInvalid;
+ if (is_keygrab_set_)
+ keygrab_set_count_--;
+ if (keydown_hander_)
+ ecore_event_handler_del(keydown_hander_);
+ if (keyup_hander_)
+ ecore_event_handler_del(keyup_hander_);
+ if (wl_win_ && keygrab_set_count_ == 0 && is_keygrab_set_)
+ ecore_wl2_window_keygrab_unset(wl_win_, kKeyBtVoice, 0, 0);
+
+ keydown_hander_ = nullptr;
+ keyup_hander_ = nullptr;
+ wl_win_ = nullptr;
+ is_keygrab_set_ = false;
+}
+
+bool MicrophoneStateUpdaterTizenEfl::CheckMicrophonePrivilege() const {
+ bool result = false;
+ result = content::EwkPrivilegeChecker::GetInstance()->CheckPrivilege(
+ "http://developer.samsung.com/privilege/smartcontroller.microphone");
+ LOG(INFO) << "CheckSmartRCMicPrivilege = " << result;
+ return result;
+}
+
+void MicrophoneStateUpdaterTizenEfl::ShowMicOpenedNotification(bool show) {
+ if (!web_contents_) {
+ LOG(ERROR) << "ShowMicOpenedNotification failed!";
+ return;
+ }
+
+ web_contents_->ShowMicOpenedNotification(show);
+}
+
+void MicrophoneStateUpdaterTizenEfl::UpdateMicrophoneState(bool open) {
+ if (state_ == State::kInvalid) {
+ LOG(ERROR) << "Invalid state!";
+ return;
+ }
+
+ ShowMicOpenedNotification(open);
+ state_ = open ? State::kOpened : State::kClosed;
+}
+
+// the key up, key down, and long press logic is copied from m94 class
+// BTMicVoiceKeyGrab
+void MicrophoneStateUpdaterTizenEfl::OnVoiceKeyDown() {
+ voice_key_timer_.Reset();
+ /// 100ms is too short here, most press will be long press
+ voice_key_timer_.Start(FROM_HERE, base::Milliseconds(500), this,
+ &MicrophoneStateUpdaterTizenEfl::VoiceKeyLongPressed);
+}
+
+void MicrophoneStateUpdaterTizenEfl::OnVoiceKeyUp() {
+ if (voice_key_timer_.IsRunning()) {
+ voice_key_timer_.Stop();
+ ShowMicOpenedNotification(true);
+ }
+}
+
+void MicrophoneStateUpdaterTizenEfl::VoiceKeyLongPressed() {
+ ShowMicOpenedNotification(false);
+}
+
+Evas_Object* MicrophoneStateUpdaterTizenEfl::GetEflNativeView() {
+ if (!web_contents_) {
+ LOG(ERROR) << "GetEflNativeView failed!";
+ return nullptr;
+ }
+
+ return static_cast<Evas_Object*>(
+ static_cast<WebContentsImplEfl*>(web_contents_)->GetEflMainLayout());
+}
+
+bool MicrophoneStateUpdaterTizenEfl::GetEflWindow() {
+ Evas_Object* parent_view = GetEflNativeView();
+ if (!parent_view) {
+ LOG(ERROR) << "GetParentEflWindow failed!";
+ return false;
+ }
+
+ Evas* evas = evas_object_evas_get(parent_view);
+ if (!evas) {
+ LOG(ERROR) << "evas_object_evas_get failed!";
+ return false;
+ }
+
+ const Ecore_Evas* ee = ecore_evas_ecore_evas_get(evas);
+ wl_win_ = ecore_evas_wayland2_window_get(ee);
+ if (!wl_win_) {
+ LOG(ERROR) << "ecore_evas_wayland2_window_get failed!";
+ return false;
+ }
+
+ return true;
+}
+
+Eina_Bool MicrophoneStateUpdaterTizenEfl::OnKeyUp(void* user_data,
+ int type,
+ void* event) {
+ Ecore_Event_Key* ev = static_cast<Ecore_Event_Key*>(event);
+ LOG(INFO) << "OnKeyUp type: " << type << " keyname: " << ev->keyname;
+
+ MicrophoneStateUpdaterTizenEfl* updater =
+ static_cast<MicrophoneStateUpdaterTizenEfl*>(user_data);
+ if (!ev || !updater || updater->GetState() == State::kInvalid) {
+ LOG(ERROR) << "Invalid state!";
+ return ECORE_CALLBACK_PASS_ON;
+ }
+
+ std::string voice_key(kKeyBtVoice);
+ if (!voice_key.compare(ev->keyname)) {
+ updater->OnVoiceKeyUp();
+ return ECORE_CALLBACK_DONE;
+ }
+
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+Eina_Bool MicrophoneStateUpdaterTizenEfl::OnKeyDown(void* user_data,
+ int type,
+ void* event) {
+ Ecore_Event_Key* ev = static_cast<Ecore_Event_Key*>(event);
+ LOG(INFO) << "OnKeyUp type: " << type << " keyname: " << ev->keyname;
+
+ MicrophoneStateUpdaterTizenEfl* updater =
+ static_cast<MicrophoneStateUpdaterTizenEfl*>(user_data);
+ if (!ev || !updater || updater->GetState() == State::kInvalid) {
+ LOG(ERROR) << "Invalid state!";
+ return ECORE_CALLBACK_PASS_ON;
+ }
+
+ std::string voice_key(kKeyBtVoice);
+ if (!voice_key.compare(ev->keyname)) {
+ updater->OnVoiceKeyDown();
+ return ECORE_CALLBACK_DONE;
+ }
+
+ return ECORE_CALLBACK_PASS_ON;
+}
+
+} // namespace content
--- /dev/null
+// Copyright 2022 Samsung Electronics Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MICROPHONE_STATE_UPDATER
+#define MICROPHONE_STATE_UPDATER
+
+#include <Ecore.h>
+#include <Ecore_Evas.h>
+#include <Ecore_Input.h>
+#include <Elementary.h>
+
+#include "base/timer/timer.h"
+
+namespace content {
+
+class WebContents;
+
+class MicrophoneStateUpdaterTizenEfl {
+ public:
+ enum class State { kClosed, kInvalid, kGranted, kOpened };
+
+ explicit MicrophoneStateUpdaterTizenEfl(WebContents* web_contents);
+ virtual ~MicrophoneStateUpdaterTizenEfl();
+
+ MicrophoneStateUpdaterTizenEfl(const MicrophoneStateUpdaterTizenEfl&) =
+ delete;
+ MicrophoneStateUpdaterTizenEfl& operator=(
+ const MicrophoneStateUpdaterTizenEfl&) = delete;
+
+ static Eina_Bool OnKeyUp(void* user_data, int type, void* event);
+ static Eina_Bool OnKeyDown(void* user_data, int type, void* event);
+
+ void UpdateMicrophoneState(bool open);
+ void Release();
+
+ protected:
+ bool CheckMicrophonePrivilege() const;
+ State GetState() { return state_; }
+ // notify only if current state is opened
+ void ShowMicOpenedNotification(bool show);
+ void OnVoiceKeyDown();
+ void OnVoiceKeyUp();
+ void VoiceKeyLongPressed();
+ bool GetEflWindow();
+ Evas_Object* GetEflNativeView();
+
+ base::OneShotTimer voice_key_timer_;
+ Ecore_Event_Handler* keydown_hander_ = nullptr;
+ Ecore_Event_Handler* keyup_hander_ = nullptr;
+ Ecore_Wl2_Window* wl_win_ = nullptr;
+
+ State state_ = State::kInvalid;
+ content::WebContents* web_contents_ = nullptr;
+
+ static int keygrab_set_count_;
+ bool is_keygrab_set_ = false;
+};
+
+} // namespace content
+#endif
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "media/base/limits.h"
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "media/audio/tizen/capi_bt_audio_input_stream.h"
+#include "tizen_src/ewk/efl_integration/ewk_privilege_checker.h"
+#define UNIQUE_BT_DEVICE_ID "1"
+#endif
namespace media {
return !devices.empty();
}
+#if BUILDFLAG(IS_TIZEN_TV)
+bool AudioManagerCapi::CheckSmartRCMicPrivilege() const {
+ bool result = content::EwkPrivilegeChecker::GetInstance()->CheckPrivilege(
+ "http://developer.samsung.com/privilege/smartcontroller.microphone");
+
+ if (!result)
+ LOG(INFO) << "AudioManagerCapi::CheckSmartRCMicPrivilege = " << result;
+ return result;
+}
+
+void AudioManagerCapi::BTHidStateChangedCB(int result,
+ bool connected,
+ const char* remote_address,
+ void* user_data) {
+ LOG(INFO) << "Bluetooth Event " << result << "Received address"
+ << remote_address;
+}
+
+void AudioManagerCapi::BTHidAudioDataReceiveCB(bt_hid_voice_data_s* voice_data,
+ void* user_data) {
+ LOG(INFO) << "Bluetooth Event BTHidAudioDataReceiveCB";
+}
+
+bool AudioManagerCapi::GetBluetoothMicNames(
+ media::AudioDeviceNames* device_names) const {
+ int ret = BT_ERROR_NONE;
+
+ ret = bt_product_init();
+ if (ret != BT_ERROR_NONE) {
+ LOG(ERROR) << "Fail to init bt : " << ret;
+ return false;
+ }
+
+ ret = bt_hid_host_initialize(BTHidStateChangedCB, NULL);
+ if (ret != BT_ERROR_NONE) {
+ LOG(ERROR) << "bt_hid_host_initialize fail: " << ret;
+ bt_product_deinit();
+ return false;
+ }
+
+ ret = bt_hid_set_audio_data_receive_cb(BTHidAudioDataReceiveCB, NULL);
+ if (ret != BT_ERROR_NONE) {
+ LOG(ERROR) << "bt_hid_set_audio_data_receive_cb fail: " << ret;
+ bt_hid_host_deinitialize();
+ bt_product_deinit();
+ return false;
+ }
+
+ bool is_connected = false;
+ ret = bt_hid_get_smart_remote_conn_status(&is_connected);
+ if (ret == BT_ERROR_NONE && is_connected) {
+ device_names->push_front(
+ AudioDeviceName("Samsung Bluetooth Microphone", UNIQUE_BT_DEVICE_ID));
+ }
+
+ bt_hid_unset_audio_data_receive_cb();
+ bt_hid_host_deinitialize();
+ bt_product_deinit();
+
+ if (!is_connected) {
+ LOG(INFO) << "Bluetooth mic is not connected.";
+ return false;
+ }
+
+ return true;
+}
+#endif
+
void AudioManagerCapi::GetAudioDeviceNames(
bool input,
media::AudioDeviceNames* device_names) const {
DCHECK(device_names != NULL);
DCHECK(device_names->empty());
+#if BUILDFLAG(IS_TIZEN_TV)
+ if (CheckSmartRCMicPrivilege() && GetBluetoothMicNames(device_names)) {
+ // if app has privilege to access microphone of smartRC
+ // and it is connected, usb mic will be hided.
+ LOG(WARNING) << "use SmartRC microphone instead.";
+ return;
+ }
+#endif
auto mask = input ? SOUND_DEVICE_IO_DIRECTION_IN_MASK
: SOUND_DEVICE_IO_DIRECTION_OUT_MASK;
return;
}
+#if BUILDFLAG(IS_TIZEN_TV)
+ sound_device_h device;
+ AudioDeviceNames device_names_built_in;
+ while (sound_manager_get_next_device(list, &device) ==
+ SOUND_MANAGER_ERROR_NONE) {
+ int id;
+ char* name = nullptr;
+ sound_device_type_e device_type;
+ int get_status = sound_manager_get_device_id(device, &id);
+ if (get_status != SOUND_MANAGER_ERROR_NONE) {
+ LOG(ERROR) << "Failed to get device ID. Err:" << get_status;
+ continue;
+ }
+
+ get_status = sound_manager_get_device_name(device, &name);
+ if (get_status != SOUND_MANAGER_ERROR_NONE) {
+ LOG(ERROR) << "Failed to get device name. Err:" << get_status;
+ continue;
+ }
+
+ get_status = sound_manager_get_device_type(device, &device_type);
+ if (get_status != SOUND_MANAGER_ERROR_NONE) {
+ LOG(ERROR) << "Failed to get device type. Err:" << get_status;
+ continue;
+ }
+
+ if (input && device_type == SOUND_DEVICE_FORWARDING) {
+ LOG(ERROR) << "Device with type SOUND_DEVICE_FORWARDING is audio input "
+ "device but it doesn't provide microphone data. ignore it:"
+ << "Device - ID:" << id << ". Name:" << name
+ << ". type:" << device_type;
+ continue;
+ }
+
+ LOG(INFO) << "Device - ID:" << id << ". Name:" << name
+ << ". type:" << device_type;
+
+ if (device_type == SOUND_DEVICE_BUILTIN_MIC) {
+ device_names_built_in.push_back(
+ AudioDeviceName(name, std::to_string(id)));
+ } else {
+ device_names->push_back(AudioDeviceName(name, std::to_string(id)));
+ }
+ }
+
+ device_names->insert(device_names->end(), device_names_built_in.begin(),
+ device_names_built_in.end());
+
+ ret = sound_manager_free_device_list(list);
+ if (ret != SOUND_MANAGER_ERROR_NONE)
+ LOG(INFO) << "Failed to free device list. Err:" << ret;
+
+ if (device_names->empty())
+ device_names->push_front(AudioDeviceName::CreateDefault());
+
+ return;
+#endif
+
device_names->push_front(AudioDeviceName::CreateDefault());
}
AudioParameters AudioManagerCapi::GetInputStreamParameters(
const std::string& device_id) {
+#if BUILDFLAG(IS_TIZEN_TV)
+ LOG(INFO) << "GetInputStreamParameters " << device_id;
+ if (CheckSmartRCMicPrivilege() &&
+ device_id.compare(UNIQUE_BT_DEVICE_ID) == 0) {
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::ChannelLayoutConfig::Mono(), kBtSampleRate,
+ kBtMinReadSize);
+ }
+#endif
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(),
kDefaultSampleRate, kDefaultInputBufferSize);
AudioInputStream* AudioManagerCapi::MakeInputStream(
const AudioParameters& params,
const std::string& device_id) {
+#if BUILDFLAG(IS_TIZEN_TV)
+ if (CheckSmartRCMicPrivilege() &&
+ device_id.compare(UNIQUE_BT_DEVICE_ID) == 0) {
+ return new CapiBtAudioInputStream(this, params);
+ }
+#endif
return new CapiUsbAudioInputStream(this, device_id, params);
}
#include "build/build_config.h"
#include "media/audio/audio_manager_base.h"
+#if BUILDFLAG(IS_TIZEN_TV)
+#include <bluetooth_product.h>
+#endif
+
namespace media {
class MuteableAudioOutputStream;
void ReleaseOutputStream(AudioOutputStream* stream) override;
void ReleaseInputStream(AudioInputStream* stream) override;
const char* GetName() override;
+#if BUILDFLAG(IS_TIZEN_TV)
+ bool CheckSmartRCMicPrivilege() const;
+#endif
+
// Implementation of AudioManagerBase.
AudioOutputStream* MakeLinearOutputStream(
const AudioParameters& params,
const AudioParameters& input_params) override;
private:
+#if BUILDFLAG(IS_TIZEN_TV)
+ static void BTHidStateChangedCB(int result,
+ bool connected,
+ const char* remote_address,
+ void* user_data);
+ static void BTHidAudioDataReceiveCB(bt_hid_voice_data_s* voice_data,
+ void* user_data);
+ bool GetBluetoothMicNames(media::AudioDeviceNames* device_names) const;
+#endif
void GetAudioDeviceNames(bool input,
media::AudioDeviceNames* device_names) const;
--- /dev/null
+// Copyright (c) 2021 The Samsung 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 "media/audio/tizen/capi_bt_audio_input_stream.h"
+
+#include <audio_io.h>
+#include <sys/types.h>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace media {
+
+#define SMART_CONTROL_EXTEND_CMD 0x03
+#define SMART_CONTROL_START_CMD 0x04
+
+#define KEY_BT_VOICE "XF86BTVoice"
+
+#define BUF_SAVE_MODE 1
+#undef BUF_SAVE_MODE
+
+#ifdef BUF_SAVE_MODE
+
+static FILE* g_dump_file;
+
+void CapiBtAudioInputStream::CreateAudioDumpFile() {
+ int ret = -1;
+ char g_dump_filename[128] = {
+ '\0',
+ };
+ static int count = 1;
+ count++;
+
+ while (1) {
+ snprintf(g_dump_filename, sizeof(g_dump_filename), "/tmp/vc_normal_%d_%d",
+ getpid(), count);
+ ret = access(g_dump_filename, 0);
+
+ if (0 == ret) {
+ if (0 == remove(g_dump_filename)) {
+ break;
+ } else {
+ count++;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* open test file */
+ g_dump_file = fopen(g_dump_filename, "wb+x");
+ if (!g_dump_file) {
+ LOG(ERROR) << "File not found!";
+ }
+}
+#endif // BUF_SAVE_MODE
+
+CapiBtAudioInputStream::CapiBtAudioInputStream(AudioManagerCapi* audio_manager,
+ const AudioParameters& params)
+ : CapiAudioInputStream(audio_manager, params),
+ send_stop_flag_(false),
+ weak_factory_(this) {}
+
+CapiBtAudioInputStream::~CapiBtAudioInputStream() {
+ LOG(INFO) << "~CapiBtAudioInputStream";
+}
+
+void CapiBtAudioInputStream::BTHidStateChangedCB(int result,
+ bool connected,
+ const char* remote_address,
+ void* user_data) {
+ LOG(INFO) << "Bluetooth Event" << result << "Received address"
+ << remote_address;
+}
+
+void CapiBtAudioInputStream::BTHidAudioDataReceiveCB(
+ bt_hid_voice_data_s* voice_data,
+ void* user_data) {
+ if (voice_data == nullptr || user_data == nullptr) {
+ LOG(ERROR) << "BTHidAudioDataReceiveCB paramerter invalid";
+ return;
+ }
+
+ CapiBtAudioInputStream* mic =
+ reinterpret_cast<CapiBtAudioInputStream*>(user_data);
+ mic->ReadBTAudioData(voice_data);
+}
+
+bool CapiBtAudioInputStream::OpenMic() {
+ int ret = BT_ERROR_NONE;
+ ret = bt_product_init();
+ if (ret != BT_ERROR_NONE) {
+ LOG(ERROR) << "Fail to init bt: " << ret;
+ if (callback_)
+ callback_->OnError();
+ return false;
+ }
+
+ send_stop_flag_ = false;
+ ret = bt_hid_host_initialize(BTHidStateChangedCB, this);
+ if (ret != BT_ERROR_NONE) {
+ LOG(ERROR) << "bt_hid_host_initialize fail: " << ret;
+ if (callback_)
+ callback_->OnError();
+ return false;
+ }
+
+ ret = bt_hid_set_audio_data_receive_cb(BTHidAudioDataReceiveCB, this);
+ if (ret != BT_ERROR_NONE) {
+ LOG(ERROR) << "bt_hid_set_audio_data_receive_cb fail: " << ret;
+ if (callback_)
+ callback_->OnError();
+ return false;
+ }
+
+ bool is_connected = false;
+ ret = bt_hid_get_smart_remote_conn_status(&is_connected);
+ if (ret != BT_ERROR_NONE || !is_connected) {
+ LOG(INFO) << "Bluetooth mic is not connected.";
+ if (callback_)
+ callback_->OnError();
+ return false;
+ }
+
+ state_ = media::kIsOpened;
+
+ return true;
+}
+
+void CapiBtAudioInputStream::StartMic() {
+#ifdef BUF_SAVE_MODE
+ CreateAudioDumpFile();
+#endif // BUF_SAVE_MODE
+
+ audio_worker_.Start();
+ StartAgc();
+
+ state_ = media::kIsStarted;
+}
+
+void CapiBtAudioInputStream::StopMic() {
+ int ret = -1;
+
+#ifdef BUF_SAVE_MODE
+ if (g_dump_file) {
+ fclose(g_dump_file);
+ g_dump_file = nullptr;
+ }
+#endif // BUF_SAVE_MODE
+
+ bool stoped = false;
+ ret = bt_hid_rc_stop_sending_voice(nullptr);
+ if (ret == BT_ERROR_NONE) {
+ LOG(INFO) << "Stop bt audio recorder";
+ stoped = true;
+ } else if (ret == BT_ERROR_NOW_IN_PROGRESS) {
+ LOG(INFO) << "Fail bt_hid_rc_stop_sending_voice(), send again in CloseMic";
+ send_stop_flag_ = true;
+ bt_hid_unset_audio_data_receive_cb();
+ }
+
+ if (!stoped) {
+ LOG(ERROR) << "Fail to stop bt audio";
+ }
+}
+
+void CapiBtAudioInputStream::CloseMic(bool success) {
+ state_ = media::kIsClosed;
+ if (send_stop_flag_) {
+ const int ret = bt_hid_rc_stop_sending_voice(nullptr);
+ LOG(INFO) << "bt_hid_rc_stop_sending_voice = " << ret;
+ send_stop_flag_ = false;
+ }
+
+ bt_hid_unset_audio_data_receive_cb();
+ bt_hid_host_deinitialize();
+ bt_product_deinit();
+}
+
+#define SEND_COMMAND_BUFFER_COUNT 250
+#define MAX_BUFFER_COUNT 100000
+void CapiBtAudioInputStream::ReadBTAudioData(bt_hid_voice_data_s* voice_data) {
+ if (state_ != media::kIsStarted) {
+ LOG(INFO) << "Not started yet, but send audio data vi Bluetooth";
+ return;
+ }
+
+ static int buffer_count = 0;
+ if (SEND_COMMAND_BUFFER_COUNT == buffer_count) {
+ const unsigned char input_data[2] = {SMART_CONTROL_EXTEND_CMD, 0x10};
+ if (BT_ERROR_NONE !=
+ bt_hid_send_rc_command(nullptr, input_data, sizeof(input_data))) {
+ LOG(ERROR) << "Fail bt_hid_send_rc_command";
+ } else {
+ LOG(INFO) << "Extend bt audio recorder";
+ }
+ }
+ if (MAX_BUFFER_COUNT == buffer_count) {
+ buffer_count = 0;
+ }
+ buffer_count++;
+
+ int number_of_frames =
+ voice_data->length / params_.GetBytesPerFrame(kDefaultSampleFormat);
+ if (number_of_frames > fifo_.GetUnfilledFrames()) {
+ int increase_blocks_of_buffer =
+ (number_of_frames - fifo_.GetUnfilledFrames()) /
+ params_.frames_per_buffer() +
+ 1;
+ fifo_.IncreaseCapacity(increase_blocks_of_buffer);
+ }
+
+ fifo_.Push((const void*)voice_data->audio_buf, number_of_frames,
+ kBitsPerSample / 8);
+
+#ifdef BUF_SAVE_MODE
+ if (g_dump_file) /* write pcm buffer */
+ fwrite(voice_data->audio_buf, 1, voice_data->length, g_dump_file);
+#endif // BUF_SAVE_MODE
+
+ double normalized_volume = 0.0;
+ GetAgcVolume(&normalized_volume);
+
+ double hardware_delay_seconds =
+ static_cast<double>(params_.GetBufferDuration().InSeconds() * 2) *
+ params_.channels();
+
+ while (fifo_.available_blocks()) {
+ const AudioBus* audio_bus = fifo_.Consume();
+ hardware_delay_seconds +=
+ static_cast<double>(fifo_.GetAvailableFrames()) / params_.sample_rate();
+
+ // To reduce latency run client CB from dedicated thread
+ if (callback_) {
+ // Need to copy data out if it runs in different thread
+ std::unique_ptr<AudioBus> audio_bus_copy =
+ AudioBus::Create(audio_bus->channels(), audio_bus->frames());
+ audio_bus->CopyTo(audio_bus_copy.get());
+ audio_worker_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CapiBtAudioInputStream::OnAudioIOData,
+ base::Unretained(this), std::move(audio_bus_copy),
+ hardware_delay_seconds, normalized_volume));
+ }
+ }
+}
+
+} // namespace media
--- /dev/null
+// Copyright (c) 2021 The Samsung 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 MEDIA_AUDIO_TIZEN_CAPI_BT_AUDIO_INPUT_STREAM_H_
+#define MEDIA_AUDIO_TIZEN_CAPI_BT_AUDIO_INPUT_STREAM_H_
+
+#include <audio_io.h>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "media/audio/audio_debug_recording_helper.h"
+#include "media/audio/tizen/capi_audio_input.h"
+#include "media/audio/tizen/capi_util.h"
+#include "media/base/audio_block_fifo.h"
+#include "media/base/audio_parameters.h"
+
+#include <bluetooth_product.h>
+
+namespace media {
+
+class AudioManagerCapi;
+
+class CapiBtAudioInputStream final : public CapiAudioInputStream {
+ public:
+ CapiBtAudioInputStream(AudioManagerCapi* audio_manager,
+ const AudioParameters& params);
+
+ CapiBtAudioInputStream(const CapiBtAudioInputStream&) = delete;
+ CapiBtAudioInputStream& operator=(const CapiBtAudioInputStream&) = delete;
+
+ ~CapiBtAudioInputStream() override;
+
+ protected:
+ bool OpenMic() override;
+ void StartMic() override;
+ void StopMic() override;
+ void CloseMic(bool success) override;
+
+ private:
+ void CreateAudioDumpFile();
+ static void BTHidStateChangedCB(int result,
+ bool connected,
+ const char* remote_address,
+ void* user_data);
+ static void BTHidAudioDataReceiveCB(bt_hid_voice_data_s* voice_data,
+ void* user_data);
+ void ReadBTAudioData(bt_hid_voice_data_s* voice_data);
+
+ bool send_stop_flag_;
+ base::WeakPtrFactory<CapiBtAudioInputStream> weak_factory_;
+};
+
+} // namespace media
+#endif // MEDIA_AUDIO_TIZEN_CAPI_AUDIO_INPUT_H_
"//tizen_src/chromium_impl/media/audio/tizen/capi_util.cc",
"//tizen_src/chromium_impl/media/audio/tizen/capi_util.h",
]
+
+ if (tizen_product_tv) {
+ external_media_efl_audio_io_sources += [
+ "//tizen_src/chromium_impl/media/audio/tizen/capi_bt_audio_input_stream.cc",
+ "//tizen_src/chromium_impl/media/audio/tizen/capi_bt_audio_input_stream.h",
+ ]
+ external_media_efl_audio_io_config += [
+ "//tizen_src/build:capi-network-bluetooth",
+ "//tizen_src/build:libcapi-network-bluetooth",
+ "//tizen_src/build:capi-network-bluetooth-tv",
+ "//tizen_src/build:libcapi-network-bluetooth-tv",
+ ]
+ }
}
return unfocus_allow_cb_.Run(ewk_view_, direction, callback_result);
}
+#if BUILDFLAG(IS_TIZEN_TV)
+void EWebView::SetViewSmartrcMicNotificationCallback(
+ Ewk_View_SmartRC_Mic_Notification_Callback callback,
+ void* user_data) {
+ smartrc_mic_notification_cb_.Set(callback, user_data);
+}
+
+void EWebView::ShowMicOpenedNotification(bool show) {
+ if (smartrc_mic_notification_cb_.IsCallbackSet()) {
+ smartrc_mic_notification_cb_.Run(ewk_view_, static_cast<Eina_Bool>(show));
+ } else {
+ LOG(INFO)
+ << "call ewk_view_smartrc_show_mic_notification_callback_set to get "
+ "notified when bluetooth mic is going to access user voice. "
+ << show;
+ }
+}
+#endif
+
void EWebView::StopFinding() {
web_contents_->StopFinding(content::STOP_FIND_ACTION_CLEAR_SELECTION);
}
void* user_data);
bool InvokeViewUnfocusAllowCallback(Ewk_Unfocus_Direction direction,
Eina_Bool* result);
+#if BUILDFLAG(IS_TIZEN_TV)
+ void SetViewSmartrcMicNotificationCallback(
+ Ewk_View_SmartRC_Mic_Notification_Callback callback,
+ void* user_data);
+ void ShowMicOpenedNotification(bool show);
+#endif
void DidChangeContentsSize(int width, int height);
const Eina_Rectangle GetContentsSize() const;
void GetScrollSize(int* w, int* h);
load_error_page_cb_;
WebViewCallback<Ewk_View_Unfocus_Allow_Callback, Ewk_Unfocus_Direction>
unfocus_allow_cb_;
+#if BUILDFLAG(IS_TIZEN_TV)
+ WebViewCallback<Ewk_View_SmartRC_Mic_Notification_Callback, bool>
+ smartrc_mic_notification_cb_;
+#endif
WebViewCallback<Ewk_View_Notification_Permission_Callback,
Ewk_Notification_Permission_Request*>
notification_permission_callback_;
impl->SetViewUnfocusAllowCallback(callback, user_data);
}
+void ewk_view_smartrc_show_mic_notification_callback_set(Evas_Object* ewkView, Ewk_View_SmartRC_Mic_Notification_Callback callback, void* user_data)
+{
+#if BUILDFLAG(IS_TIZEN_TV)
+ EWK_VIEW_IMPL_GET_OR_RETURN(ewkView, impl);
+ impl->SetViewSmartrcMicNotificationCallback(callback, user_data);
+#else
+ LOG_EWK_API_MOCKUP("This API is only available in Tizen TV product.");
+#endif
+}
+
void ewk_view_geolocation_permission_callback_set(Evas_Object* ewk_view, Ewk_View_Geolocation_Permission_Callback callback, void* user_data)
{
EWK_VIEW_IMPL_GET_OR_RETURN(ewk_view, impl);
LOG_EWK_API_MOCKUP();
}
-void ewk_view_smartrc_show_mic_notification_callback_set(Evas_Object* o, Ewk_View_SmartRC_Mic_Notification_Callback callback, void* user_data)
-{
- LOG_EWK_API_MOCKUP();
-}
-
Eina_Bool ewk_view_set_support_canvas_hole(Evas_Object* ewkView, const char* url)
{
LOG_EWK_API_MOCKUP();
int media_position) {
return web_view_->NotifyPESData(buf, len, media_position);
}
+
+void WebContentsDelegateEfl::ShowMicOpenedNotification(bool show) {
+ web_view_->ShowMicOpenedNotification(show);
+}
#endif
void WebContentsDelegateEfl::OnGetMainFrameScrollbarVisible(int callback_id,
base::OnceCallback<void(const MediaDeviceEnumeration&)>;
void GetMediaDeviceList(EnumerationCallback cb);
void VideoPlayingStatusReceived(bool is_playing, int callback_id) override;
+ void ShowMicOpenedNotification(bool show) override;
#endif
#if defined(TIZEN_AUTOFILL)