#if defined(TIZEN_MULTIMEDIA)
class PipelineImpl;
#endif
+#if defined(TIZEN_TV_USE_MCH_PCM)
+class MchPcmOutputStream;
+#endif
} // namespace media
namespace base {
#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 {};
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);
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);
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) {
#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.
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)
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
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)
MEDIA_EXPORT bool IsUpstreamArchitectureEnabled();
MEDIA_EXPORT bool IsGameModeEnabled();
+
+MEDIA_EXPORT bool UseMchPcmOutput();
#endif
} // namespace media
%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
%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
%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}" \
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 = [
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",
#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"
#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
format.clockrate_hz, format.num_channels, volume);
#else
return nullptr;
-#endif // OS_TIZEN_TV_PRODUCT
+#endif // BUILDFLAG(IS_TIZEN_TV)
}
private:
}
#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>,
}
}
+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) {
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" ]
}
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
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) {}
# 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 = [
"//v8/include",
]
}
+
+if (tizen_tv_use_mch_pcm) {
+ tizen_media_logger_config("mch_pcm_logger_config") {
+ logger_tag = "AUDIO_MCH_PCM"
+ }
+}
#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(
if (device_names->empty()) {
device_names->push_front(AudioDeviceName::CreateDefault());
}
-
- return;
-#endif
-
+#else
device_names->push_front(AudioDeviceName::CreateDefault());
+#endif
}
void AudioManagerCapi::GetAudioInputDeviceNames(
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();
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);
}
--- /dev/null
+// 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
--- /dev/null
+// 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_
--- /dev/null
+// 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
--- /dev/null
+// 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_
--- /dev/null
+// 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
--- /dev/null
+// 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_
--- /dev/null
+// 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
--- /dev/null
+// 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_
--- /dev/null
+// 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
--- /dev/null
+// 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_
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 = []
"//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",
+ ]
+ }
+ }
}
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 = [
ttvd_layers_path,
ttvd_ozone_path,
ttvd_viz_path,
+ webrtc_tizen_audio_path,
]
}
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) !=
"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 =
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();
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") {