blob: e50fbe8426bce1732f7319c3918e5f4d7c6520e8 [file] [log] [blame]
Andy Hung06f3aba2019-12-03 16:36:42 -08001/*
2 * Copyright (C) 2019 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 "AudioAnalytics"
19#include <utils/Log.h>
20
21#include "AudioAnalytics.h"
Andy Hungce9b6632020-04-28 20:15:17 -070022#include "MediaMetricsService.h" // package info
23#include <audio_utils/clock.h> // clock conversions
24#include <statslog.h> // statsd
Andy Hung06f3aba2019-12-03 16:36:42 -080025
Andy Hungce9b6632020-04-28 20:15:17 -070026// Enable for testing of delivery to statsd
27// #define STATSD
Andy Hung06f3aba2019-12-03 16:36:42 -080028
29namespace android::mediametrics {
30
31AudioAnalytics::AudioAnalytics()
32{
33 ALOGD("%s", __func__);
Andy Hung0f7ad8c2020-01-03 13:24:34 -080034
35 // Add action to save AnalyticsState if audioserver is restarted.
36 // This triggers on an item of "audio.flinger"
37 // with a property "event" set to "AudioFlinger" (the constructor).
38 mActions.addAction(
Andy Hungea186fa2020-01-09 18:13:15 -080039 AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
40 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_CTOR),
Andy Hung0f7ad8c2020-01-03 13:24:34 -080041 std::make_shared<AnalyticsActions::Function>(
Andy Hungea186fa2020-01-09 18:13:15 -080042 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
43 ALOGW("(key=%s) Audioflinger constructor event detected", item->getKey().c_str());
Andy Hung0f7ad8c2020-01-03 13:24:34 -080044 mPreviousAnalyticsState.set(std::make_shared<AnalyticsState>(
45 *mAnalyticsState.get()));
46 // Note: get returns shared_ptr temp, whose lifetime is extended
47 // to end of full expression.
48 mAnalyticsState->clear(); // TODO: filter the analytics state.
Andy Hungea186fa2020-01-09 18:13:15 -080049 // Perhaps report this.
50 }));
51
52 // Check underruns
53 mActions.addAction(
54 AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT,
55 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_UNDERRUN),
56 std::make_shared<AnalyticsActions::Function>(
57 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
58 std::string threadId = item->getKey().substr(
59 sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) - 1);
60 std::string outputDevices;
61 mAnalyticsState->timeMachine().get(
62 item->getKey(), AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
63 ALOGD("(key=%s) Thread underrun event detected on io handle:%s device:%s",
64 item->getKey().c_str(), threadId.c_str(), outputDevices.c_str());
65 if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
66 // report this for Bluetooth
67 }
68 }));
69
70 // Check latencies, playback and startup
71 mActions.addAction(
72 AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_LATENCYMS,
73 std::monostate{}, // accept any value
74 std::make_shared<AnalyticsActions::Function>(
75 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
76 double latencyMs{};
77 double startupMs{};
78 if (!item->get(AMEDIAMETRICS_PROP_LATENCYMS, &latencyMs)
79 || !item->get(AMEDIAMETRICS_PROP_STARTUPMS, &startupMs)) return;
80
81 std::string trackId = item->getKey().substr(
82 sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK) - 1);
83 std::string thread = getThreadFromTrack(item->getKey());
84 std::string outputDevices;
85 mAnalyticsState->timeMachine().get(
86 thread, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
87 ALOGD("(key=%s) Track latencyMs:%lf startupMs:%lf detected on port:%s device:%s",
88 item->getKey().c_str(), latencyMs, startupMs,
89 trackId.c_str(), outputDevices.c_str());
90 if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
91 // report this for Bluetooth
92 }
Andy Hung0f7ad8c2020-01-03 13:24:34 -080093 }));
Andy Hungce9b6632020-04-28 20:15:17 -070094
95 // Handle device use thread statistics
96 mActions.addAction(
97 AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT,
98 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
99 std::make_shared<AnalyticsActions::Function>(
100 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
101 mDeviceUse.endAudioIntervalGroup(item, false /* isTrack */);
102 }));
103
104 // Handle device use track statistics
105 mActions.addAction(
106 AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_EVENT,
107 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
108 std::make_shared<AnalyticsActions::Function>(
109 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
110 mDeviceUse.endAudioIntervalGroup(item, true /* isTrack */);
111 }));
112
113 // Handle device routing statistics
114
115 // We track connections (not disconnections) for the time to connect.
116 // TODO: consider BT requests in their A2dp service
117 // AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent
118 // AudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent
119 // AudioDeviceBroker.postA2dpActiveDeviceChange
120 mActions.addAction(
121 "audio.device.a2dp.state",
122 std::string("connected"),
123 std::make_shared<AnalyticsActions::Function>(
124 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
125 mDeviceConnection.a2dpConnected(item);
126 }));
127 // If audio is active, we expect to see a createAudioPatch after the device is connected.
128 mActions.addAction(
129 AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT,
130 std::string("createAudioPatch"),
131 std::make_shared<AnalyticsActions::Function>(
132 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
133 mDeviceConnection.createPatch(item);
134 }));
Joey Poomarin52989982020-03-05 17:40:49 +0800135
136 // Handle power usage
137 mActions.addAction(
138 AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK "*." AMEDIAMETRICS_PROP_EVENT,
139 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
140 std::make_shared<AnalyticsActions::Function>(
141 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
142 mAudioPowerUsage.checkTrackRecord(item, true /* isTrack */);
143 }));
144
145 mActions.addAction(
146 AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD "*." AMEDIAMETRICS_PROP_EVENT,
147 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_ENDAUDIOINTERVALGROUP),
148 std::make_shared<AnalyticsActions::Function>(
149 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
150 mAudioPowerUsage.checkTrackRecord(item, false /* isTrack */);
151 }));
152
153 mActions.addAction(
154 AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
155 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_SETMODE),
156 std::make_shared<AnalyticsActions::Function>(
157 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
158 // ALOGD("(key=%s) Audioflinger setMode", item->getKey().c_str());
159 mAudioPowerUsage.checkMode(item);
160 }));
161
162 mActions.addAction(
163 AMEDIAMETRICS_KEY_AUDIO_FLINGER "." AMEDIAMETRICS_PROP_EVENT,
164 std::string(AMEDIAMETRICS_PROP_EVENT_VALUE_SETVOICEVOLUME),
165 std::make_shared<AnalyticsActions::Function>(
166 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
167 // ALOGD("(key=%s) Audioflinger setVoiceVolume", item->getKey().c_str());
168 mAudioPowerUsage.checkVoiceVolume(item);
169 }));
170
171 mActions.addAction(
172 AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD "*." AMEDIAMETRICS_PROP_EVENT,
173 std::string("createAudioPatch"),
174 std::make_shared<AnalyticsActions::Function>(
175 [this](const std::shared_ptr<const android::mediametrics::Item> &item){
176 mAudioPowerUsage.checkCreatePatch(item);
177 }));
Andy Hung06f3aba2019-12-03 16:36:42 -0800178}
179
180AudioAnalytics::~AudioAnalytics()
181{
182 ALOGD("%s", __func__);
Andy Hungce9b6632020-04-28 20:15:17 -0700183 mTimedAction.quit(); // ensure no deferred access during destructor.
Andy Hung06f3aba2019-12-03 16:36:42 -0800184}
185
186status_t AudioAnalytics::submit(
Ray Essickf27e9872019-12-07 06:28:46 -0800187 const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted)
Andy Hung06f3aba2019-12-03 16:36:42 -0800188{
Andy Hungea186fa2020-01-09 18:13:15 -0800189 if (!startsWith(item->getKey(), AMEDIAMETRICS_KEY_PREFIX_AUDIO)) return BAD_VALUE;
Andy Hung0f7ad8c2020-01-03 13:24:34 -0800190 status_t status = mAnalyticsState->submit(item, isTrusted);
191 if (status != NO_ERROR) return status; // may not be permitted.
192
193 // Only if the item was successfully submitted (permission)
194 // do we check triggered actions.
195 checkActions(item);
196 return NO_ERROR;
Andy Hung06f3aba2019-12-03 16:36:42 -0800197}
198
Andy Hung709b91e2020-04-04 14:23:36 -0700199std::pair<std::string, int32_t> AudioAnalytics::dump(
200 int32_t lines, int64_t sinceNs, const char *prefix) const
Andy Hung06f3aba2019-12-03 16:36:42 -0800201{
202 std::stringstream ss;
203 int32_t ll = lines;
204
205 if (ll > 0) {
Andy Hung709b91e2020-04-04 14:23:36 -0700206 auto [s, l] = mAnalyticsState->dump(ll, sinceNs, prefix);
Andy Hungb744faf2020-04-09 13:09:26 -0700207 ss << s;
Andy Hung06f3aba2019-12-03 16:36:42 -0800208 ll -= l;
209 }
210 if (ll > 0) {
Andy Hung0f7ad8c2020-01-03 13:24:34 -0800211 ss << "Prior audioserver state:\n";
Andy Hung06f3aba2019-12-03 16:36:42 -0800212 --ll;
213 }
214 if (ll > 0) {
Andy Hung709b91e2020-04-04 14:23:36 -0700215 auto [s, l] = mPreviousAnalyticsState->dump(ll, sinceNs, prefix);
Andy Hungb744faf2020-04-09 13:09:26 -0700216 ss << s;
Andy Hung06f3aba2019-12-03 16:36:42 -0800217 ll -= l;
218 }
Joey Poomarin52989982020-03-05 17:40:49 +0800219
220 if (ll > 0 && prefix == nullptr) {
221 auto [s, l] = mAudioPowerUsage.dump(ll);
222 ss << s;
223 ll -= l;
224 }
Andy Hung06f3aba2019-12-03 16:36:42 -0800225 return { ss.str(), lines - ll };
226}
227
Andy Hung0f7ad8c2020-01-03 13:24:34 -0800228void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
229{
230 auto actions = mActions.getActionsForItem(item); // internally locked.
231 // Execute actions with no lock held.
232 for (const auto& action : actions) {
233 (*action)(item);
234 }
235}
236
Andy Hungea186fa2020-01-09 18:13:15 -0800237// HELPER METHODS
238
239std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const
240{
241 int32_t threadId_int32{};
242 if (mAnalyticsState->timeMachine().get(
243 track, AMEDIAMETRICS_PROP_THREADID, &threadId_int32) != NO_ERROR) {
244 return {};
245 }
246 return std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(threadId_int32);
247}
248
Andy Hungce9b6632020-04-28 20:15:17 -0700249// DeviceUse helper class.
250void AudioAnalytics::DeviceUse::endAudioIntervalGroup(
251 const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const {
252 const std::string& key = item->getKey();
253 const std::string id = key.substr(
254 (isTrack ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK)
255 : sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD))
256 - 1);
257 // deliver statistics
258 int64_t deviceTimeNs = 0;
259 mAudioAnalytics.mAnalyticsState->timeMachine().get(
260 key, AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs);
261 std::string encoding;
262 mAudioAnalytics.mAnalyticsState->timeMachine().get(
263 key, AMEDIAMETRICS_PROP_ENCODING, &encoding);
264 int32_t frameCount = 0;
265 mAudioAnalytics.mAnalyticsState->timeMachine().get(
266 key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount);
267 int32_t intervalCount = 0;
268 mAudioAnalytics.mAnalyticsState->timeMachine().get(
269 key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount);
270 std::string outputDevices;
271 mAudioAnalytics.mAnalyticsState->timeMachine().get(
272 key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
273 int32_t sampleRate = 0;
274 mAudioAnalytics.mAnalyticsState->timeMachine().get(
275 key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate);
276 int32_t underrun = 0;
277 mAudioAnalytics.mAnalyticsState->timeMachine().get(
278 key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
279
280 // Get connected device name if from bluetooth.
281 bool isBluetooth = false;
282 std::string name;
283 if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
284 isBluetooth = true;
285 mAudioAnalytics.mAnalyticsState->timeMachine().get(
286 "audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &name);
287 }
288
289 // We may have several devices. We only list the first device.
290 // TODO: consider whether we should list all the devices separated by |
291 std::string firstDevice = "unknown";
292 auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(outputDevices);
293 if (devaddrvec.size() != 0) {
294 firstDevice = devaddrvec[0].first;
295 // DO NOT show the address.
296 }
297
298 if (isTrack) {
299 std::string callerName;
300 mAudioAnalytics.mAnalyticsState->timeMachine().get(
301 key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName);
302 std::string contentType;
303 mAudioAnalytics.mAnalyticsState->timeMachine().get(
304 key, AMEDIAMETRICS_PROP_CONTENTTYPE, &contentType);
305 double deviceLatencyMs = 0.;
306 mAudioAnalytics.mAnalyticsState->timeMachine().get(
307 key, AMEDIAMETRICS_PROP_DEVICELATENCYMS, &deviceLatencyMs);
308 double deviceStartupMs = 0.;
309 mAudioAnalytics.mAnalyticsState->timeMachine().get(
310 key, AMEDIAMETRICS_PROP_DEVICESTARTUPMS, &deviceStartupMs);
311 double deviceVolume = 0.;
312 mAudioAnalytics.mAnalyticsState->timeMachine().get(
313 key, AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume);
314 std::string packageName;
315 int64_t versionCode = 0;
316 int32_t uid = -1;
317 mAudioAnalytics.mAnalyticsState->timeMachine().get(
318 key, AMEDIAMETRICS_PROP_ALLOWUID, &uid);
319 if (uid != -1) {
320 std::tie(packageName, versionCode) =
321 MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid);
322 }
323 double playbackPitch = 0.;
324 mAudioAnalytics.mAnalyticsState->timeMachine().get(
325 key, AMEDIAMETRICS_PROP_PLAYBACK_PITCH, &playbackPitch);
326 double playbackSpeed = 0.;
327 mAudioAnalytics.mAnalyticsState->timeMachine().get(
328 key, AMEDIAMETRICS_PROP_PLAYBACK_SPEED, &playbackSpeed);
329 int32_t selectedDeviceId = 0;
330 mAudioAnalytics.mAnalyticsState->timeMachine().get(
331 key, AMEDIAMETRICS_PROP_SELECTEDDEVICEID, &selectedDeviceId);
332
333 std::string usage;
334 mAudioAnalytics.mAnalyticsState->timeMachine().get(
335 key, AMEDIAMETRICS_PROP_USAGE, &usage);
336
337 ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s "
338 "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
339 "sampleRate:%d underrun:%d "
340 "callerName:%s contentType:%s "
341 "deviceLatencyMs:%lf deviceStartupMs:%lf deviceVolume:%lf"
342 "packageName:%s playbackPitch:%lf playbackSpeed:%lf "
343 "selectedDevceId:%d usage:%s",
344 key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(),
345 (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
346 sampleRate, underrun,
347 callerName.c_str(), contentType.c_str(),
348 deviceLatencyMs, deviceStartupMs, deviceVolume,
349 packageName.c_str(), playbackPitch, playbackSpeed,
350 selectedDeviceId, usage.c_str());
351#ifdef STATSD
352 if (mAudioAnalytics.mDeliverStatistics) {
353 (void)android::util::stats_write(
354 android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED
355 /* timestamp, */
356 /* mediaApexVersion, */
357 , firstDevice.c_str()
358 , name.c_str()
359 , deviceTimeNs
360 , encoding.c_str()
361 , frameCount
362 , intervalCount
363 , sampleRate
364 , underrun
365
366 , packageName.c_str()
367 , (float)deviceLatencyMs
368 , (float)deviceStartupMs
369 , (float)deviceVolume
370 , selectedDeviceId
371 , usage.c_str()
372 , contentType.c_str()
373 , callerName.c_str()
374 );
375 }
376#endif
377 } else {
378
379 std::string flags;
380 mAudioAnalytics.mAnalyticsState->timeMachine().get(
381 key, AMEDIAMETRICS_PROP_FLAGS, &flags);
382
383 ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s "
384 "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
385 "sampleRate:%d underrun:%d "
386 "flags:%s",
387 key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(),
388 (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
389 sampleRate, underrun,
390 flags.c_str());
391#ifdef STATSD
392 if (mAudioAnalytics.mDeliverStatistics) {
393 (void)android::util::stats_write(
394 android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED
395 /* timestamp, */
396 /* mediaApexVersion, */
397 , firstDevice.c_str()
398 , name.c_str()
399 , deviceTimeNs
400 , encoding.c_str()
401 , frameCount
402 , intervalCount
403 , sampleRate
404 , underrun
405 );
406 }
407#endif
408 }
409
410 // Report this as needed.
411 if (isBluetooth) {
412 // report this for Bluetooth
413 }
414}
415
416// DeviceConnection helper class.
417void AudioAnalytics::DeviceConnection::a2dpConnected(
418 const std::shared_ptr<const android::mediametrics::Item> &item) {
419 const std::string& key = item->getKey();
420
421 const int64_t connectedAtNs = item->getTimestamp();
422 {
423 std::lock_guard l(mLock);
424 mA2dpTimeConnectedNs = connectedAtNs;
425 ++mA2dpConnectedAttempts;
426 }
427 std::string name;
428 item->get(AMEDIAMETRICS_PROP_NAME, &name);
429 ALOGD("(key=%s) a2dp connected device:%s "
430 "connectedAtNs:%lld",
431 key.c_str(), name.c_str(),
432 (long long)connectedAtNs);
433 // Note - we need to be able to cancel a timed event
434 mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); });
435 // This sets the time we were connected. Now we look for the delta in the future.
436}
437
438void AudioAnalytics::DeviceConnection::createPatch(
439 const std::shared_ptr<const android::mediametrics::Item> &item) {
440 std::lock_guard l(mLock);
441 if (mA2dpTimeConnectedNs == 0) return; // ignore
442 const std::string& key = item->getKey();
443 std::string outputDevices;
444 item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
445 if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
446 // TODO compare address
447 const int64_t timeDiff = item->getTimestamp() - mA2dpTimeConnectedNs;
448 ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff);
449 mA2dpTimeConnectedNs = 0; // reset counter.
450 ++mA2dpConnectedSuccesses;
451 }
452}
453
454void AudioAnalytics::DeviceConnection::expire() {
455 std::lock_guard l(mLock);
456 if (mA2dpTimeConnectedNs == 0) return; // ignore
457
458 // An expiration may occur because there is no audio playing.
459 // TODO: disambiguate this case.
460 ALOGD("A2DP device connection expired");
461 ++mA2dpConnectedFailures; // this is not a true failure.
462 mA2dpTimeConnectedNs = 0;
463}
464
Andy Hung06f3aba2019-12-03 16:36:42 -0800465} // namespace android