| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <alsa/asoundlib.h> |
| |
| #include "base/at_exit.h" |
| #include "base/command_line.h" |
| #include "base/files/scoped_file.h" |
| #include "base/logging.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chromecast/base/init_command_line_shlib.h" |
| #include "chromecast/base/task_runner_impl.h" |
| #include "chromecast/media/cma/backend/alsa/media_pipeline_backend_alsa.h" |
| #include "chromecast/media/cma/backend/alsa/stream_mixer_alsa.h" |
| #include "chromecast/public/cast_media_shlib.h" |
| #include "chromecast/public/graphics_types.h" |
| #include "chromecast/public/media_codec_support_shlib.h" |
| #include "chromecast/public/video_plane.h" |
| #include "media/base/media.h" |
| #include "media/base/media_switches.h" |
| |
| #define RETURN_ON_ALSA_ERROR(snd_func, ...) \ |
| do { \ |
| int err = snd_func(__VA_ARGS__); \ |
| if (err < 0) { \ |
| LOG(ERROR) << #snd_func " error: " << snd_strerror(err); \ |
| return; \ |
| } \ |
| } while (0) |
| |
| namespace chromecast { |
| namespace media { |
| namespace { |
| |
| const char kDefaultPcmDevice[] = "hw:0"; |
| const int kSoundControlBlockingMode = 0; |
| const char kRateOffsetInterfaceName[] = "PCM Playback Rate Offset"; |
| |
| // 1 MHz reference allows easy translation between frequency and PPM. |
| const double kOneMhzReference = 1e6; |
| const double kMaxAdjustmentHz = 500; |
| const double kGranularityHz = 1.0; |
| |
| class DefaultVideoPlane : public VideoPlane { |
| public: |
| ~DefaultVideoPlane() override {} |
| |
| void SetGeometry(const RectF& display_rect, Transform transform) override {} |
| }; |
| |
| snd_hctl_t* g_hardware_controls = nullptr; |
| snd_ctl_elem_id_t* g_rate_offset_id = nullptr; |
| snd_ctl_elem_value_t* g_rate_offset_ppm = nullptr; |
| snd_hctl_elem_t* g_rate_offset_element = nullptr; |
| |
| void InitializeAlsaControls() { |
| RETURN_ON_ALSA_ERROR(snd_ctl_elem_id_malloc, &g_rate_offset_id); |
| RETURN_ON_ALSA_ERROR(snd_ctl_elem_value_malloc, &g_rate_offset_ppm); |
| |
| std::string alsa_device_name = kDefaultPcmDevice; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAlsaOutputDevice)) { |
| alsa_device_name = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| switches::kAlsaOutputDevice); |
| } |
| RETURN_ON_ALSA_ERROR(snd_hctl_open, &g_hardware_controls, |
| alsa_device_name.c_str(), kSoundControlBlockingMode); |
| RETURN_ON_ALSA_ERROR(snd_hctl_load, g_hardware_controls); |
| snd_ctl_elem_id_set_interface(g_rate_offset_id, SND_CTL_ELEM_IFACE_PCM); |
| snd_ctl_elem_id_set_name(g_rate_offset_id, kRateOffsetInterfaceName); |
| g_rate_offset_element = |
| snd_hctl_find_elem(g_hardware_controls, g_rate_offset_id); |
| if (g_rate_offset_element) { |
| snd_ctl_elem_value_set_id(g_rate_offset_ppm, g_rate_offset_id); |
| } else { |
| LOG(ERROR) << "snd_hctl_find_elem failed to find the rate offset element."; |
| } |
| } |
| |
| DefaultVideoPlane* g_video_plane = nullptr; |
| |
| base::AtExitManager g_at_exit_manager; |
| |
| std::unique_ptr<base::ThreadTaskRunnerHandle> g_thread_task_runner_handle; |
| |
| } // namespace |
| |
| void CastMediaShlib::Initialize(const std::vector<std::string>& argv) { |
| chromecast::InitCommandLineShlib(argv); |
| |
| g_video_plane = new DefaultVideoPlane(); |
| |
| InitializeAlsaControls(); |
| ::media::InitializeMediaLibrary(); |
| } |
| |
| void CastMediaShlib::Finalize() { |
| if (g_hardware_controls) |
| snd_hctl_close(g_hardware_controls); |
| snd_ctl_elem_value_free(g_rate_offset_ppm); |
| snd_ctl_elem_id_free(g_rate_offset_id); |
| |
| g_hardware_controls = nullptr; |
| g_rate_offset_id = nullptr; |
| g_rate_offset_ppm = nullptr; |
| g_rate_offset_element = nullptr; |
| |
| delete g_video_plane; |
| g_video_plane = nullptr; |
| |
| g_thread_task_runner_handle.reset(); |
| } |
| |
| VideoPlane* CastMediaShlib::GetVideoPlane() { |
| return g_video_plane; |
| } |
| |
| MediaPipelineBackend* CastMediaShlib::CreateMediaPipelineBackend( |
| const MediaPipelineDeviceParams& params) { |
| // Set up the static reference in base::ThreadTaskRunnerHandle::Get |
| // for the media thread in this shared library. We can extract the |
| // SingleThreadTaskRunner passed in from cast_shell for this. |
| if (!base::ThreadTaskRunnerHandle::IsSet()) { |
| DCHECK(!g_thread_task_runner_handle); |
| const scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| static_cast<TaskRunnerImpl*>(params.task_runner)->runner(); |
| DCHECK(task_runner->BelongsToCurrentThread()); |
| g_thread_task_runner_handle.reset( |
| new base::ThreadTaskRunnerHandle(task_runner)); |
| } |
| |
| // TODO(cleichner): Implement MediaSyncType in MediaPipelineDeviceAlsa |
| return new MediaPipelineBackendAlsa(params); |
| } |
| |
| double CastMediaShlib::GetMediaClockRate() { |
| int ppm = 0; |
| if (!g_rate_offset_element) { |
| VLOG(1) << "g_rate_offset_element is null, ALSA rate offset control will " |
| "not be possible."; |
| return kOneMhzReference; |
| } |
| snd_ctl_elem_value_t* rate_offset_ppm; |
| snd_ctl_elem_value_alloca(&rate_offset_ppm); |
| int err = snd_hctl_elem_read(g_rate_offset_element, rate_offset_ppm); |
| if (err < 0) { |
| LOG(ERROR) << "snd_htcl_elem_read error: " << snd_strerror(err); |
| return kOneMhzReference; |
| } |
| ppm = snd_ctl_elem_value_get_integer(rate_offset_ppm, 0); |
| return kOneMhzReference + ppm; |
| } |
| |
| double CastMediaShlib::MediaClockRatePrecision() { |
| return kGranularityHz; |
| } |
| |
| void CastMediaShlib::MediaClockRateRange(double* minimum_rate, |
| double* maximum_rate) { |
| DCHECK(minimum_rate); |
| DCHECK(maximum_rate); |
| |
| *minimum_rate = kOneMhzReference - kMaxAdjustmentHz; |
| *maximum_rate = kOneMhzReference + kMaxAdjustmentHz; |
| } |
| |
| bool CastMediaShlib::SetMediaClockRate(double new_rate) { |
| int new_ppm = new_rate - kOneMhzReference; |
| if (!g_rate_offset_element) { |
| VLOG(1) << "g_rate_offset_element is null, ALSA rate offset control will " |
| "not be possible."; |
| return false; |
| } |
| snd_ctl_elem_value_t* rate_offset_ppm; |
| snd_ctl_elem_value_alloca(&rate_offset_ppm); |
| snd_ctl_elem_value_set_integer(rate_offset_ppm, 0, new_ppm); |
| int err = snd_hctl_elem_write(g_rate_offset_element, rate_offset_ppm); |
| if (err < 0) { |
| LOG(ERROR) << "snd_htcl_elem_write error: " << snd_strerror(err); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CastMediaShlib::SupportsMediaClockRateChange() { |
| return g_rate_offset_element != nullptr; |
| } |
| |
| void CastMediaShlib::AddLoopbackAudioObserver(LoopbackAudioObserver* observer) { |
| StreamMixerAlsa::Get()->AddLoopbackAudioObserver(observer); |
| } |
| |
| void CastMediaShlib::RemoveLoopbackAudioObserver( |
| LoopbackAudioObserver* observer) { |
| StreamMixerAlsa::Get()->RemoveLoopbackAudioObserver(observer); |
| } |
| |
| } // namespace media |
| } // namespace chromecast |