1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chromecast/media/cma/backend/android/volume_control_android.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "base/bind.h"
15 #include "base/callback_helpers.h"
16 #include "base/files/file_util.h"
17 #include "base/location.h"
18 #include "base/logging.h"
19 #include "base/memory/ptr_util.h"
20 #include "base/message_loop/message_pump_type.h"
21 #include "base/no_destructor.h"
22 #include "base/numerics/ranges.h"
23 #include "chromecast/base/init_command_line_shlib.h"
24 #include "chromecast/base/serializers.h"
25 #include "chromecast/chromecast_buildflags.h"
26 #include "chromecast/media/cma/backend/android/audio_track_jni_headers/VolumeControl_jni.h"
27 #if BUILDFLAG(ENABLE_VOLUME_TABLES_ACCESS)
28 #include "chromecast/media/cma/backend/android/audio_track_jni_headers/VolumeMap_jni.h"
29 #endif
30
31 namespace chromecast {
32 namespace media {
33
GetVolumeControl()34 VolumeControlAndroid& GetVolumeControl() {
35 static base::NoDestructor<VolumeControlAndroid> volume_control;
36 return *volume_control;
37 }
38
VolumeControlAndroid()39 VolumeControlAndroid::VolumeControlAndroid()
40 : thread_("VolumeControl"),
41 initialize_complete_event_(
42 base::WaitableEvent::ResetPolicy::MANUAL,
43 base::WaitableEvent::InitialState::NOT_SIGNALED) {
44 DCHECK(j_volume_control_.is_null());
45 j_volume_control_.Reset(Java_VolumeControl_createVolumeControl(
46 base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this)));
47
48 base::Thread::Options options;
49 options.message_pump_type = base::MessagePumpType::IO;
50 thread_.StartWithOptions(options);
51
52 thread_.task_runner()->PostTask(
53 FROM_HERE, base::BindOnce(&VolumeControlAndroid::InitializeOnThread,
54 base::Unretained(this)));
55 initialize_complete_event_.Wait();
56 }
57
~VolumeControlAndroid()58 VolumeControlAndroid::~VolumeControlAndroid() {}
59
AddVolumeObserver(VolumeObserver * observer)60 void VolumeControlAndroid::AddVolumeObserver(VolumeObserver* observer) {
61 base::AutoLock lock(observer_lock_);
62 volume_observers_.push_back(observer);
63 }
64
RemoveVolumeObserver(VolumeObserver * observer)65 void VolumeControlAndroid::RemoveVolumeObserver(VolumeObserver* observer) {
66 base::AutoLock lock(observer_lock_);
67 volume_observers_.erase(
68 std::remove(volume_observers_.begin(), volume_observers_.end(), observer),
69 volume_observers_.end());
70 }
71
GetVolume(AudioContentType type)72 float VolumeControlAndroid::GetVolume(AudioContentType type) {
73 base::AutoLock lock(volume_lock_);
74 // The return level needs to be in the kMedia (MUSIC) volume table domain.
75 return MapIntoDifferentVolumeTableDomain(type, AudioContentType::kMedia,
76 volumes_[type]);
77 }
78
SetVolume(VolumeChangeSource source,AudioContentType type,float level)79 void VolumeControlAndroid::SetVolume(VolumeChangeSource source,
80 AudioContentType type,
81 float level) {
82 if (type == AudioContentType::kOther) {
83 NOTREACHED() << "Can't set volume for content type kOther";
84 return;
85 }
86
87 level = base::ClampToRange(level, 0.0f, 1.0f);
88 // The input level value is in the kMedia (MUSIC) volume table domain.
89 float mapped_level =
90 MapIntoDifferentVolumeTableDomain(AudioContentType::kMedia, type, level);
91 thread_.task_runner()->PostTask(
92 FROM_HERE, base::BindOnce(&VolumeControlAndroid::SetVolumeOnThread,
93 base::Unretained(this), source, type,
94 mapped_level, false /* from_android */));
95 }
96
IsMuted(AudioContentType type)97 bool VolumeControlAndroid::IsMuted(AudioContentType type) {
98 base::AutoLock lock(volume_lock_);
99 return muted_[type];
100 }
101
SetMuted(VolumeChangeSource source,AudioContentType type,bool muted)102 void VolumeControlAndroid::SetMuted(VolumeChangeSource source,
103 AudioContentType type,
104 bool muted) {
105 if (type == AudioContentType::kOther) {
106 NOTREACHED() << "Can't set mute state for content type kOther";
107 return;
108 }
109
110 thread_.task_runner()->PostTask(
111 FROM_HERE, base::BindOnce(&VolumeControlAndroid::SetMutedOnThread,
112 base::Unretained(this), source, type, muted,
113 false /* from_android */));
114 }
115
SetOutputLimit(AudioContentType type,float limit)116 void VolumeControlAndroid::SetOutputLimit(AudioContentType type, float limit) {
117 if (type == AudioContentType::kOther) {
118 NOTREACHED() << "Can't set output limit for content type kOther";
119 return;
120 }
121
122 // The input limit is in the kMedia (MUSIC) volume table domain.
123 limit = base::ClampToRange(limit, 0.0f, 1.0f);
124 float limit_db = VolumeToDbFSCached(AudioContentType::kMedia, limit);
125 AudioSinkManager::Get()->SetOutputLimitDb(type, limit_db);
126 }
127
OnVolumeChange(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint type,jfloat level)128 void VolumeControlAndroid::OnVolumeChange(
129 JNIEnv* env,
130 const base::android::JavaParamRef<jobject>& obj,
131 jint type,
132 jfloat level) {
133 thread_.task_runner()->PostTask(
134 FROM_HERE,
135 base::BindOnce(&VolumeControlAndroid::ReportVolumeChangeOnThread,
136 base::Unretained(this), (AudioContentType)type, level));
137 }
138
OnMuteChange(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint type,jboolean muted)139 void VolumeControlAndroid::OnMuteChange(
140 JNIEnv* env,
141 const base::android::JavaParamRef<jobject>& obj,
142 jint type,
143 jboolean muted) {
144 thread_.task_runner()->PostTask(
145 FROM_HERE,
146 base::BindOnce(&VolumeControlAndroid::ReportMuteChangeOnThread,
147 base::Unretained(this), (AudioContentType)type, muted));
148 }
149
150 #if BUILDFLAG(ENABLE_VOLUME_TABLES_ACCESS)
151
GetMaxVolumeIndex(AudioContentType type)152 int VolumeControlAndroid::GetMaxVolumeIndex(AudioContentType type) {
153 return Java_VolumeMap_getMaxVolumeIndex(base::android::AttachCurrentThread(),
154 static_cast<int>(type));
155 }
156
VolumeToDbFS(AudioContentType type,float volume)157 float VolumeControlAndroid::VolumeToDbFS(AudioContentType type, float volume) {
158 return Java_VolumeMap_volumeToDbFs(base::android::AttachCurrentThread(),
159 static_cast<int>(type), volume);
160 }
161
162 #else // Dummies:
163
GetMaxVolumeIndex(AudioContentType type)164 int VolumeControlAndroid::GetMaxVolumeIndex(AudioContentType type) {
165 return 1;
166 }
167
VolumeToDbFS(AudioContentType type,float volume)168 float VolumeControlAndroid::VolumeToDbFS(AudioContentType type, float volume) {
169 return 1.0f;
170 }
171
172 #endif
173
InitializeOnThread()174 void VolumeControlAndroid::InitializeOnThread() {
175 DCHECK(thread_.task_runner()->BelongsToCurrentThread());
176
177 for (auto type :
178 {AudioContentType::kMedia, AudioContentType::kAlarm,
179 AudioContentType::kCommunication, AudioContentType::kOther}) {
180 std::unique_ptr<VolumeCache> vc(new VolumeCache(type, this));
181 volume_cache_.emplace(type, std::move(vc));
182
183 volumes_[type] =
184 Java_VolumeControl_getVolume(base::android::AttachCurrentThread(),
185 j_volume_control_, static_cast<int>(type));
186 float volume_db = VolumeToDbFSCached(type, volumes_[type]);
187 AudioSinkManager::Get()->SetTypeVolumeDb(type, volume_db);
188 muted_[type] =
189 Java_VolumeControl_isMuted(base::android::AttachCurrentThread(),
190 j_volume_control_, static_cast<int>(type));
191 LOG(INFO) << __func__ << ": Initial values for"
192 << " type=" << static_cast<int>(type) << ": "
193 << " volume=" << volumes_[type] << " (" << volume_db << ")"
194 << " mute=" << muted_[type];
195 }
196
197 #if !BUILDFLAG(IS_SINGLE_VOLUME)
198 // The kOther content type should not have any type-wide volume control or
199 // mute (volume control for kOther is per-stream only). Therefore, ensure
200 // that the global volume and mute state fo kOther is initialized correctly
201 // (100% volume, and not muted).
202 SetVolumeOnThread(VolumeChangeSource::kAutomatic, AudioContentType::kOther,
203 1.0f, false /* from_android */);
204 SetMutedOnThread(VolumeChangeSource::kAutomatic, AudioContentType::kOther,
205 false, false /* from_android */);
206 #endif
207
208 initialize_complete_event_.Signal();
209 }
210
SetVolumeOnThread(VolumeChangeSource source,AudioContentType type,float level,bool from_android)211 void VolumeControlAndroid::SetVolumeOnThread(VolumeChangeSource source,
212 AudioContentType type,
213 float level,
214 bool from_android) {
215 // Note: |level| is in the |type| volume table domain.
216 DCHECK(thread_.task_runner()->BelongsToCurrentThread());
217 {
218 base::AutoLock lock(volume_lock_);
219 if (level == volumes_[type]) {
220 return;
221 }
222 volumes_[type] = level;
223 }
224
225 float level_db = VolumeToDbFSCached(type, level);
226 LOG(INFO) << __func__ << ": level=" << level << " (" << level_db << ")";
227 // Provide the type volume to the sink manager so it can properly calculate
228 // the limiter multiplier. The volume is *not* applied by the sink though.
229 AudioSinkManager::Get()->SetTypeVolumeDb(type, level_db);
230
231 // Set proper volume in Android OS.
232 if (!from_android) {
233 Java_VolumeControl_setVolume(base::android::AttachCurrentThread(),
234 j_volume_control_, static_cast<int>(type),
235 level);
236 }
237
238 // Report new volume level to observers. Note that the reported value needs
239 // to be in the kMedia (MUSIC) volume table domain.
240 float media_level =
241 MapIntoDifferentVolumeTableDomain(type, AudioContentType::kMedia, level);
242 {
243 base::AutoLock lock(observer_lock_);
244 for (VolumeObserver* observer : volume_observers_) {
245 observer->OnVolumeChange(source, type, media_level);
246 }
247 }
248 }
249
SetMutedOnThread(VolumeChangeSource source,AudioContentType type,bool muted,bool from_android)250 void VolumeControlAndroid::SetMutedOnThread(VolumeChangeSource source,
251 AudioContentType type,
252 bool muted,
253 bool from_android) {
254 DCHECK(thread_.task_runner()->BelongsToCurrentThread());
255 {
256 base::AutoLock lock(volume_lock_);
257 if (muted == muted_[type]) {
258 return;
259 }
260 muted_[type] = muted;
261 }
262
263 if (!from_android) {
264 Java_VolumeControl_setMuted(base::android::AttachCurrentThread(),
265 j_volume_control_, static_cast<int>(type),
266 muted);
267 }
268
269 {
270 base::AutoLock lock(observer_lock_);
271 for (VolumeObserver* observer : volume_observers_) {
272 observer->OnMuteChange(source, type, muted);
273 }
274 }
275 }
276
ReportVolumeChangeOnThread(AudioContentType type,float level)277 void VolumeControlAndroid::ReportVolumeChangeOnThread(AudioContentType type,
278 float level) {
279 DCHECK(thread_.task_runner()->BelongsToCurrentThread());
280 #if !BUILDFLAG(IS_SINGLE_VOLUME)
281 if (type == AudioContentType::kOther) {
282 // Volume for AudioContentType::kOther should stay at 1.0.
283 Java_VolumeControl_setVolume(base::android::AttachCurrentThread(),
284 j_volume_control_, static_cast<int>(type),
285 1.0f);
286 return;
287 }
288 #endif
289
290 SetVolumeOnThread(VolumeChangeSource::kUser, type, level,
291 true /* from android */);
292 }
293
ReportMuteChangeOnThread(AudioContentType type,bool muted)294 void VolumeControlAndroid::ReportMuteChangeOnThread(AudioContentType type,
295 bool muted) {
296 DCHECK(thread_.task_runner()->BelongsToCurrentThread());
297 #if !BUILDFLAG(IS_SINGLE_VOLUME)
298 if (type == AudioContentType::kOther) {
299 // Mute state for AudioContentType::kOther should always be false.
300 Java_VolumeControl_setMuted(base::android::AttachCurrentThread(),
301 j_volume_control_, static_cast<int>(type),
302 false);
303 return;
304 }
305 #endif
306
307 SetMutedOnThread(VolumeChangeSource::kUser, type, muted,
308 true /* from_android */);
309 }
310
MapIntoDifferentVolumeTableDomain(AudioContentType from_type,AudioContentType to_type,float level)311 float VolumeControlAndroid::MapIntoDifferentVolumeTableDomain(
312 AudioContentType from_type,
313 AudioContentType to_type,
314 float level) {
315 if (from_type == to_type) {
316 return level;
317 }
318 float from_db = VolumeToDbFSCached(from_type, level);
319 return DbFSToVolumeCached(to_type, from_db);
320 }
321
VolumeToDbFSCached(AudioContentType type,float vol_level)322 float VolumeControlAndroid::VolumeToDbFSCached(AudioContentType type,
323 float vol_level) {
324 return volume_cache_[type]->VolumeToDbFS(vol_level);
325 }
326
DbFSToVolumeCached(AudioContentType type,float db)327 float VolumeControlAndroid::DbFSToVolumeCached(AudioContentType type,
328 float db) {
329 return volume_cache_[type]->DbFSToVolume(db);
330 }
331
332 //
333 // Implementation of VolumeControl as defined in public/volume_control.h
334 //
335
336 // static
Initialize(const std::vector<std::string> & argv)337 void VolumeControl::Initialize(const std::vector<std::string>& argv) {
338 // Nothing to do.
339 }
340
341 // static
Finalize()342 void VolumeControl::Finalize() {
343 // Nothing to do.
344 }
345
346 // static
AddVolumeObserver(VolumeObserver * observer)347 void VolumeControl::AddVolumeObserver(VolumeObserver* observer) {
348 GetVolumeControl().AddVolumeObserver(observer);
349 }
350
351 // static
RemoveVolumeObserver(VolumeObserver * observer)352 void VolumeControl::RemoveVolumeObserver(VolumeObserver* observer) {
353 GetVolumeControl().RemoveVolumeObserver(observer);
354 }
355
356 // static
GetVolume(AudioContentType type)357 float VolumeControl::GetVolume(AudioContentType type) {
358 return GetVolumeControl().GetVolume(type);
359 }
360
361 // static
SetVolume(VolumeChangeSource source,AudioContentType type,float level)362 void VolumeControl::SetVolume(VolumeChangeSource source,
363 AudioContentType type,
364 float level) {
365 GetVolumeControl().SetVolume(source, type, level);
366 }
367
368 // static
IsMuted(AudioContentType type)369 bool VolumeControl::IsMuted(AudioContentType type) {
370 return GetVolumeControl().IsMuted(type);
371 }
372
373 // static
SetMuted(VolumeChangeSource source,AudioContentType type,bool muted)374 void VolumeControl::SetMuted(VolumeChangeSource source,
375 AudioContentType type,
376 bool muted) {
377 GetVolumeControl().SetMuted(source, type, muted);
378 }
379
380 // static
SetOutputLimit(AudioContentType type,float limit)381 void VolumeControl::SetOutputLimit(AudioContentType type, float limit) {
382 GetVolumeControl().SetOutputLimit(type, limit);
383 }
384
385 // static
VolumeToDbFS(float volume)386 float VolumeControl::VolumeToDbFS(float volume) {
387 // The volume value is the kMedia (MUSIC) volume table domain.
388 return GetVolumeControl().VolumeToDbFSCached(AudioContentType::kMedia,
389 volume);
390 }
391
392 // static
DbFSToVolume(float db)393 float VolumeControl::DbFSToVolume(float db) {
394 // The db value is the kMedia (MUSIC) volume table domain.
395 return GetVolumeControl().DbFSToVolumeCached(AudioContentType::kMedia, db);
396 }
397
398 } // namespace media
399 } // namespace chromecast
400