summaryrefslogtreecommitdiffstats
path: root/chromecast/media/cma/backend/alsa/cast_media_shlib.cc
blob: bb4c6b5c88e45c290f5b64969ee28792a2b5c4c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// 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/thread_task_runner_handle.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;

scoped_ptr<base::ThreadTaskRunnerHandle> g_thread_task_runner_handle;

}  // namespace

void CastMediaShlib::Initialize(const std::vector<std::string>& argv) {
  base::CommandLine::Init(0, nullptr);
  base::CommandLine::ForCurrentProcess()->InitFromArgv(argv);

  g_video_plane = new DefaultVideoPlane();

  InitializeAlsaControls();
  ::media::InitializeMediaLibrary();
}

void CastMediaShlib::Finalize() {
  base::CommandLine::Reset();

  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