chromium/chromecast/media/cma/backend/alsa/cast_media_shlib.cc

// Copyright 2015 The Chromium Authors
// 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/command_line.h"
#include "base/logging.h"
#include "chromecast/media/cma/backend/media_pipeline_backend_for_mixer.h"
#include "chromecast/public/cast_media_shlib.h"
#include "chromecast/public/graphics_types.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;

}  // namespace

void CastMediaShlib::Initialize(const std::vector<std::string>& argv) {
  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;
}

VideoPlane* CastMediaShlib::GetVideoPlane() {
  if (!g_video_plane) {
    g_video_plane = new DefaultVideoPlane();
  }
  return g_video_plane;
}

MediaPipelineBackend* CastMediaShlib::CreateMediaPipelineBackend(
    const MediaPipelineDeviceParams& params) {
  return new MediaPipelineBackendForMixer(params);
}

double CastMediaShlib::GetMediaClockRate() {
  int ppm = 0;
  if (!g_rate_offset_element) {
    LOG(INFO) << "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) {
    LOG(INFO) << "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;
}

}  // namespace media
}  // namespace chromecast