[GS] Adding mm-audioout as an output for audio in game streaming 74/321174/21
authorAdam Bujalski <a.bujalski@samsung.com>
Fri, 6 Sep 2024 12:19:54 +0000 (14:19 +0200)
committerBot Blink <blinkbot@samsung.com>
Thu, 28 Nov 2024 20:16:52 +0000 (20:16 +0000)
This patch adds aplication ability to select multichannel PCM output
(MCH PCM) for all audio played by Chromium. Features:
- Mixer support - it is possible to mix audio from various sources like
  WebAudio, HTML5 audio/video element, WebRTC
- Up to 8 channels (7.1) audio support
- Lower latency than default implementation using Tizen AudioI/O API.

To request playing audio using this output application must add
following entry:

```
<tizen:metadata
   key="http://samsung.com/tv/metadata/use.multichannel.pcm.output"
   value="true"/>
```

To its `config.xml` file.

Bug: https://jira-eu.sec.samsung.net/browse/VDGAME-315
Signed-off-by: Adam Bujalski <a.bujalski@samsung.com>
Change-Id: I3453b5657d50f1f34f9f2443093ba5d466f31ee8

29 files changed:
base/task/sequenced_task_runner.h
content/renderer/renderer_blink_platform_impl.cc
gpu/config/gpu_info_collector_linux.cc
media/audio/BUILD.gn
media/base/media_switches.cc
media/base/media_switches.h
packaging/chromium-efl.spec
services/audio/BUILD.gn
third_party/blink/renderer/platform/peerconnection/audio_codec_factory.cc
tizen_src/build/BUILD.gn
tizen_src/build/config/BUILD.gn
tizen_src/build/config/tizen_features.gni
tizen_src/chromium_impl/base/tizen/resource_manager.h
tizen_src/chromium_impl/media/BUILD.gn
tizen_src/chromium_impl/media/audio/tizen/audio_manager_capi.cc
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.cc [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.h [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.cc [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.h [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.cc [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.h [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.cc [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.h [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.cc [new file with mode: 0644]
tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.h [new file with mode: 0644]
tizen_src/chromium_impl/media/media_efl.gni
tizen_src/chromium_impl/third_party/blink/public/platform/tizen/tizen_tv_wasm_version/BUILD.gn
tizen_src/chromium_impl/tizen/tizen_tv_platform.h
wrt/src/browser/tv/wrt_native_window_tv.cc

index 6668ada703f2cd51149f00fc2c4b5f5a64d46446..167b0542998d866bdd3b064ba76a8c8ab7fe4f41 100644 (file)
@@ -32,6 +32,9 @@ class FakeAudioWorker;
 #if defined(TIZEN_MULTIMEDIA)
 class PipelineImpl;
 #endif
+#if defined(TIZEN_TV_USE_MCH_PCM)
+class MchPcmOutputStream;
+#endif
 }  // namespace media
 
 namespace base {
@@ -70,6 +73,9 @@ class PostDelayedTaskPassKey {
 #if defined(TIZEN_MULTIMEDIA)
   friend class media::PipelineImpl;
 #endif
+#if defined(TIZEN_TV_USE_MCH_PCM)
+  friend class media::MchPcmOutputStream;
+#endif
 };
 
 class PostDelayedTaskPassKeyForTesting : public PostDelayedTaskPassKey {};
index a8ca217dd5adbd6db2b1f153f15a13deb7ef26a8..34ea94dcd547b55dcc9784cf47530a3edeb90d9c 100644 (file)
@@ -602,6 +602,11 @@ bool RendererBlinkPlatformImpl::IsWebRtcSrtpEncryptedHeadersEnabled() {
 
 bool RendererBlinkPlatformImpl::IsWebRtcAudioBypassEnabled() {
 #if BUILDFLAG(IS_TIZEN_TV)
+  if (media::UseMchPcmOutput()) {
+    LOG(INFO) << "Use MCH PCM output, disable audio by-pass.";
+    return false;
+  }
+
   return media::IsGameModeEnabled() &&
          base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kSingleProcess);
index d7119744912eccb3840df15bda1e2ddd1fb342fb..267ad1e0745d22a1d29ee951054b4ef056420205 100644 (file)
 
 namespace gpu {
 
-#if BUILDFLAG(IS_TIZEN_TV)
-namespace {
-int GetTVProductYear() {
-  constexpr static const char* kKeyProductYear =
-      "com.samsung/featureconf/product.tv_year";
-  int result = 0;
-  int ret = system_info_get_custom_int(kKeyProductYear, &result);
-  if (ret != SYSTEM_INFO_ERROR_NONE) {
-    LOG(WARNING) << "Failed to get system info : " << kKeyProductYear;
-    return 0;
-  }
-  return result;
-}
-}  // namespace
-#endif  // BUILDFLAG(IS_TIZEN_TV)
-
 bool CollectContextGraphicsInfo(GPUInfo* gpu_info) {
   DCHECK(gpu_info);
 
index 5b9cf29fc4c1da2ac01fcfcdf1b361eaf04e3be7..7970cccb2003deb3d3004e11d013de3fb0a5ddf7 100644 (file)
@@ -171,6 +171,7 @@ source_set("audio") {
   if (tizen_audio_io) {
     configs += external_media_efl_audio_io_config
     sources += external_media_efl_audio_io_sources
+    deps += external_media_efl_audio_io_deps
   }
 
   if (is_apple) {
index f1e60d249eceb68a4f6fe7d4c60b3f2e294bf953..c06f3dc02ada6dd3dd01508970bab745c0e15bc3 100644 (file)
 #include "base/cpu.h"
 #endif
 
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "tizen/tizen_tv_platform.h"
+#endif
+
 namespace switches {
 
 // Allow users to specify a custom buffer size for debugging purpose.
@@ -235,6 +239,7 @@ const char kEnableLiveCaptionPrefForTesting[] =
 const char kEnableGameMode[] = "enable-game-mode";
 const char kEnableNDecoding[] = "enable-n-decoding";
 const char kEnableUpstreamArchitecture[] = "enable-upstream-architecture";
+const char kUseMultichannelPcmOutput[] = "use-multichannel-pcm-output";
 #endif
 
 #if BUILDFLAG(IS_TIZEN_TV)
@@ -1781,6 +1786,27 @@ bool IsGameModeEnabled() {
          base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kEnableGameMode);
 }
+
+bool UseMchPcmOutput() {
+#if BUILDFLAG(IS_TIZEN_TV)
+  // Since Tizen7.0 onward device `/dev/snd/comprC2D7` is present and serves
+  // as Multichannel PCM output. On previous years models the same device
+  // served for different purpose and using it as MCH PCM will crash.
+  static constexpr int kTizen70Year = 23;
+  if (GetTVProductYear() < kTizen70Year) {
+    // Test needed for OSU, as `/dev/snd/comprC2D7` is product part and
+    // its behavior won't change during OS upgrade.
+    return false;
+  }
+
+  return base::CommandLine::ForCurrentProcess() &&
+         base::CommandLine::ForCurrentProcess()->HasSwitch(
+             switches::kUseMultichannelPcmOutput);
+#else
+  return false;
+#endif
+}
+
 #endif
 
 }  // namespace media
index f41927c7110bbb73cceb4a0e84d428938ae94b93..1c0b3fdbf4bc57f723ba6c8ace00f187b96f9936 100644 (file)
@@ -99,6 +99,7 @@ MEDIA_EXPORT extern const char kEnableLiveCaptionPrefForTesting[];
 MEDIA_EXPORT extern const char kEnableGameMode[];
 MEDIA_EXPORT extern const char kEnableNDecoding[];
 MEDIA_EXPORT extern const char kEnableUpstreamArchitecture[];
+MEDIA_EXPORT extern const char kUseMultichannelPcmOutput[];
 #endif
 
 #if BUILDFLAG(IS_TIZEN_TV)
@@ -534,6 +535,8 @@ MEDIA_EXPORT uint32_t GetPassthroughAudioFormats();
 MEDIA_EXPORT bool IsUpstreamArchitectureEnabled();
 
 MEDIA_EXPORT bool IsGameModeEnabled();
+
+MEDIA_EXPORT bool UseMchPcmOutput();
 #endif
 
 }  // namespace media
index b3de81168e5a15bc3a297fb82f97c6ed753d2cf1..cae3670227c36811fcdc9c4e58761065a755bf88 100644 (file)
@@ -150,6 +150,22 @@ BuildRequires: pkgconfig(video-capture)
 %define __tizen_tv_upstream_multimedia_omx_ppi 0
 %endif
 
+# Multichannel PCM (MCH PCM) low-latency output.
+%if "%{__tizen_tv_upstream_multimedia}" == "1" && %{tizen_version} >= 70 && "%{?_with_emulator}" != "1" && "%{_vd_cfg_licensing}" == "n"
+%define __tizen_tv_use_mch_pcm 1
+%if %{tizen_version} >= 90 && "%{?__tizen_tv_riscv64}" != "1"
+%define __tizen_tv_mch_pcm_use_ppi 1
+%define __tizen_tv_mch_pcm_use_tinycompress 0
+%else
+%define __tizen_tv_mch_pcm_use_ppi 0
+%define __tizen_tv_mch_pcm_use_tinycompress 1
+%endif
+%else
+%define __tizen_tv_use_mch_pcm 0
+%define __tizen_tv_mch_pcm_use_ppi 0
+%define __tizen_tv_mch_pcm_use_tinycompress 0
+%endif
+
 %{?_use_system_icu: %define __use_system_icu %{_use_system_icu}}
 %if "%{?profile}" == "tv" && %{tizen_version} > 80 && %{?_use_system_icu: 0}%{!?_use_system_icu: 1} && "%{?__tizen_tv_riscv64}" != "1"
 %define __use_system_icu 1
@@ -339,6 +355,15 @@ BuildRequires: pkgconfig(mm-omxil)
 %if %{tizen_version} < 80
 BuildRequires: pkgconfig(mm-common)
 %endif
+
+%if "%{__tizen_tv_mch_pcm_use_ppi}" == "1"
+BuildRequires: pkgconfig(mm-audioout)
+%endif
+
+%if "%{__tizen_tv_mch_pcm_use_tinycompress}" == "1"
+BuildRequires: pkgconfig(tinycompress)
+%endif
+
 %endif
 %endif
 
@@ -871,6 +896,15 @@ touch ./tizen_src/downloadable/ewk_api_wrapper_generator.py
 %if "%{__build_tizen_media_stream_processor}" == "1"
   "tizen_ai=true" \
 %endif
+%if %{__tizen_tv_use_mch_pcm}
+  "tizen_tv_use_mch_pcm=true" \
+%endif
+%if %{__tizen_tv_mch_pcm_use_ppi}
+  "tizen_tv_mch_pcm_use_ppi=true" \
+%endif
+%if %{__tizen_tv_mch_pcm_use_tinycompress}
+  "tizen_tv_mch_pcm_use_tinycompress=true" \
+%endif
 %endif  # _skip_gn
 
 ninja %{_smp_mflags} -C "%{OUTPUT_FOLDER}" \
index 394a112f4f389ad57026691c187b8d86e29d277a..d1040278f0466ee1a66b6644743908bb7e377923 100644 (file)
@@ -7,6 +7,7 @@ import("//build/config/chromecast_build.gni")
 import("//build/config/chromeos/ui_mode.gni")
 import("//media/media_options.gni")
 import("//testing/test.gni")
+import("//tizen_src/build/config/tizen_features.gni")
 
 source_set("audio") {
   sources = [
@@ -123,6 +124,21 @@ source_set("audio") {
     public_deps += [ "//media/webrtc" ]
   }
 
+  if (tizen_tv_use_mch_pcm) {
+    sources += [
+      "mixing_graph.cc",
+      "mixing_graph.h",
+      "mixing_graph_impl.cc",
+      "mixing_graph_impl.h",
+      "output_device_mixer.cc",
+      "output_device_mixer.h",
+      "output_device_mixer_impl.cc",
+      "output_device_mixer_impl.h",
+      "sync_mixing_graph_input.cc",
+      "sync_mixing_graph_input.h",
+    ]
+  }
+
   configs += [
     "//build/config/compiler:wexit_time_destructors",
     "//media:media_config",
index f1e1d6d7c949476997a33364f1f6578a142f109f..a61aefc3bd73406928765b16c1f7da763e9c8f2c 100644 (file)
@@ -8,7 +8,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/command_line.h"
 #include "base/strings/string_util.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/webrtc/api/audio_codecs/L16/audio_decoder_L16.h"
@@ -25,6 +24,7 @@
 #include "third_party/webrtc/api/audio_codecs/opus/audio_encoder_opus.h"
 
 #if BUILDFLAG(IS_TIZEN_TV)
+#include "media/base/media_switches.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_audio_bypass_opus_impl.h"
 #endif
 
@@ -114,7 +114,7 @@ class AudioDecoderFactoryWithBypass : public webrtc::AudioDecoderFactory {
         format.clockrate_hz, format.num_channels, volume);
 #else
     return nullptr;
-#endif  // OS_TIZEN_TV_PRODUCT
+#endif  // BUILDFLAG(IS_TIZEN_TV)
   }
 
  private:
@@ -131,6 +131,15 @@ CreateWebrtcAudioDecoderFactory() {
   }
 #endif
 
+#if BUILDFLAG(IS_TIZEN_TV) && defined(TIZEN_TV_USE_MCH_PCM)
+  if (media::UseMchPcmOutput()) {
+    return webrtc::CreateAudioDecoderFactory<
+        webrtc::AudioDecoderOpus, webrtc::AudioDecoderG722,
+        webrtc::AudioDecoderG711, webrtc::AudioDecoderMultiChannelOpus,
+        NotAdvertisedDecoder<webrtc::AudioDecoderL16>>();
+  }
+#endif
+
   return webrtc::CreateAudioDecoderFactory<
       webrtc::AudioDecoderOpus, webrtc::AudioDecoderG722,
       webrtc::AudioDecoderG711, NotAdvertisedDecoder<webrtc::AudioDecoderL16>,
index 8efcd091b8321584db01083b57ce292392161c67..b1edd242456be3f8e64290b399b43c7053531b01 100644 (file)
@@ -671,6 +671,18 @@ config("capi-network-bluetooth") {
   }
 }
 
+if (tizen_tv_mch_pcm_use_ppi) {
+  tizen_pkg_config("ppi-mm-audioout") {
+    packages = [ "mm-audioout" ]
+  }
+}
+
+if (tizen_tv_mch_pcm_use_tinycompress) {
+  tizen_pkg_config("tinycompress") {
+    packages = [ "tinycompress" ]
+  }
+}
+
 tizen_pkg_config("libcapi-network-bluetooth") {
   packages = []
   if (tizen_product_tv) {
index 4cca6f3d600f8639f00125d3ed07609be13878ed..68b77f2387bc03ce077b10151c8809cc19b7daf4 100644 (file)
@@ -81,6 +81,14 @@ config("tizen_feature_flags") {
     if (tizen_audio_io) {
       defines += [ "TIZEN_MULTIMEDIA_USE_CAPI_AUDIO_IO" ]
     }
+    if (tizen_tv_use_mch_pcm) {
+      defines += [ "TIZEN_TV_USE_MCH_PCM" ]
+      if (tizen_tv_mch_pcm_use_ppi) {
+        defines += [ "TIZEN_TV_MCH_PCM_USE_PPI" ]
+      } else if (tizen_tv_mch_pcm_use_tinycompress) {
+        defines += [ "TIZEN_TV_MCH_PCM_USE_TINYCOMPRESS" ]
+      }
+    }
     if (tizen_atmos_decoder_enable) {
       defines += [ "ENABLE_ATMOS_DECODER" ]
     }
index f832a5db9cfe6ea51bd95200a38ba50eefc48f2f..259d68f41b9337c7eea0e06993cd023f9f04fbf2 100644 (file)
@@ -92,6 +92,9 @@ declare_args() {
   tizen_tv_upstream_multimedia = false
   tizen_tv_upstream_multimedia_omx = false
   tizen_tv_upstream_multimedia_omx_ppi = false
+  tizen_tv_use_mch_pcm = false
+  tizen_tv_mch_pcm_use_ppi = false
+  tizen_tv_mch_pcm_use_tinycompress = false
   tizen_rtc_unittests = false
   tizen_tv_video_capture = false
 
index 98e150a1d5c22c9f7fc74a14b1f060b97020db90..5d445f4d808aa4bf92eb1a6d6e5e79501ac68e2b 100644 (file)
@@ -173,6 +173,10 @@ class ResourceManager : public suspend_resume::SuspendResumeClient {
 
   void ReleaseDevice(int device_id);
 
+#if defined(TIZEN_TV_USE_MCH_PCM)
+  int handle() const { return handle_; }
+#endif
+
  protected:
   ResourceManager() : entry_(nullptr) {}
   explicit ResourceManager(ResourceManagerEntry* entry) : entry_(entry) {}
index 10788042083b971ef376c087bd0bec15124be1ba..11faa5d4cbce67f12afb669b6f1bb22f96cf3836 100644 (file)
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//tizen_src/build/config/tizen_features.gni")
+import("//tizen_src/chromium_impl/media/base/tizen/logger/logger.gni")
 
 config("media_efl_config") {
   include_dirs = [
@@ -12,3 +13,9 @@ config("media_efl_config") {
     "//v8/include",
   ]
 }
+
+if (tizen_tv_use_mch_pcm) {
+  tizen_media_logger_config("mch_pcm_logger_config") {
+    logger_tag = "AUDIO_MCH_PCM"
+  }
+}
index fc5e0a8906e33e6e6be06b4c4388b30b9c9421da..78ebb6c2a1b16afd56a11b3b086f22ab757f77bf 100644 (file)
@@ -18,6 +18,8 @@
 #include "media/base/audio_parameters.h"
 #include "media/base/channel_layout.h"
 #include "media/base/limits.h"
+#include "media/base/media_switches.h"
+
 #if BUILDFLAG(IS_TIZEN_TV)
 #include "build/tizen_version.h"
 #include "media/audio/tizen/capi_bt_audio_input_stream.h"
 #define UNIQUE_BT_DEVICE_ID "1"
 #endif
 
+#if defined(TIZEN_TV_USE_MCH_PCM)
+#include "media/audio/tizen/mch_pcm_manager.h"
+#endif
+
 namespace media {
 
 std::unique_ptr<AudioManager> CreateAudioManager(
@@ -261,11 +267,9 @@ void AudioManagerCapi::GetAudioDeviceNames(
   if (device_names->empty()) {
     device_names->push_front(AudioDeviceName::CreateDefault());
   }
-
-  return;
-#endif
-
+#else
   device_names->push_front(AudioDeviceName::CreateDefault());
+#endif
 }
 
 void AudioManagerCapi::GetAudioInputDeviceNames(
@@ -393,6 +397,13 @@ AudioParameters AudioManagerCapi::GetPreferredOutputStreamParameters(
   int frame_per_buffer = kDefaultOutputBufferSize;
   int sample_rate = kDefaultSampleRate;
   ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+
+#if defined(TIZEN_TV_USE_MCH_PCM)
+  if (UseMchPcmOutput()) {
+    return MchPcmManager::GetPreferredOutputStreamParameters(input_params);
+  }
+#endif
+
   if (input_params.IsValid()) {
     channel_layout = input_params.channel_layout();
     frame_per_buffer = input_params.frames_per_buffer();
@@ -445,6 +456,12 @@ AudioParameters AudioManagerCapi::GetPreferredOutputStreamParameters(
 AudioOutputStream* AudioManagerCapi::MakeOutputStream(
     const AudioParameters& params,
     const std::string& input_device_id) {
+#if defined(TIZEN_TV_USE_MCH_PCM)
+  if (UseMchPcmOutput()) {
+    return MchPcmManager::GetInstance().CreateOutputStream(params, this);
+  }
+#endif
+
   return new CapiAudioOutputStream(params, this);
 }
 
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.cc b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.cc
new file mode 100644 (file)
index 0000000..8ad0b80
--- /dev/null
@@ -0,0 +1,619 @@
+// Copyright 2023 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 "media/audio/tizen/mch_pcm_device.h"
+
+#include <avoc/avoc.h>
+#include <avoc/avoc_avsink.h>
+#include <avoc/avoc_mls.h>
+#include <rm_type.h>
+
+#include <memory>
+#include <type_traits>
+
+#include "base/check.h"
+#include "base/tizen/global_resource_manager.h"
+#include "base/tizen/provider_callbacks_helper.h"
+#include "media/audio/tizen/mch_pcm_manager.h"
+#include "media/base/tizen/logger/media_logger.h"
+
+#if defined(TIZEN_TV_MCH_PCM_USE_PPI)
+#include <mm-audioout/ppi-mm-audioout.h>
+#include <mm-audioout/ppi-mm-audioout-format.h>
+#elif defined(TIZEN_TV_MCH_PCM_USE_TINYCOMPRESS)
+#include <sound/compress_params.h>
+#include <sound/compress_offload.h>
+#include <tinycompress/tinycompress.h>
+#endif
+
+namespace media {
+
+namespace {
+
+// Values taken from mmpcmsink:
+// Default card supporting compress writes
+static constexpr int kDefaultCard = 2;  // sif_scard_audio
+
+// Devices:
+// 7 - Compress Mch Pcm Rendering / Compress Mch Pcm
+// 10 - Main Port Renderer / Main speaker
+// 11 - Sub Port Renderer / Remote speaker
+static constexpr int kDefaultDeviceNum = 7;
+
+static constexpr int kDefaultSourceType = AVOC_AUDIO_SOURCE_MULTIMEDIA_DEC0;
+static constexpr int kMainAudioOut = 0;
+
+}
+
+#if defined(TIZEN_TV_MCH_PCM_USE_PPI)
+class MchPcmDevice::Impl {
+ public:
+  static std::unique_ptr<Impl> Create(uint32_t channel_count,
+                                      uint32_t sample_rate) {
+    TIZEN_MEDIA_LOG_NO_INSTANCE(INFO) << "Creating PPI, params:"
+                                      << " channel_count: " << channel_count
+                                      << " sample_rate: " << sample_rate;
+    mm_audioout_format_handle_t fmt;
+    PPI_ERROR err = ppi_mm_audioout_format_create(&fmt);
+    if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+      TIZEN_MEDIA_LOG_NO_INSTANCE(ERROR)
+          << "Failed to create ppi audio out format, err: " << err;
+      return nullptr;
+    }
+
+    TIZEN_MEDIA_LOG_NO_INSTANCE(INFO) << "PPI audio out format created.";
+    ppi_mm_audioout_format_set_codec_type(fmt, PPI_MM_AUDIOOUT_CODEC_TYPE_PCM);
+    ppi_mm_audioout_format_set_channels(fmt, channel_count);
+    ppi_mm_audioout_format_set_samplerate(fmt, sample_rate);
+    ppi_mm_audioout_format_set_bit_depth(fmt, 16);
+    ppi_mm_audioout_format_set_endianness(fmt, PPI_MM_AUDIOOUT_LITTLE_ENDIAN);
+    ppi_mm_audioout_format_set_signedness(fmt, PPI_MM_AUDIOOUT_SIGNED);
+    ppi_mm_audioout_format_set_gamemode(fmt, true);
+    ppi_mm_audioout_format_set_associated_resource_type(
+        fmt, PPI_MM_AUDIOOUT_MAIN_RESOURCE);
+    ppi_mm_audioout_format_set_buffer_size(
+        fmt, PPI_MM_AUDIOOUT_DEFAULT_FRAGMENT_SIZE,
+        PPI_MM_AUDIOOUT_DEFAULT_FRAGMENTS);
+
+    std::unique_ptr<Impl> ret{new Impl(fmt)};
+    if (!ret->Open()) {
+      return nullptr;
+    }
+
+    return ret;
+  }
+
+  ~Impl() {
+    TIZEN_MEDIA_LOG(INFO);
+    if (handle_) {
+      auto err = ppi_mm_audioout_close(handle_);
+      if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+        TIZEN_MEDIA_LOG(WARNING) << "Failed to close handle. Error: " << err;
+      }
+      err = ppi_mm_audioout_destroy(handle_);
+      if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+        TIZEN_MEDIA_LOG(WARNING) << "Failed to destroy handle. Error: " << err;
+      }
+    }
+
+    if (format_) {
+      auto err = ppi_mm_audioout_format_destroy(format_);
+      if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+        TIZEN_MEDIA_LOG(WARNING) << "Failed to destroy format. Error: " << err;
+      }
+    }
+  }
+
+  PPI_ERROR Stop() {
+    DCHECK(handle_);
+    TIZEN_MEDIA_LOG(WARNING) << "Stopping PPI audio device not supported.";
+    return PPI_MM_AUDIOOUT_ERROR_NOT_SUPPORTED;
+  }
+
+  PPI_ERROR Flush() {
+    DCHECK(handle_);
+    return ppi_mm_audioout_flush(handle_);
+  }
+
+  // TODO(a.bujalski) use variant
+  int32_t Write(const uint8_t* buffer_data, size_t buffer_size) {
+    DCHECK(handle_);
+    int32_t written_size = 0;
+    PPI_ERROR err = ppi_mm_audioout_write(
+        handle_, const_cast<char*>(reinterpret_cast<const char*>(buffer_data)),
+        buffer_size, &written_size);
+    if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+      DCHECK(err < 0);
+      return err < 0 ? err  : -err;
+    }
+
+    return written_size;
+  }
+
+  PPI_ERROR Start() {
+    DCHECK(handle_);
+    return ppi_mm_audioout_start(handle_);
+  }
+
+  absl::optional<size_t> GetRemainBuffer() {
+    DCHECK(handle_);
+
+    int32_t frame_count = 0;
+    PPI_ERROR ret =
+        ppi_mm_audioout_get_buffered_frame_count(handle_, &frame_count);
+    if (ret != PPI_MM_AUDIOOUT_ERROR_OK) {
+      TIZEN_MEDIA_LOG(ERROR) << "Failed to get buffer size: " << ret;
+      return absl::nullopt;
+    }
+
+    return static_cast<size_t>(frame_count);
+  }
+
+  const char* GetErrorSting(int err) {
+    DCHECK(handle_);
+    switch (err) {
+      case PPI_MM_AUDIOOUT_ERROR_OK:
+        return "No error";
+      case PPI_MM_AUDIOOUT_ERROR_NOT_SUPPORTED:
+        return "Not supported";
+      case PPI_MM_AUDIOOUT_ERROR_BUFFER_NO_SPACE:
+        return "No buffer space";
+      case PPI_MM_AUDIOOUT_ERROR_NO_MEMORY:
+        return "Out of memory";
+      case PPI_MM_AUDIOOUT_ERROR_INVALID_PARAMETER:
+        return "Invalid parameter";
+      default:
+        return "Unknown error";
+    }
+  }
+
+ private:
+  explicit Impl(mm_audioout_format_handle_t format) : format_(format) {}
+
+  bool Open() {
+    DCHECK(format_);
+
+    mm_audioout_handle_t out;
+    PPI_ERROR err = ppi_mm_audioout_create(&out);
+    if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+      TIZEN_MEDIA_LOG(ERROR) << "Failed to create PPI audio out";
+      return false;
+    }
+    handle_ = out;
+
+    ppi_mm_audioout_device_info_s device{};
+    device.card = kDefaultCard;
+    device.device = kDefaultDeviceNum;
+
+    err = ppi_mm_audioout_open(&device, format_, handle_);
+    if (err != PPI_MM_AUDIOOUT_ERROR_OK) {
+      TIZEN_MEDIA_LOG(ERROR) << "Failed to open PPI audio out";
+      return false;
+    }
+
+    TIZEN_MEDIA_LOG(INFO) << "PPI audio out stream opened.";
+    return true;
+  }
+
+  mm_audioout_format_handle_t format_{};
+  mm_audioout_handle_t handle_{};
+};
+#elif defined(TIZEN_TV_MCH_PCM_USE_TINYCOMPRESS)
+class MchPcmDevice::Impl {
+ public:
+  static std::unique_ptr<Impl> Create(uint32_t channel_count,
+                                      uint32_t sample_rate) {
+    TIZEN_MEDIA_LOG_NO_INSTANCE(INFO) << "Creating tinycompress device, params:"
+                                      << " channel_count: " << channel_count
+                                      << " sample_rate: " << sample_rate;
+    std::unique_ptr<Impl> ret{new Impl(channel_count, sample_rate)};
+    if (!ret->Open()) {
+      return nullptr;
+    }
+
+    return ret;
+  }
+
+  void Stop() {
+    DCHECK(compress_);
+    compress_stop(compress_.get());
+  }
+
+  void Flush() {
+    DCHECK(compress_);
+    compress_drain(compress_.get());
+  }
+
+  int Write(const uint8_t* buffer_data, size_t buffer_size) {
+    DCHECK(compress_);
+    return compress_write(compress_.get(), buffer_data, buffer_size);
+  }
+
+  int Start() {
+    DCHECK(compress_);
+    return compress_start(compress_.get());
+  }
+
+  absl::optional<size_t> GetRemainBuffer() {
+    DCHECK(compress_);
+
+    snd_compr_metadata data{};
+    int ret = compress_get_remain_buffer(compress_.get(), &data);
+    if (ret < 0) {
+      TIZEN_MEDIA_LOG(ERROR) << "Failed to get buffer size: " << errno;
+      return absl::nullopt;
+    }
+
+    return static_cast<size_t>(data.value[0]);
+  }
+
+  const char* GetErrorSting(int err) {
+    DCHECK(compress_);
+    static_cast<void>(err);
+    return compress_get_error(compress_.get());
+  }
+
+ private:
+  struct Deleter {
+    void operator()(compress* device) const {
+      compress_close(device);
+    }
+  };
+
+  Impl(uint32_t channel_count, uint32_t sample_rate) {
+    codec_.id = SND_AUDIOCODEC_PCM;
+    codec_.ch_in = channel_count;
+    codec_.ch_out = channel_count;
+    codec_.sample_rate = sample_rate;
+    codec_.format = 1;
+    codec_.rate_control = 16;  // bit dept (??)
+    codec_.bit_rate = sample_rate * channel_count * 16;
+    codec_.profile = 0;
+    codec_.level = 0;
+    codec_.ch_mode = 0;
+
+    codec_.options.generic.reserved[1] = 1;  // 1 == game_mode (bool)
+    codec_.options.generic.reserved[3] = 0;  // signed (??)
+    codec_.options.generic.reserved[7] = 0;  // 0 ==  AUDIO_MAIN_DECODER
+  }
+
+  bool Open() {
+    compr_config config{};
+    // According to the driver team this
+    // `config.fragment_size` & `config.fragments` are ignored.
+    config.codec = &codec_;
+
+    TIZEN_MEDIA_LOG(INFO) << "parameters: ch_in: " << codec_.ch_in
+                          << " ch_out: " << codec_.ch_out
+                          << " sample_rate: " << codec_.sample_rate
+                          << " id: " << codec_.id
+                          << " bit_rate: " << codec_.bit_rate;
+    compress_.reset(
+        compress_open(kDefaultCard, kDefaultDeviceNum, COMPRESS_IN, &config));
+
+    if (!compress_) {
+      TIZEN_MEDIA_LOG(ERROR) << "Failed to open tinycompress!";
+      return false;
+    }
+
+    TIZEN_MEDIA_LOG(INFO) << "Audio stream using tinycompress opened."
+                          << " buffer config:"
+                          << " fragment size: " << config.fragment_size
+                          << " fragments: " << config.fragments;
+
+    return true;
+  }
+
+  snd_codec codec_{};
+  std::unique_ptr<compress, Deleter> compress_;
+};
+#else
+#error "No backend for MCH PCM defined"
+#endif
+
+MchPcmDevice::~MchPcmDevice() {
+  weak_factory_.InvalidateWeakPtrs();
+  CloseAudioDevice();
+  ReleaseResources();
+}
+
+MchPcmDevice::MchPcmDevice(uint32_t channel_count, uint32_t sample_rate)
+    : channel_count_(channel_count), sample_rate_(sample_rate),
+      task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
+  TIZEN_MEDIA_LOG(INFO) << "channel_count: " << channel_count
+                        << " sample_rate: " << sample_rate;
+}
+
+void MchPcmDevice::OnSuspend() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+  CloseAudioDevice();
+  ReleaseResources();
+}
+
+void MchPcmDevice::OnResume() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+  Start();
+}
+
+bool MchPcmDevice::HasResources() const {
+  base::AutoLock guard{lock_};
+  return audio_out_allocated_ && audio_out_resource_ && audio_decoder_resource_;
+}
+
+void MchPcmDevice::AudioResourceLost() {
+  // We've lost one resource. Release all others.
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&MchPcmDevice::OnSuspend, GetWeakPtr()));
+}
+
+bool MchPcmDevice::AllocateResources() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO) << "Allocating resources";
+  base::AutoLock guard{lock_};
+
+  using AudioHardwarePreference = audio_preference::AudioHardwarePreference;
+  auto audio_preference = MchPcmManager::GetInstance().GetAudioPreference();
+  if (audio_preference != AudioHardwarePreference::kHardware) {
+    TIZEN_MEDIA_LOG(INFO) << "App not visible can't allocate resources";
+    return false;
+  }
+
+  TIZEN_MEDIA_LOG(INFO) << "Allocating main audio out.";
+  audio_out_resource_ = base::GetGlobalResourceManager()->AllocateResource(
+      RM_CATEGORY_AUDIO_MAIN_OUT, 0,
+      base::BindRepeating(&MchPcmDevice::ReleaseAudioOutResource,
+                          base::Unretained(this)));
+  if (!audio_out_resource_) {
+    TIZEN_MEDIA_LOG(ERROR) << "Failed to allocate audio out.";
+    return false;
+  }
+
+  TIZEN_MEDIA_LOG(INFO) << "Allocating main audio decoder.";
+  audio_decoder_resource_ = base::GetGlobalResourceManager()->AllocateResource(
+      RM_CATEGORY_AUDIO_DECODER_PCM, 0,
+      base::BindRepeating(&MchPcmDevice::ReleaseAudioDecoderResource,
+                          base::Unretained(this)));
+  if (!audio_decoder_resource_) {
+    TIZEN_MEDIA_LOG(ERROR) << "Failed to allocate audio decoder.";
+    return false;
+  }
+
+  TIZEN_MEDIA_LOG(INFO) << "Allocating audio output using AVOC.";
+  audio_out_allocated_ = AllocateAudioOutput();
+  if (!audio_out_allocated_) {
+    TIZEN_MEDIA_LOG(ERROR) << "Failed to allocate audio output using AVOC.";
+  } else {
+    TIZEN_MEDIA_LOG(INFO) << "Resources allocated.";
+  }
+
+  return audio_out_allocated_;
+}
+
+bool MchPcmDevice::AllocateAudioOutput() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  int handle = base::GetGlobalResourceManager()->handle();
+  avoc_error_e avoc_error =
+      avoc_player_register_m(0, kDefaultSourceType, kMainAudioOut, handle);
+  if (avoc_error < AVOC_EXIT_SUCCESS) {
+    TIZEN_MEDIA_LOG(ERROR) << "avoc_av_player_register_m failed: "
+                           << avoc_error;
+    return false;
+  }
+  avoc_error = avoc_play_request_m(handle, kDefaultSourceType, kMainAudioOut);
+  if (avoc_error < AVOC_EXIT_SUCCESS) {
+    TIZEN_MEDIA_LOG(ERROR) << "avoc_av_play_request_m failed: " << avoc_error;
+    return false;
+  }
+  avoc_error = avoc_audio_mute(handle, AVOC_SETTING_OFF);
+  if (avoc_error < AVOC_EXIT_SUCCESS) {
+    TIZEN_MEDIA_LOG(ERROR) << "avoc_audio_mute failed: " << avoc_error;
+    return false;
+  }
+  avoc_audio_information_s info{.audio_dec = AVOC_AUDIO_DEC_0,
+                                .source = AVOC_AUDIO_SOURCE_MULTIMEDIA_DEC0,
+                                .encode_type = AVOC_AUDIO_ENCODE_TYPE_PCM,
+                                .fs_rate = AVOC_AUDIO_FS_48KHZ,
+                                .zone_id = -1,
+                                .zero_latency = 1,
+                                .channel = static_cast<int>(channel_count_),
+                                .bit_length = 16,
+                                .window_id = -1,
+                                .v_max_res = 4096,
+                                .h_max_res = 2160};
+  int err = avoc_set_audio_information(&info);
+  if (err != 0) {
+    TIZEN_MEDIA_LOG(ERROR) << "avoc_set_audio_information failed: " << err;
+    return false;
+  }
+  TIZEN_MEDIA_LOG(INFO) << "Allocated audio output with AVOC.";
+  return true;
+}
+
+bool MchPcmDevice::OpenAudioDevice() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (device_) {
+    TIZEN_MEDIA_LOG(INFO) << "MCH PCM device already created.";
+    return true;
+  }
+
+  device_ = Impl::Create(channel_count_, sample_rate_);
+  started_ = false;
+  return !!device_;
+}
+
+void MchPcmDevice::ReleaseResources() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::AutoLock guard{lock_};
+
+  ReleaseAudioOutput();
+  audio_out_resource_.reset();
+  audio_decoder_resource_.reset();
+}
+
+void MchPcmDevice::ReleaseAudioOutput() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!audio_out_allocated_)
+    return;
+
+  int handle = base::GetGlobalResourceManager()->handle();
+  avoc_error_e avoc_error = avoc_player_disconnect(handle);
+  if (avoc_error < AVOC_EXIT_SUCCESS) {
+    TIZEN_MEDIA_LOG(WARNING) << "avoc_player_disconnect failed: " << avoc_error;
+  }
+  avoc_error = avoc_player_unregister(handle);
+  if (avoc_error < AVOC_EXIT_SUCCESS) {
+    TIZEN_MEDIA_LOG(WARNING) << "avoc_player_unregister failed: " << avoc_error;
+  }
+
+  audio_out_allocated_ = false;
+  TIZEN_MEDIA_LOG(INFO) << "Audio output released.";
+}
+
+base::TokenCB MchPcmDevice::ReleaseAudioOutResource() {
+  DCHECK(audio_out_resource_);
+  base::AutoLock guard{lock_};
+  TIZEN_MEDIA_LOG(INFO) << " Releasing audio out";
+
+  auto [result, provider] = base::GenerateProviderCallbacks<
+      absl::optional<base::AllocatedResource::Token>>();
+  std::move(provider).Run(std::move(audio_out_resource_->token));
+  audio_out_resource_.reset();
+  AudioResourceLost();
+  return std::move(result);
+}
+
+base::TokenCB MchPcmDevice::ReleaseAudioDecoderResource() {
+  DCHECK(audio_decoder_resource_);
+  base::AutoLock guard{lock_};
+  TIZEN_MEDIA_LOG(INFO) << " Releasing audio decoder";
+
+  auto [result, provider] = base::GenerateProviderCallbacks<
+      absl::optional<base::AllocatedResource::Token>>();
+  std::move(provider).Run(std::move(audio_decoder_resource_->token));
+  audio_decoder_resource_.reset();
+  AudioResourceLost();
+  return std::move(result);
+}
+
+void MchPcmDevice::CloseAudioDevice() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!device_) {
+    TIZEN_MEDIA_LOG(INFO) << "The device has been already closed.";
+    return;
+  }
+
+  device_.reset();
+}
+
+void MchPcmDevice::Start() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  bool has_resources = HasResources();
+  if (!has_resources) {
+    has_resources = AllocateResources();
+  }
+
+  if (!has_resources) {
+    TIZEN_MEDIA_LOG(WARNING) << "Couldn't allocate audio resources.";
+    ReleaseResources();
+    return;
+  }
+
+  if (!device_) {
+    OpenAudioDevice();
+  }
+}
+
+void MchPcmDevice::Stop() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  if (!device_)
+    return;
+
+  started_ = false;
+  bytes_written_ = 0;
+  device_->Stop();
+
+  CloseAudioDevice();
+}
+
+void MchPcmDevice::Flush() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  if (!device_) {
+    return;
+  }
+
+  device_->Flush();
+}
+
+size_t MchPcmDevice::Write(const uint8_t* buffer_data, size_t buffer_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!device_) {
+    // This may happen in Suspend/Resume scenario, so silently drop this buffer.
+    return buffer_size;
+  }
+
+  if (buffer_size <= 0) {
+    TIZEN_MEDIA_LOG(WARNING) << "Got empty buffer, nothing to write.";
+    return 0;
+  }
+
+  auto bytes_written = device_->Write(buffer_data, buffer_size);
+  if (bytes_written < 0) {
+    TIZEN_MEDIA_LOG(ERROR) << "Failed to write data to the device ret: "
+                           << bytes_written;
+    return 0;
+  }
+
+  bytes_written_ += bytes_written;
+  if (!started_) {
+    auto err = device_->Start();
+    if (err < 0) {
+      TIZEN_MEDIA_LOG(WARNING) << "Failed to start device error: " << err << "'"
+                               << device_->GetErrorSting(err) << "'";
+    } else {
+      TIZEN_MEDIA_LOG(INFO) << "The device has been started.";
+      started_ = true;
+      // Reset counters.
+      bytes_written_ = bytes_written;
+      start_time_ = base::TimeTicks::Now();
+    }
+  }
+
+  return static_cast<size_t>(bytes_written);
+}
+
+absl::optional<size_t> MchPcmDevice::FilledBufferSize() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!device_) {
+    // This may happen when app is suspended, but it is still playing audio.
+    return absl::nullopt;
+  }
+
+  return device_->GetRemainBuffer();
+}
+
+size_t MchPcmDevice::BytesPerSecond() const {
+  return sample_rate_ * sizeof(int16_t) * channel_count_;
+}
+
+base::TimeDelta MchPcmDevice::CalculateDelay() const {
+  if (!device_ || !started_)
+    return {};
+
+  return DataSizeToTime(bytes_written_) -
+         (base::TimeTicks::Now() - start_time_);
+}
+
+base::TimeDelta MchPcmDevice::DataSizeToTime(uint64_t size) const {
+  return base::Seconds(size / static_cast<double>(BytesPerSecond()));
+}
+
+}  // namespace media
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.h b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.h
new file mode 100644 (file)
index 0000000..4d30a46
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright 2023 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 MEDIA_AUDIO_TIZEN_MCH_PCM_DEVICE_H_
+#define MEDIA_AUDIO_TIZEN_MCH_PCM_DEVICE_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "absl/types/optional.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "base/tizen/resource_manager.h"
+
+namespace media {
+
+class MchPcmManager;
+
+// Wrapper for TinyCompress library holding required audio resources.
+// All methods must be called on Audio Thread.
+class MchPcmDevice : public base::RefCounted<MchPcmDevice> {
+ public:
+  void Start();
+  void Stop();
+  void Flush();
+
+  // Writes audio buffer to the device, returns number of bytes written.
+  size_t Write(const uint8_t* buffer_data, size_t buffer_size);
+
+  // Query the device of currently used (filled) buffer.
+  absl::optional<size_t> FilledBufferSize() const;
+
+  // Returns duration estimate of the data sent to the device but not played
+  // yet. See `delay` argument to `AudioSourceCallback::OnMoreData` method.
+  base::TimeDelta CalculateDelay() const;
+
+  size_t BytesPerSecond() const;
+  base::TimeDelta DataSizeToTime(uint64_t size) const;
+
+  base::WeakPtr<MchPcmDevice> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  friend class MchPcmManager;
+  friend base::RefCounted<MchPcmDevice>;
+
+  class Impl;
+
+  MchPcmDevice(uint32_t channel_count, uint32_t sample_rate);
+  ~MchPcmDevice();
+
+  void OnSuspend();
+  void OnResume();
+  void AudioResourceLost();
+
+  bool HasResources() const;
+
+  bool AllocateResources();
+  bool AllocateAudioOutput();
+  bool OpenAudioDevice();
+
+  void ReleaseResources();
+  void ReleaseAudioOutput();
+  base::TokenCB ReleaseAudioOutResource();
+  base::TokenCB ReleaseAudioDecoderResource();
+  void CloseAudioDevice();
+
+  uint32_t channel_count_;
+  uint32_t sample_rate_;
+
+  std::unique_ptr<Impl> device_;
+
+  // Flag indicating whether the device playback has started.
+  bool started_{false};
+  // Time when the device playback has started.
+  base::TimeTicks start_time_;
+  // Number of bytes written to the device.
+  uint64_t bytes_written_{};
+
+  // Lock protecting audio resources, which may get resource conflict
+  // notification run on any thread.
+  mutable base::Lock lock_;
+  bool audio_out_allocated_{false};
+  absl::optional<base::AllocatedResource> audio_out_resource_ GUARDED_BY(lock_);
+  absl::optional<base::AllocatedResource> audio_decoder_resource_
+      GUARDED_BY(lock_);
+
+  base::WeakPtrFactory<MchPcmDevice> weak_factory_{this};
+
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_AUDIO_TIZEN_MCH_PCM_DEVICE_H_
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.cc b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.cc
new file mode 100644 (file)
index 0000000..0f1f0b8
--- /dev/null
@@ -0,0 +1,158 @@
+// Copyright 2023 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 "media/audio/tizen/mch_pcm_manager.h"
+
+#include "base/task/single_thread_task_runner.h"
+#include "media/base/tizen/logger/media_logger.h"
+
+#if defined(THREAD_BOOSTER_SERVICE)
+#include "base/threading/platform_thread.h"
+#include "services/thread_booster/public/cpp/thread_booster.h"
+#endif
+
+namespace media {
+
+namespace {
+
+std::ostream& operator<<(std::ostream& os,
+                         audio_preference::AudioHardwarePreference value) {
+  switch (value) {
+    case audio_preference::AudioHardwarePreference::kHardware:
+      os << "Hardware";
+      break;
+    case audio_preference::AudioHardwarePreference::kSoftware:
+      os << "Software";
+      break;
+    case audio_preference::AudioHardwarePreference::kDisabled:
+      os << "Disabled";
+      break;
+  }
+
+  return os;
+}
+
+}  // namespace
+
+// static
+MchPcmManager& MchPcmManager::GetInstance() {
+  static base::NoDestructor<MchPcmManager> instance;
+  return *instance;
+}
+
+// static
+AudioParameters MchPcmManager::GetPreferredOutputStreamParameters(
+    const AudioParameters& input_params) {
+  ChannelLayoutConfig channel_layout_config =
+      ChannelLayoutConfig::FromLayout<kChannelLayout>();
+  int frame_per_buffer = kDefaultFramesPerBuffer;
+  int sample_rate = kDefaultSampleRate;
+
+  if (input_params.IsValid()) {
+    frame_per_buffer = input_params.frames_per_buffer();
+    sample_rate = input_params.sample_rate();
+    channel_layout_config = input_params.channel_layout_config();
+  }
+
+  return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+                         channel_layout_config, sample_rate, frame_per_buffer);
+}
+
+MchPcmManager::MchPcmManager()
+    : device_{nullptr},
+      task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
+  base::AutoLock guard{lock_};
+  last_preference_ = audio_preference::AddObserver(this);
+  TIZEN_MEDIA_LOG(INFO) << "Registered as audio preference observer."
+                        << " Current preference: " << last_preference_;
+}
+
+MchPcmManager::~MchPcmManager() {
+  audio_preference::RemoveObserver(this);
+}
+
+void MchPcmManager::AudioHardwarePreferenceChanged(
+    audio_preference::AudioHardwarePreference preference) {
+  TIZEN_MEDIA_LOG(INFO) << "New preference: " << preference;
+  {
+    base::AutoLock guard{lock_};
+    last_preference_ = preference;
+  }
+
+  switch (preference) {
+    case audio_preference::AudioHardwarePreference::kHardware:
+      task_runner_->PostTask(
+          FROM_HERE, base::BindOnce(&MchPcmDevice::OnResume, device_));
+      break;
+    case audio_preference::AudioHardwarePreference::kSoftware:
+    case audio_preference::AudioHardwarePreference::kDisabled:
+      task_runner_->PostTask(
+          FROM_HERE, base::BindOnce(&MchPcmDevice::OnSuspend, device_));
+      break;
+  }
+}
+
+audio_preference::AudioHardwarePreference MchPcmManager::GetAudioPreference() {
+  base::AutoLock guard{lock_};
+  return last_preference_;
+}
+
+AudioOutputStream* MchPcmManager::CreateOutputStream(
+    const AudioParameters& params,
+    AudioManagerBase* manager) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  TIZEN_MEDIA_LOG(INFO) << "Creating mixable stream for params: "
+                        << params.AsHumanReadableString();
+  auto* stream = MchPcmMixerManager::CreateOutputStream(
+      GetMixerManager(params, manager), params, manager);
+  if (!stream) {
+    TIZEN_MEDIA_LOG(WARNING) << "Failed to create mixable stream for params: "
+                             << params.AsHumanReadableString();
+    return nullptr;
+  }
+
+#if defined(THREAD_BOOSTER_SERVICE)
+  thread_booster::BoostThreadByName(base::PlatformThread::GetName(),
+                                    thread_booster::MediaType::kAudio,
+                                    thread_booster::BoostingLevel::kRealtime);
+  thread_booster::BoostThreadByName("AudioOutputDevice",
+                                    thread_booster::MediaType::kAudio,
+                                    thread_booster::BoostingLevel::kPriority);
+#endif
+
+  return stream;
+}
+
+scoped_refptr<MchPcmDevice> MchPcmManager::GetDevice(
+    uint32_t channel_count,
+    uint32_t sample_rate) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  if (device_) {
+    TIZEN_MEDIA_LOG(INFO) << "Returning existing MCH PCM instance: "
+                          << device_.get();
+    return base::WrapRefCounted(device_.get());
+  }
+
+  auto ret = base::WrapRefCounted(new MchPcmDevice(channel_count, sample_rate));
+  device_ = ret->GetWeakPtr();
+  TIZEN_MEDIA_LOG(INFO) << "Created new MCH PCM instance: "
+                        << device_.get();
+  return ret;
+}
+
+scoped_refptr<MchPcmMixerManager> MchPcmManager::GetMixerManager(
+    const AudioParameters& params,
+    AudioManagerBase* manager) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  if (mixer_) {
+    return base::WrapRefCounted(mixer_.get());
+  }
+
+  auto mixer = base::MakeRefCounted<MchPcmMixerManager>(params, manager);
+  mixer_ = mixer->GetWeakPtr();
+  return mixer;
+}
+
+}  // namespace media
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.h b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.h
new file mode 100644 (file)
index 0000000..ead4d57
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2023 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 MEDIA_AUDIO_TIZEN_MCH_PCM_MANAGER_H_
+#define MEDIA_AUDIO_TIZEN_MCH_PCM_MANAGER_H_
+
+#include <cstdint>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
+#include "services/audio_preference/public/cpp/audio_preference.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/tizen/mch_pcm_device.h"
+#include "media/audio/tizen/mch_pcm_mixer_manager.h"
+
+namespace media {
+
+// Factory for creating/obtaining MultiChannel PCM (MCH PCM) device.
+class MchPcmManager : public audio_preference::AudioHardwarePreferenceObserver {
+ public:
+  static constexpr ChannelLayout kChannelLayout = CHANNEL_LAYOUT_7_1;
+  static constexpr int kDefaultSampleRate = 48000;
+  static constexpr int kDefaultFramesPerBuffer = 480;  // 10 ms of audio data
+
+  ~MchPcmManager();
+
+  // May be called on any thread
+  void AudioHardwarePreferenceChanged(
+      audio_preference::AudioHardwarePreference preference) override;
+
+  static MchPcmManager& GetInstance();
+  static AudioParameters GetPreferredOutputStreamParameters(
+      const AudioParameters& input_params);
+
+  // Will be called on Audio thread.
+  AudioOutputStream* CreateOutputStream(const AudioParameters& params,
+                                        AudioManagerBase* manager);
+  scoped_refptr<MchPcmDevice> GetDevice(uint32_t channel_count,
+                                        uint32_t sample_rate);
+
+  audio_preference::AudioHardwarePreference GetAudioPreference();
+
+ private:
+  friend class base::NoDestructor<MchPcmManager>;
+  friend class MchPcmDevice;
+
+  MchPcmManager();
+
+  scoped_refptr<MchPcmMixerManager> GetMixerManager(
+      const AudioParameters& params,
+      AudioManagerBase* manager);
+
+  base::Lock lock_;
+  audio_preference::AudioHardwarePreference last_preference_ GUARDED_BY(lock_);
+
+  // Accessed on audio thread.
+  base::WeakPtr<MchPcmDevice> device_;
+
+  // Accessed on audio thread.
+  base::WeakPtr<MchPcmMixerManager> mixer_;
+
+  // Audio thread
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_AUDIO_TIZEN_MCH_PCM_MANAGER_H_
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.cc b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.cc
new file mode 100644 (file)
index 0000000..02deea5
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright 2023 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 "media/audio/tizen/mch_pcm_mixable_stream.h"
+
+#include "media/audio/tizen/mch_pcm_mixer_manager.h"
+#include "media/base/tizen/logger/media_logger.h"
+
+namespace media {
+
+MchPcmMixableStream::MchPcmMixableStream(
+    AudioOutputStream* mixable_stream,
+    scoped_refptr<MchPcmMixerManager> mixer_manager)
+    : mixable_stream_(mixable_stream),
+      mixer_manager_(std::move(mixer_manager)) {
+  TIZEN_MEDIA_LOG(INFO) << "mixable_stream: " << mixable_stream_.get()
+                        << " mixer_manager: " << mixer_manager_.get();
+  TIZEN_MEDIA_LOG_ASSERT(mixable_stream_);
+  TIZEN_MEDIA_LOG_ASSERT(mixer_manager_);
+}
+
+MchPcmMixableStream::~MchPcmMixableStream() {
+  TIZEN_MEDIA_LOG(INFO);
+  Stop();
+}
+
+bool MchPcmMixableStream::Open() {
+  TIZEN_MEDIA_LOG(INFO);
+  return mixable_stream_->Open();
+}
+
+void MchPcmMixableStream::Start(AudioSourceCallback* callback) {
+  TIZEN_MEDIA_LOG(INFO);
+  mixer_manager_->StreamStarted(this);
+  mixable_stream_->Start(callback);
+}
+
+void MchPcmMixableStream::Stop() {
+  TIZEN_MEDIA_LOG(INFO);
+  mixer_manager_->StreamStopped(this);
+  mixable_stream_->Stop();
+}
+
+void MchPcmMixableStream::SetVolume(double volume) {
+  TIZEN_MEDIA_LOG(INFO) << "volume: " << volume;
+  mixable_stream_->SetVolume(volume);
+}
+
+void MchPcmMixableStream::GetVolume(double* volume) {
+  TIZEN_MEDIA_LOG(INFO);
+  mixable_stream_->GetVolume(volume);
+}
+
+void MchPcmMixableStream::Close() {
+  TIZEN_MEDIA_LOG(INFO);
+  mixer_manager_->StreamStopped(this);
+  mixable_stream_->Close();
+}
+
+void MchPcmMixableStream::Flush() {
+  TIZEN_MEDIA_LOG(INFO);
+  mixable_stream_->Flush();
+}
+
+}  // namespace media
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.h b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.h
new file mode 100644 (file)
index 0000000..f03ca24
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright 2023 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 MEDIA_AUDIO_TIZEN_MCH_PCM_MIXABLE_STREAM_H_
+#define MEDIA_AUDIO_TIZEN_MCH_PCM_MIXABLE_STREAM_H_
+
+#include "media/audio/audio_io.h"
+
+namespace media {
+
+class MchPcmMixerManager;
+
+// Wrapper for streams returned by `OutputDeviceMixer` instance.
+// Needed to share one common multichannel PCM (MCH PCM) device by multiple
+// `AudioOutputStream`. When there is only one **active** instance of this
+// class then audio date are rendered directly to multichannel PCM (MCH PCM)
+// device.
+class MchPcmMixableStream : public AudioOutputStream {
+ public:
+  MchPcmMixableStream(
+      AudioOutputStream* mixable_stream,
+      scoped_refptr<MchPcmMixerManager> mixer_manager);
+  ~MchPcmMixableStream();
+
+  bool Open() override;
+  void Start(AudioSourceCallback* callback) override;
+  void Stop() override;
+
+  void SetVolume(double volume) override;
+  void GetVolume(double* volume) override;
+
+  void Close() override;
+  void Flush() override;
+
+ private:
+  std::unique_ptr<AudioOutputStream> mixable_stream_;
+  scoped_refptr<MchPcmMixerManager> mixer_manager_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_AUDIO_TIZEN_MCH_PCM_MIXABLE_STREAM_H_
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.cc b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.cc
new file mode 100644 (file)
index 0000000..395e81d
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright 2023 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 "media/audio/tizen/mch_pcm_mixer_manager.h"
+
+#include "base/functional/callback_helpers.h"
+#include "base/task/single_thread_task_runner.h"
+#include "media/audio/tizen/mch_pcm_mixable_stream.h"
+#include "media/audio/tizen/mch_pcm_output.h"
+#include "media/base/tizen/logger/media_logger.h"
+#include "services/audio/reference_output.h"
+
+
+namespace media {
+
+MchPcmMixerManager::MchPcmMixerManager(
+    const AudioParameters& params,
+    AudioManagerBase* manager) : manager_{manager} {
+  TIZEN_MEDIA_LOG(INFO) << "audio params: " << params.AsHumanReadableString();
+  mixer_ = audio::OutputDeviceMixer::Create(
+      "", params,
+      base::BindRepeating(&MchPcmMixerManager::CreateMchPcmStream,
+                          base::Unretained(this)),
+      base::SingleThreadTaskRunner::GetCurrentDefault());
+  TIZEN_MEDIA_LOG_ASSERT(mixer_) << "Failed to create mixer for parameters: "
+                                 << params.AsHumanReadableString();
+}
+
+MchPcmMixerManager::~MchPcmMixerManager() {
+  weak_factory_.InvalidateWeakPtrs();
+}
+
+// static
+AudioOutputStream* MchPcmMixerManager::CreateOutputStream(
+    scoped_refptr<MchPcmMixerManager> mixer_manager,
+    const AudioParameters& params,
+    AudioManagerBase* manager) {
+  TIZEN_MEDIA_LOG_PTR(INFO, mixer_manager.get())
+      << "audio params: " << params.AsHumanReadableString();
+  // TODO(vd.wasm): Implement resampling in case |params| will be different than
+  // one passed to the ctor.
+  auto* mixer = mixer_manager->mixer_.get();
+  if (!mixer || !mixer_manager) {
+    return nullptr;
+  }
+
+  return new MchPcmMixableStream(
+      mixer->MakeMixableStream(params, base::DoNothing()),
+      std::move(mixer_manager));
+}
+
+void MchPcmMixerManager::StreamStarted(AudioOutputStream* stream) {
+  if (active_streams_.count(stream)) {
+    TIZEN_MEDIA_LOG(INFO) << "stream " << stream << " already active";
+    return;
+  }
+
+  active_streams_.insert(stream);
+  TIZEN_MEDIA_LOG(INFO) << "active streams: " << active_streams_.size();
+  if (active_streams_.size() == 2) {
+    TIZEN_MEDIA_LOG(INFO) << "Starting listening to mixed audio.";
+    mixer_->StartListening(this);
+  }
+}
+
+void MchPcmMixerManager::StreamStopped(AudioOutputStream* stream) {
+  if (!active_streams_.count(stream)) {
+    TIZEN_MEDIA_LOG(INFO) << "stream " << stream << " not active";
+    return;
+  }
+
+  active_streams_.erase(stream);
+  TIZEN_MEDIA_LOG(INFO) << "active streams: " << active_streams_.size();
+  if (active_streams_.size() == 1) {
+    TIZEN_MEDIA_LOG(INFO) << "Stopping listening to mixed audio.";
+    mixer_->StopListening(this);
+  }
+}
+
+base::WeakPtr<MchPcmMixerManager> MchPcmMixerManager::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+void MchPcmMixerManager::OnPlayoutData(const media::AudioBus& audio_bus,
+                                       int sample_rate,
+                                       base::TimeDelta audio_delay) {}
+
+AudioOutputStream* MchPcmMixerManager::CreateMchPcmStream(
+    const std::string& device_id,
+    const AudioParameters& params) {
+  TIZEN_MEDIA_LOG(INFO) << "audio params: " << params.AsHumanReadableString();
+  return new MchPcmOutputStream(params, manager_);
+}
+
+}  // namespace media
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.h b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.h
new file mode 100644 (file)
index 0000000..5c75b7c
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright 2023 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 MEDIA_AUDIO_TIZEN_MCH_PCM_MIXER_MANAGER_H_
+#define MEDIA_AUDIO_TIZEN_MCH_PCM_MIXER_MANAGER_H_
+
+#include "base/containers/flat_set.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager_base.h"
+#include "services/audio/output_device_mixer.h"
+
+namespace media {
+
+// Class that changes way of rendering audio by owned mixer.
+// When only one stream is active |StreamStarted()| called for one stream,
+// then audio is rendered directly to multichannel PCM (MCH PCM) device.
+// For two and more active streams it is mixed by |OutputDeviceMixer| and then
+// is sent to multichannel PCM (MCH PCM) device.
+class MchPcmMixerManager
+    : public base::RefCountedThreadSafe<MchPcmMixerManager>,
+      public audio::ReferenceOutput::Listener {
+ public:
+  MchPcmMixerManager(const AudioParameters& params,
+                           AudioManagerBase* manager);
+  ~MchPcmMixerManager();
+
+  static AudioOutputStream* CreateOutputStream(
+      scoped_refptr<MchPcmMixerManager> mixer_manager,
+      const AudioParameters& params,
+      AudioManagerBase* manager);
+
+  void StreamStarted(AudioOutputStream* stream);
+  void StreamStopped(AudioOutputStream* stream);
+
+  base::WeakPtr<MchPcmMixerManager> GetWeakPtr();
+
+  // audio::ReferenceOutput::Listener
+  void OnPlayoutData(const media::AudioBus& audio_bus,
+                     int sample_rate,
+                     base::TimeDelta audio_delay) override;
+
+ private:
+  AudioOutputStream* CreateMchPcmStream(const std::string& device_id,
+                                        const AudioParameters& params);
+
+  base::flat_set<void*> active_streams_;
+  std::unique_ptr<audio::OutputDeviceMixer> mixer_;
+
+  AudioManagerBase* manager_;
+
+  base::WeakPtrFactory<MchPcmMixerManager> weak_factory_{this};
+};
+
+}  // namespace media
+
+#endif  // MEDIA_AUDIO_TIZEN_MCH_PCM_MIXER_MANAGER_H_
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.cc b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.cc
new file mode 100644 (file)
index 0000000..a7d796c
--- /dev/null
@@ -0,0 +1,299 @@
+// Copyright 2023 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 "media/audio/tizen/mch_pcm_output.h"
+
+#include <avoc/avoc.h>
+#include <avoc/avoc_avsink.h>
+#include <avoc/avoc_mls.h>
+#include <sound/compress_params.h>
+#include <rm_type.h>
+
+#include <cstddef>
+#include <memory>
+
+#include "base/task/single_thread_task_runner.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/tizen/mch_pcm_manager.h"
+#include "media/base/channel_mixer.h"
+#include "media/base/data_buffer.h"
+#include "media/base/seekable_buffer.h"
+#include "media/base/tizen/logger/media_logger.h"
+
+namespace media {
+
+namespace {
+static const SampleFormat kSampleFormat = kSampleFormatS16;
+
+// Buffering logic control
+
+// Default duration of pull threshold and also pushed data chunk.
+// Matches default audio chunk duration in `webrtc::NetEq::GetAudio()`.
+static constexpr base::TimeDelta kDefaultChunkDuration = base::Milliseconds(10);
+
+// Buffer low watermark, below which we need to submit packet with no delay.
+// On Samsung TVs audio DSP read multichannel PCM data in chunks of 256
+// frames, which at 48000 kHz sampling (only valid one for this DSP) gives
+// ~ 5.33 ms. Round this up to full millisecond.
+static constexpr base::TimeDelta kBufferLowDuration = base::Milliseconds(6);
+
+// Buffer hight watermark, below which we need to submit packet soon,
+// to avoid risk of buffer underflow.
+static constexpr base::TimeDelta kBufferHighDuration = 2 * kBufferLowDuration;
+
+// Multichannel PCM (MCH PCM) & ALSA on TV platform has set huge buffers which
+// causes that buffer status reported by multichannel PCM (MCH PCM) will be low,
+// but audio data fed to the player is way ahead its playback time (delay).
+// To keep delay low avoid pushing data too frequently, try to estimate
+// delay and don't push data too frequently.
+// This value has been set empirically to lowest possible avoiding ALSA
+// underflow and sound glitches.
+static constexpr base::TimeDelta kMaxBufferDelay = 4 * kDefaultChunkDuration;
+
+}  // namespace
+
+MchPcmOutputStream::MchPcmOutputStream(const AudioParameters& params,
+                                       AudioManagerBase* manager)
+    : channels_(params.channels()),
+      channel_layout_(params.channel_layout()),
+      sample_rate_(params.sample_rate()),
+      bytes_per_frame_(params.GetBytesPerFrame(kSampleFormat)),
+      packet_size_(params.GetBytesPerBuffer(kSampleFormat)),
+      manager_(manager),
+      audio_bus_(AudioBus::Create(params)),
+      task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
+  device_ = MchPcmManager::GetInstance().GetDevice(channels_, sample_rate_);
+  TIZEN_MEDIA_LOG(INFO) << "channel count: " << channels_
+                        << " layout: " << channel_layout_
+                        << " sample_rate: " << sample_rate_
+                        << " frames per buffer: " << params.frames_per_buffer();
+  // Sanity check input values.
+  if (!params.IsValid()) {
+    TIZEN_MEDIA_LOG(WARNING) << "Unsupported audio parameters.";
+  }
+}
+
+MchPcmOutputStream::~MchPcmOutputStream() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!device_);
+}
+
+bool MchPcmOutputStream::Open() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  buffer_ = std::make_unique<SeekableBuffer>(0, packet_size_);
+  stop_stream_ = false;
+
+  return true;
+}
+
+void MchPcmOutputStream::Close() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  stop_stream_ = true;
+  buffer_.reset();
+  weak_factory_.InvalidateWeakPtrs();
+  manager_->ReleaseOutputStream(this);
+}
+
+void MchPcmOutputStream::Start(AudioSourceCallback* callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  stop_stream_ = false;
+  CHECK(callback);
+  device_->Start();
+
+  // Before starting, the buffer might have audio from previous user of this
+  // device.
+  buffer_->Clear();
+
+  source_callback_ = callback;
+  WriteTask();
+}
+
+void MchPcmOutputStream::Stop() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  weak_factory_.InvalidateWeakPtrs();
+
+  stop_stream_ = true;
+  device_->Stop();
+}
+
+void MchPcmOutputStream::Flush() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  TIZEN_MEDIA_LOG(INFO);
+
+  while (buffer_->forward_bytes()) {
+    auto written = WritePacket();
+    if (!written.is_positive()) break;
+  }
+  device_->Flush();
+  buffer_->Clear();
+}
+
+void MchPcmOutputStream::SetVolume(double volume) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  volume_ = static_cast<float>(volume);
+}
+
+void MchPcmOutputStream::GetVolume(double* volume) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  *volume = volume_;
+}
+
+void MchPcmOutputStream::BufferPacket(bool* source_exhausted) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // If stopped, simulate a 0-length packet.
+  if (stop_stream_) {
+    buffer_->Clear();
+    *source_exhausted = true;
+    return;
+  }
+
+  *source_exhausted = false;
+
+  // Request more data only when we run out of data in the buffer, because
+  // WritePacket() consumes only the current chunk of data.
+  if (!buffer_->forward_bytes()) {
+    scoped_refptr<DataBuffer> packet = new DataBuffer(packet_size_);
+    int frames_filled = RunDataCallback(
+        device_->CalculateDelay(), base::TimeTicks::Now(), audio_bus_.get());
+
+    size_t packet_size = frames_filled * bytes_per_frame_;
+    DCHECK_LE(packet_size, packet_size_);
+    AudioBus* output_bus = audio_bus_.get();
+
+    // Note: If this ever changes to output raw float the data must be clipped
+    // and sanitized since it may come from an untrusted source such as NaCl.
+    output_bus->Scale(volume_);
+    output_bus->ToInterleaved<SignedInt16SampleTypeTraits>(
+        frames_filled, reinterpret_cast<int16_t*>(packet->writable_data()));
+
+    if (packet_size > 0) {
+      packet->set_data_size(packet_size);
+      buffer_->Append(packet);
+    } else {
+      *source_exhausted = true;
+    }
+  }
+}
+
+base::TimeDelta MchPcmOutputStream::WritePacket() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::TimeDelta written;
+
+  // If the device is in error, just eat the bytes.
+  if (stop_stream_) {
+    buffer_->Clear();
+    return written;
+  }
+
+  CHECK_EQ(buffer_->forward_bytes() % bytes_per_frame_, 0u);
+
+  const uint8_t* buffer_data;
+  int buffer_size = 0;
+  if (!buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) {
+    TIZEN_MEDIA_LOG(WARNING) << "Failed to get data from buffer.";
+    return written;
+  }
+
+  // Write at most kDefaultChunkDuration of audio data to ensure
+  // that device will be filled constantly.
+  const int max_write_size = (bytes_per_frame_ * sample_rate_ *
+                              kDefaultChunkDuration.InMilliseconds()) /
+                             base::Time::kMillisecondsPerSecond;
+  const int write_size = std::min(buffer_size, max_write_size);
+  size_t bytes_written = device_->Write(buffer_data, write_size);
+  if (bytes_written > 0) {
+    written = device_->DataSizeToTime(bytes_written);
+    // Seek forward in the buffer after we've written some data.
+    buffer_->Seek(bytes_written);
+  }
+  return written;
+}
+
+void MchPcmOutputStream::WriteTask() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (stop_stream_)
+    return;
+
+  bool source_exhausted = false;
+  BufferPacket(&source_exhausted);
+  base::TimeDelta written = WritePacket();
+  ScheduleNextWrite(source_exhausted, written);
+}
+
+base::TimeDelta MchPcmOutputStream::GetNextWriteDelay(
+    bool source_exhausted,
+    base::TimeDelta last_written) {
+  const bool can_pull_data = buffer_->forward_bytes() || !source_exhausted;
+  const auto filled_buffer = device_->FilledBufferSize();
+  const auto delay = device_->CalculateDelay();
+
+  // We can't write more data now.
+  if (!last_written.is_positive())
+    return kDefaultChunkDuration;
+
+  // We can't pull more data now or we can't estimate used buffer.
+  if (!can_pull_data || !filled_buffer)
+    return std::min(last_written, kDefaultChunkDuration);
+
+  base::TimeDelta filled_time = device_->DataSizeToTime(*filled_buffer);
+  // We are below low water mark.
+  if (filled_time < kBufferLowDuration)
+    return {};
+
+  // Don't buffer too much to avoid large delays.
+  if (delay > kMaxBufferDelay)
+    return kDefaultChunkDuration;
+
+  // We are below high water mark.
+  if (filled_time < kBufferHighDuration)
+    return std::min(last_written, kDefaultChunkDuration / 2);
+
+  // We are above high watermark.
+  return kDefaultChunkDuration;
+}
+
+void MchPcmOutputStream::ScheduleNextWrite(bool source_exhausted,
+                                           base::TimeDelta last_written) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (stop_stream_)
+    return;
+
+  const base::TimeDelta next_fill_time =
+      GetNextWriteDelay(source_exhausted, last_written);
+  task_runner_->PostDelayedTaskAt(
+      base::subtle::PostDelayedTaskPassKey(), FROM_HERE,
+      base::BindOnce(&MchPcmOutputStream::WriteTask,
+                     weak_factory_.GetWeakPtr()),
+      !next_fill_time.is_positive() ? base::TimeTicks()
+                                    : base::TimeTicks::Now() + next_fill_time,
+      base::subtle::DelayPolicy::kPrecise);
+}
+
+int MchPcmOutputStream::RunDataCallback(base::TimeDelta delay,
+                                        base::TimeTicks delay_timestamp,
+                                        AudioBus* audio_bus) {
+  TRACE_EVENT0("audio", "MchPcmOutputStream::RunDataCallback");
+
+  if (source_callback_)
+    return source_callback_->OnMoreData(delay, delay_timestamp,
+                                        AudioGlitchInfo{}, audio_bus);
+
+  return 0;
+}
+
+}  // namespace media
diff --git a/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.h b/tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.h
new file mode 100644 (file)
index 0000000..e16eb6c
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright 2023 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.
+//
+// Based on AlsaPcmOutputStream see comments there regarding threading
+// assumptions.
+
+#ifndef MEDIA_AUDIO_TIZEN_MCH_PCM_OUTPUT_H_
+#define MEDIA_AUDIO_TIZEN_MCH_PCM_OUTPUT_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/tizen/mch_pcm_device.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManagerBase;
+class SeekableBuffer;
+
+// MultiChannel PCM (MCH PCM) output device.
+class MEDIA_EXPORT MchPcmOutputStream : public AudioOutputStream {
+ public:
+  MchPcmOutputStream(const AudioParameters& params,
+                           AudioManagerBase* manager);
+
+  MchPcmOutputStream(const MchPcmOutputStream&) = delete;
+  MchPcmOutputStream& operator=(const MchPcmOutputStream&) = delete;
+
+  ~MchPcmOutputStream() override;
+
+  // Implementation of AudioOutputStream.
+  bool Open() override;
+  void Close() override;
+  void Start(AudioSourceCallback* callback) override;
+  void Stop() override;
+  void Flush() override;
+  void SetVolume(double volume) override;
+  void GetVolume(double* volume) override;
+
+ private:
+  // Functions to get another packet from the data source and write it into the
+  // multichannel PCM (MCH PCM) device.
+  void BufferPacket(bool* source_exhausted);
+  base::TimeDelta WritePacket();
+  void WriteTask();
+  base::TimeDelta GetNextWriteDelay(bool source_exhausted,
+                                    base::TimeDelta last_written);
+  void ScheduleNextWrite(bool source_exhausted, base::TimeDelta last_written);
+
+  // See AlsaPcmOutputStream
+  int RunDataCallback(base::TimeDelta delay,
+                      base::TimeTicks delay_timestamp,
+                      AudioBus* audio_bus);
+
+  scoped_refptr<MchPcmDevice> device_;
+  const uint32_t channels_;
+  const ChannelLayout channel_layout_;
+  const uint32_t sample_rate_;
+  const uint32_t bytes_per_frame_;
+
+  bool stop_stream_ = false;
+
+  // Device configuration data. Populated after OpenTask() completes.
+  std::string device_name_;
+  uint32_t packet_size_;
+
+  float volume_ = 1.0f;  // Volume level from 0.0 to 1.0.
+
+  // Audio manager that created us.  Used to report that we've been closed.
+  raw_ptr<AudioManagerBase> manager_;
+  raw_ptr<AudioSourceCallback> source_callback_ = nullptr;
+
+  std::unique_ptr<SeekableBuffer> buffer_;
+  std::unique_ptr<AudioBus> audio_bus_;
+
+  base::WeakPtrFactory<MchPcmOutputStream> weak_factory_{this};
+
+  // Task runner to use for polling.
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_AUDIO_TIZEN_MCH_PCM_OUTPUT_H_
index 2b575d116501679d2309a7c24a93acef7877d990..1948276365a61254796e74961e11faa6a8274134 100644 (file)
@@ -6,6 +6,7 @@ import("//tizen_src/build/config/tizen_features.gni")
 
 external_media_efl_audio_io_config = []
 external_media_efl_audio_io_sources = []
+external_media_efl_audio_io_deps = []
 external_media_efl_deps = []
 external_media_efl_sources = []
 external_exclude_media_efl_sources = []
@@ -195,4 +196,42 @@ if (tizen_audio_io) {
       "//tizen_src/build:libcapi-network-bluetooth-tv",
     ]
   }
+
+  if (tizen_tv_use_mch_pcm) {
+    external_media_efl_audio_io_config += [
+      "//tizen_src/build:tizen-tv-resource-manager-config",
+      "//tizen_src/chromium_impl/media:mch_pcm_logger_config",
+    ]
+
+    if (tizen_tv_mch_pcm_use_ppi) {
+      external_media_efl_audio_io_config += [
+        "//tizen_src/build:ppi-mm-audioout",
+      ]
+    }
+
+    if (tizen_tv_mch_pcm_use_tinycompress) {
+      external_media_efl_audio_io_config += [
+        "//tizen_src/build:tinycompress",
+      ]
+    }
+
+    external_media_efl_audio_io_sources += [
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.cc",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_device.h",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.cc",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_manager.h",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.cc",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixable_stream.h",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.cc",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_mixer_manager.h",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.cc",
+      "//tizen_src/chromium_impl/media/audio/tizen/mch_pcm_output.h",
+    ]
+
+    if (tizen_thread_booster_service) {
+      external_media_efl_audio_io_deps += [
+        "//tizen_src/chromium_impl/services/thread_booster/public/cpp",
+      ]
+    }
+  }
 }
index a22b970c4627f750233ca4a84a46b241dfae942b..8cfa6110c42960aa66c95b3ee49e7f47662b5a38 100644 (file)
@@ -147,6 +147,9 @@ webrtc_tizen_peerconnection_renderer_path = rebase_path(
 webrtc_tizen_browser_player_path =
     rebase_path("//tizen_src/chromium_impl/media/filters/tizen")
 
+webrtc_tizen_audio_path =
+    rebase_path("//tizen_src/chromium_impl/media/audio/tizen")
+
 calculate_hash_from_dirs("calculate_webrtc_version") {
   define_name = "WEBRTC_VERSION"
   source_dirs = [
@@ -162,6 +165,7 @@ calculate_hash_from_dirs("calculate_webrtc_version") {
     ttvd_layers_path,
     ttvd_ozone_path,
     ttvd_viz_path,
+    webrtc_tizen_audio_path,
   ]
 }
 
index bf52c0f202a5c10239139945ed3b14685c5b8678..ba96c221d4e36a5bbde7d3e3e4a56d42596ed49c 100644 (file)
@@ -137,6 +137,18 @@ inline std::string GetTVChipset() {
   return chipset;
 }
 
+inline int GetTVProductYear() {
+  constexpr static const char* kKeyProductYear =
+      "com.samsung/featureconf/product.tv_year";
+  int result = 0;
+  int ret = system_info_get_custom_int(kKeyProductYear, &result);
+  if (ret != SYSTEM_INFO_ERROR_NONE) {
+    LOG(WARNING) << "Failed to get system info : " << kKeyProductYear;
+    return 0;
+  }
+  return result;
+}
+
 inline bool IsSmartMonitorType() {
   bool bSupport = false;
   if (system_info_get_custom_bool(FEATURE_KEY_SMART_MONITOR, &bSupport) !=
index 75af01a9da052357954f3bc78cf501105dafd031..d4a7226260fb6c463bbfb507d5095cf120eea8e9 100644 (file)
@@ -110,6 +110,8 @@ const char* kUpstreamArchitecture =
     "http://samsung.com/tv/metadata/use.upstream.architecture";
 const char* kNDecoding =
     "http://samsung.com/tv/metadata/use.n.decoding";
+const char* kMultichannelPcmOutput =
+    "http://samsung.com/tv/metadata/use.multichannel.pcm.output";
 const char* kGpuRasterization =
     "http://samsung.com/tv/metadata/gpu.rasterization.support";
 const char* kGraphicPlaneSupport =
@@ -846,6 +848,8 @@ void WRTNativeWindowTV::SetAppMetaDataInfo() {
   const bool enable_game_mode = (meta_data_info.GetValue(kGameMode) == "true");
   const bool enable_upstream_architecture =
       (meta_data_info.GetValue(kUpstreamArchitecture) == "true");
+  const bool use_multi_channel_pcm_output =
+      (meta_data_info.GetValue(kMultichannelPcmOutput) == "true");
 
   if (enable_game_mode) {
     auto current_command_line = base::CommandLine::ForCurrentProcess();
@@ -867,6 +871,11 @@ void WRTNativeWindowTV::SetAppMetaDataInfo() {
     auto current_command_line = base::CommandLine::ForCurrentProcess();
     LOG(INFO) << "Use upstream architecture";
     current_command_line->AppendSwitch(switches::kEnableUpstreamArchitecture);
+
+    if (use_multi_channel_pcm_output) {
+      LOG(INFO) << "Using Multichannel PCM output (MCH PCM)";
+      current_command_line->AppendSwitch(switches::kUseMultichannelPcmOutput);
+    }
   }
 
   if (meta_data_info.GetValue(kScreenOrientationMultiView) == "portrait") {