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