1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "AudioPowerUsage"
19 #include <utils/Log.h>
20
21 #include "AudioAnalytics.h"
22 #include "MediaMetricsService.h"
23 #include "StringUtils.h"
24 #include <map>
25 #include <sstream>
26 #include <string>
27 #include <audio_utils/clock.h>
28 #include <cutils/properties.h>
29 #include <statslog.h>
30 #include <sys/timerfd.h>
31 #include <system/audio.h>
32
33 // property to disable audio power use metrics feature, default is enabled
34 #define PROP_AUDIO_METRICS_DISABLED "persist.media.audio_metrics.power_usage_disabled"
35 #define AUDIO_METRICS_DISABLED_DEFAULT (false)
36
37 // property to set how long to send audio power use metrics data to statsd, default is 24hrs
38 #define PROP_AUDIO_METRICS_INTERVAL_HR "persist.media.audio_metrics.interval_hr"
39 #define INTERVAL_HR_DEFAULT (24)
40
41 // for Audio Power Usage Metrics
42 #define AUDIO_POWER_USAGE_KEY_AUDIO_USAGE "audio.power.usage"
43
44 #define AUDIO_POWER_USAGE_PROP_DEVICE "device" // int32
45 #define AUDIO_POWER_USAGE_PROP_DURATION_NS "durationNs" // int64
46 #define AUDIO_POWER_USAGE_PROP_TYPE "type" // int32
47 #define AUDIO_POWER_USAGE_PROP_VOLUME "volume" // double
48
49 namespace android::mediametrics {
50
51 /* static */
typeFromString(const std::string & type_string,int32_t & type)52 bool AudioPowerUsage::typeFromString(const std::string& type_string, int32_t& type) {
53 static std::map<std::string, int32_t> typeTable = {
54 { "AUDIO_STREAM_VOICE_CALL", VOIP_CALL_TYPE },
55 { "AUDIO_STREAM_SYSTEM", MEDIA_TYPE },
56 { "AUDIO_STREAM_RING", RINGTONE_NOTIFICATION_TYPE },
57 { "AUDIO_STREAM_MUSIC", MEDIA_TYPE },
58 { "AUDIO_STREAM_ALARM", ALARM_TYPE },
59 { "AUDIO_STREAM_NOTIFICATION", RINGTONE_NOTIFICATION_TYPE },
60
61 { "AUDIO_CONTENT_TYPE_SPEECH", VOIP_CALL_TYPE },
62 { "AUDIO_CONTENT_TYPE_MUSIC", MEDIA_TYPE },
63 { "AUDIO_CONTENT_TYPE_MOVIE", MEDIA_TYPE },
64 { "AUDIO_CONTENT_TYPE_SONIFICATION", RINGTONE_NOTIFICATION_TYPE },
65
66 { "AUDIO_USAGE_MEDIA", MEDIA_TYPE },
67 { "AUDIO_USAGE_VOICE_COMMUNICATION", VOIP_CALL_TYPE },
68 { "AUDIO_USAGE_ALARM", ALARM_TYPE },
69 { "AUDIO_USAGE_NOTIFICATION", RINGTONE_NOTIFICATION_TYPE },
70
71 { "AUDIO_SOURCE_CAMCORDER", CAMCORDER_TYPE },
72 { "AUDIO_SOURCE_VOICE_COMMUNICATION", VOIP_CALL_TYPE },
73 { "AUDIO_SOURCE_DEFAULT", RECORD_TYPE },
74 { "AUDIO_SOURCE_MIC", RECORD_TYPE },
75 { "AUDIO_SOURCE_UNPROCESSED", RECORD_TYPE },
76 { "AUDIO_SOURCE_VOICE_RECOGNITION", RECORD_TYPE },
77 };
78
79 auto it = typeTable.find(type_string);
80 if (it == typeTable.end()) {
81 type = UNKNOWN_TYPE;
82 return false;
83 }
84
85 type = it->second;
86 return true;
87 }
88
89 /* static */
deviceFromString(const std::string & device_string,int32_t & device)90 bool AudioPowerUsage::deviceFromString(const std::string& device_string, int32_t& device) {
91 static std::map<std::string, int32_t> deviceTable = {
92 { "AUDIO_DEVICE_OUT_EARPIECE", OUTPUT_EARPIECE },
93 { "AUDIO_DEVICE_OUT_SPEAKER_SAFE", OUTPUT_SPEAKER_SAFE },
94 { "AUDIO_DEVICE_OUT_SPEAKER", OUTPUT_SPEAKER },
95 { "AUDIO_DEVICE_OUT_WIRED_HEADSET", OUTPUT_WIRED_HEADSET },
96 { "AUDIO_DEVICE_OUT_WIRED_HEADPHONE", OUTPUT_WIRED_HEADSET },
97 { "AUDIO_DEVICE_OUT_BLUETOOTH_SCO", OUTPUT_BLUETOOTH_SCO },
98 { "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP", OUTPUT_BLUETOOTH_A2DP },
99 { "AUDIO_DEVICE_OUT_USB_HEADSET", OUTPUT_USB_HEADSET },
100 { "AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET", OUTPUT_BLUETOOTH_SCO },
101
102 { "AUDIO_DEVICE_IN_BUILTIN_MIC", INPUT_BUILTIN_MIC },
103 { "AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET", INPUT_BLUETOOTH_SCO },
104 { "AUDIO_DEVICE_IN_WIRED_HEADSET", INPUT_WIRED_HEADSET_MIC },
105 { "AUDIO_DEVICE_IN_USB_DEVICE", INPUT_USB_HEADSET_MIC },
106 { "AUDIO_DEVICE_IN_BACK_MIC", INPUT_BUILTIN_BACK_MIC },
107 };
108
109 auto it = deviceTable.find(device_string);
110 if (it == deviceTable.end()) {
111 device = 0;
112 return false;
113 }
114
115 device = it->second;
116 return true;
117 }
118
deviceFromStringPairs(const std::string & device_strings)119 int32_t AudioPowerUsage::deviceFromStringPairs(const std::string& device_strings) {
120 int32_t deviceMask = 0;
121 const auto devaddrvec = stringutils::getDeviceAddressPairs(device_strings);
122 for (const auto &[device, addr] : devaddrvec) {
123 int32_t combo_device = 0;
124 deviceFromString(device, combo_device);
125 deviceMask |= combo_device;
126 }
127 return deviceMask;
128 }
129
sendItem(const std::shared_ptr<const mediametrics::Item> & item) const130 void AudioPowerUsage::sendItem(const std::shared_ptr<const mediametrics::Item>& item) const
131 {
132 int32_t type;
133 if (!item->getInt32(AUDIO_POWER_USAGE_PROP_TYPE, &type)) return;
134
135 int32_t audio_device;
136 if (!item->getInt32(AUDIO_POWER_USAGE_PROP_DEVICE, &audio_device)) return;
137
138 int64_t duration_ns;
139 if (!item->getInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, &duration_ns)) return;
140
141 double volume;
142 if (!item->getDouble(AUDIO_POWER_USAGE_PROP_VOLUME, &volume)) return;
143
144 const int32_t duration_secs = (int32_t)(duration_ns / NANOS_PER_SECOND);
145 const float average_volume = (float)volume;
146 const int result = android::util::stats_write(android::util::AUDIO_POWER_USAGE_DATA_REPORTED,
147 audio_device,
148 duration_secs,
149 average_volume,
150 type);
151
152 std::stringstream log;
153 log << "result:" << result << " {"
154 << " mediametrics_audio_power_usage_data_reported:"
155 << android::util::AUDIO_POWER_USAGE_DATA_REPORTED
156 << " audio_device:" << audio_device
157 << " duration_secs:" << duration_secs
158 << " average_volume:" << average_volume
159 << " type:" << type
160 << " }";
161 mStatsdLog->log(android::util::AUDIO_POWER_USAGE_DATA_REPORTED, log.str());
162 }
163
saveAsItem_l(int32_t device,int64_t duration_ns,int32_t type,double average_vol)164 bool AudioPowerUsage::saveAsItem_l(
165 int32_t device, int64_t duration_ns, int32_t type, double average_vol)
166 {
167 ALOGV("%s: (%#x, %d, %lld, %f)", __func__, device, type,
168 (long long)duration_ns, average_vol );
169 if (duration_ns == 0) {
170 return true; // skip duration 0 usage
171 }
172 if (device == 0) {
173 return true; //ignore unknown device
174 }
175
176 for (const auto& item : mItems) {
177 int32_t item_type = 0, item_device = 0;
178 double item_volume = 0.;
179 int64_t item_duration_ns = 0;
180 item->getInt32(AUDIO_POWER_USAGE_PROP_DEVICE, &item_device);
181 item->getInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, &item_duration_ns);
182 item->getInt32(AUDIO_POWER_USAGE_PROP_TYPE, &item_type);
183 item->getDouble(AUDIO_POWER_USAGE_PROP_VOLUME, &item_volume);
184
185 // aggregate by device and type
186 if (item_device == device && item_type == type) {
187 int64_t final_duration_ns = item_duration_ns + duration_ns;
188 double final_volume = (device & INPUT_DEVICE_BIT) ? 1.0:
189 ((item_volume * (double)item_duration_ns +
190 average_vol * (double)duration_ns) / (double)final_duration_ns);
191
192 item->setInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, final_duration_ns);
193 item->setDouble(AUDIO_POWER_USAGE_PROP_VOLUME, final_volume);
194 item->setTimestamp(systemTime(SYSTEM_TIME_REALTIME));
195
196 ALOGV("%s: update (%#x, %d, %lld, %f) --> (%lld, %f)", __func__,
197 device, type,
198 (long long)item_duration_ns, item_volume,
199 (long long)final_duration_ns, final_volume);
200
201 return true;
202 }
203 }
204
205 auto sitem = std::make_shared<mediametrics::Item>(AUDIO_POWER_USAGE_KEY_AUDIO_USAGE);
206 sitem->setTimestamp(systemTime(SYSTEM_TIME_REALTIME));
207 sitem->setInt32(AUDIO_POWER_USAGE_PROP_DEVICE, device);
208 sitem->setInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, duration_ns);
209 sitem->setInt32(AUDIO_POWER_USAGE_PROP_TYPE, type);
210 sitem->setDouble(AUDIO_POWER_USAGE_PROP_VOLUME, average_vol);
211 mItems.emplace_back(sitem);
212 return true;
213 }
214
saveAsItems_l(int32_t device,int64_t duration_ns,int32_t type,double average_vol)215 bool AudioPowerUsage::saveAsItems_l(
216 int32_t device, int64_t duration_ns, int32_t type, double average_vol)
217 {
218 ALOGV("%s: (%#x, %d, %lld, %f)", __func__, device, type,
219 (long long)duration_ns, average_vol );
220 if (duration_ns == 0) {
221 return true; // skip duration 0 usage
222 }
223 if (device == 0) {
224 return true; //ignore unknown device
225 }
226
227 bool ret = false;
228 const int32_t input_bit = device & INPUT_DEVICE_BIT;
229 int32_t device_bits = device ^ input_bit;
230
231 while (device_bits != 0) {
232 int32_t tmp_device = device_bits & -device_bits; // get lowest bit
233 device_bits ^= tmp_device; // clear lowest bit
234 tmp_device |= input_bit; // restore input bit
235 ret = saveAsItem_l(tmp_device, duration_ns, type, average_vol);
236
237 ALOGV("%s: device %#x recorded, remaining device_bits = %#x", __func__,
238 tmp_device, device_bits);
239 }
240 return ret;
241 }
242
checkTrackRecord(const std::shared_ptr<const mediametrics::Item> & item,bool isTrack)243 void AudioPowerUsage::checkTrackRecord(
244 const std::shared_ptr<const mediametrics::Item>& item, bool isTrack)
245 {
246 const std::string key = item->getKey();
247
248 int64_t deviceTimeNs = 0;
249 if (!item->getInt64(AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs)) {
250 return;
251 }
252 double deviceVolume = 1.;
253 if (isTrack && !item->getDouble(AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume)) {
254 return;
255 }
256 int32_t type = 0;
257 std::string type_string;
258 if ((isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
259 key, AMEDIAMETRICS_PROP_STREAMTYPE, &type_string) == OK) ||
260 (!isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
261 key, AMEDIAMETRICS_PROP_SOURCE, &type_string) == OK)) {
262 typeFromString(type_string, type);
263
264 if (isTrack && type == UNKNOWN_TYPE &&
265 mAudioAnalytics->mAnalyticsState->timeMachine().get(
266 key, AMEDIAMETRICS_PROP_USAGE, &type_string) == OK) {
267 typeFromString(type_string, type);
268 }
269 if (isTrack && type == UNKNOWN_TYPE &&
270 mAudioAnalytics->mAnalyticsState->timeMachine().get(
271 key, AMEDIAMETRICS_PROP_CONTENTTYPE, &type_string) == OK) {
272 typeFromString(type_string, type);
273 }
274 ALOGV("type = %s => %d", type_string.c_str(), type);
275 }
276
277 int32_t device = 0;
278 std::string device_strings;
279 if ((isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
280 key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &device_strings) == OK) ||
281 (!isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get(
282 key, AMEDIAMETRICS_PROP_INPUTDEVICES, &device_strings) == OK)) {
283
284 device = deviceFromStringPairs(device_strings);
285 ALOGV("device = %s => %d", device_strings.c_str(), device);
286 }
287 std::lock_guard l(mLock);
288 saveAsItems_l(device, deviceTimeNs, type, deviceVolume);
289 }
290
checkMode(const std::shared_ptr<const mediametrics::Item> & item)291 void AudioPowerUsage::checkMode(const std::shared_ptr<const mediametrics::Item>& item)
292 {
293 std::string mode;
294 if (!item->getString(AMEDIAMETRICS_PROP_AUDIOMODE, &mode)) return;
295
296 std::lock_guard l(mLock);
297 if (mode == mMode) return; // no change in mode.
298
299 if (mMode == "AUDIO_MODE_IN_CALL") { // leaving call mode
300 const int64_t endCallNs = item->getTimestamp();
301 const int64_t durationNs = endCallNs - mDeviceTimeNs;
302 if (durationNs > 0) {
303 mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) +
304 mVoiceVolume * double(endCallNs - mVolumeTimeNs)) / (double)durationNs;
305 saveAsItems_l(mPrimaryDevice, durationNs, VOICE_CALL_TYPE, mDeviceVolume);
306 }
307 } else if (mode == "AUDIO_MODE_IN_CALL") { // entering call mode
308 mStartCallNs = item->getTimestamp(); // advisory only
309
310 mDeviceVolume = 0;
311 mVolumeTimeNs = mStartCallNs;
312 mDeviceTimeNs = mStartCallNs;
313 }
314 ALOGV("%s: new mode:%s old mode:%s", __func__, mode.c_str(), mMode.c_str());
315 mMode = mode;
316 }
317
checkVoiceVolume(const std::shared_ptr<const mediametrics::Item> & item)318 void AudioPowerUsage::checkVoiceVolume(const std::shared_ptr<const mediametrics::Item>& item)
319 {
320 double voiceVolume = 0.;
321 if (!item->getDouble(AMEDIAMETRICS_PROP_VOICEVOLUME, &voiceVolume)) return;
322
323 std::lock_guard l(mLock);
324 if (voiceVolume == mVoiceVolume) return; // no change in volume
325
326 // we only track average device volume when we are in-call
327 if (mMode == "AUDIO_MODE_IN_CALL") {
328 const int64_t timeNs = item->getTimestamp();
329 const int64_t durationNs = timeNs - mDeviceTimeNs;
330 if (durationNs > 0) {
331 mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) +
332 mVoiceVolume * double(timeNs - mVolumeTimeNs)) / (double)durationNs;
333 mVolumeTimeNs = timeNs;
334 }
335 }
336 ALOGV("%s: new voice volume:%lf old voice volume:%lf", __func__, voiceVolume, mVoiceVolume);
337 mVoiceVolume = voiceVolume;
338 }
339
checkCreatePatch(const std::shared_ptr<const mediametrics::Item> & item)340 void AudioPowerUsage::checkCreatePatch(const std::shared_ptr<const mediametrics::Item>& item)
341 {
342 std::string outputDevices;
343 if (!item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices)) return;
344
345 const std::string& key = item->getKey();
346 std::string flags;
347 if (mAudioAnalytics->mAnalyticsState->timeMachine().get(
348 key, AMEDIAMETRICS_PROP_FLAGS, &flags) != OK) return;
349
350 if (flags.find("AUDIO_OUTPUT_FLAG_PRIMARY") == std::string::npos) return;
351
352 const int32_t device = deviceFromStringPairs(outputDevices);
353
354 std::lock_guard l(mLock);
355 if (mPrimaryDevice == device) return;
356
357 if (mMode == "AUDIO_MODE_IN_CALL") {
358 // Save statistics
359 const int64_t endDeviceNs = item->getTimestamp();
360 const int64_t durationNs = endDeviceNs - mDeviceTimeNs;
361 if (durationNs > 0) {
362 mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) +
363 mVoiceVolume * double(endDeviceNs - mVolumeTimeNs)) / (double)durationNs;
364 saveAsItems_l(mPrimaryDevice, durationNs, VOICE_CALL_TYPE, mDeviceVolume);
365 }
366 // reset statistics
367 mDeviceVolume = 0;
368 mDeviceTimeNs = endDeviceNs;
369 mVolumeTimeNs = endDeviceNs;
370 }
371 ALOGV("%s: new primary device:%#x old primary device:%#x", __func__, device, mPrimaryDevice);
372 mPrimaryDevice = device;
373 }
374
AudioPowerUsage(AudioAnalytics * audioAnalytics,const std::shared_ptr<StatsdLog> & statsdLog)375 AudioPowerUsage::AudioPowerUsage(
376 AudioAnalytics *audioAnalytics, const std::shared_ptr<StatsdLog>& statsdLog)
377 : mAudioAnalytics(audioAnalytics)
378 , mStatsdLog(statsdLog)
379 , mDisabled(property_get_bool(PROP_AUDIO_METRICS_DISABLED, AUDIO_METRICS_DISABLED_DEFAULT))
380 , mIntervalHours(property_get_int32(PROP_AUDIO_METRICS_INTERVAL_HR, INTERVAL_HR_DEFAULT))
381 {
382 ALOGD("%s", __func__);
383 ALOGI_IF(mDisabled, "AudioPowerUsage is disabled.");
384 collect(); // send items
385 }
386
~AudioPowerUsage()387 AudioPowerUsage::~AudioPowerUsage()
388 {
389 ALOGD("%s", __func__);
390 }
391
clear()392 void AudioPowerUsage::clear()
393 {
394 std::lock_guard _l(mLock);
395 mItems.clear();
396 }
397
collect()398 void AudioPowerUsage::collect()
399 {
400 std::lock_guard _l(mLock);
401 for (const auto &item : mItems) {
402 sendItem(item);
403 }
404 mItems.clear();
405 mAudioAnalytics->mTimedAction.postIn(
406 mIntervalHours <= 0 ? std::chrono::seconds(5) : std::chrono::hours(mIntervalHours),
407 [this](){ collect(); });
408 }
409
dump(int limit) const410 std::pair<std::string, int32_t> AudioPowerUsage::dump(int limit) const {
411 if (limit <= 2) {
412 return {{}, 0};
413 }
414 std::lock_guard _l(mLock);
415 if (mDisabled) {
416 return {"AudioPowerUsage disabled\n", 1};
417 }
418 if (mItems.empty()) {
419 return {"AudioPowerUsage empty\n", 1};
420 }
421
422 int slot = 1;
423 std::stringstream ss;
424 ss << "AudioPowerUsage:\n";
425 for (const auto &item : mItems) {
426 if (slot >= limit - 1) {
427 ss << "-- AudioPowerUsage may be truncated!\n";
428 ++slot;
429 break;
430 }
431 ss << " " << slot << " " << item->toString() << "\n";
432 slot++;
433 }
434 return { ss.str(), slot };
435 }
436
437 } // namespace android::mediametrics
438