blob: fe3a34de1102d94c2776b7b86921cfabdcc1ee24 [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 }));
Andy Hung06f3aba2019-12-03 16:36:42 -0800135}
136
137AudioAnalytics::~AudioAnalytics()
138{
139 ALOGD("%s", __func__);
Andy Hungce9b6632020-04-28 20:15:17 -0700140 mTimedAction.quit(); // ensure no deferred access during destructor.
Andy Hung06f3aba2019-12-03 16:36:42 -0800141}
142
143status_t AudioAnalytics::submit(
Ray Essickf27e9872019-12-07 06:28:46 -0800144 const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted)
Andy Hung06f3aba2019-12-03 16:36:42 -0800145{
Andy Hungea186fa2020-01-09 18:13:15 -0800146 if (!startsWith(item->getKey(), AMEDIAMETRICS_KEY_PREFIX_AUDIO)) return BAD_VALUE;
Andy Hung0f7ad8c2020-01-03 13:24:34 -0800147 status_t status = mAnalyticsState->submit(item, isTrusted);
148 if (status != NO_ERROR) return status; // may not be permitted.
149
150 // Only if the item was successfully submitted (permission)
151 // do we check triggered actions.
152 checkActions(item);
153 return NO_ERROR;
Andy Hung06f3aba2019-12-03 16:36:42 -0800154}
155
Andy Hung709b91e2020-04-04 14:23:36 -0700156std::pair<std::string, int32_t> AudioAnalytics::dump(
157 int32_t lines, int64_t sinceNs, const char *prefix) const
Andy Hung06f3aba2019-12-03 16:36:42 -0800158{
159 std::stringstream ss;
160 int32_t ll = lines;
161
162 if (ll > 0) {
Andy Hung709b91e2020-04-04 14:23:36 -0700163 auto [s, l] = mAnalyticsState->dump(ll, sinceNs, prefix);
Andy Hungb744faf2020-04-09 13:09:26 -0700164 ss << s;
Andy Hung06f3aba2019-12-03 16:36:42 -0800165 ll -= l;
166 }
167 if (ll > 0) {
Andy Hung0f7ad8c2020-01-03 13:24:34 -0800168 ss << "Prior audioserver state:\n";
Andy Hung06f3aba2019-12-03 16:36:42 -0800169 --ll;
170 }
171 if (ll > 0) {
Andy Hung709b91e2020-04-04 14:23:36 -0700172 auto [s, l] = mPreviousAnalyticsState->dump(ll, sinceNs, prefix);
Andy Hungb744faf2020-04-09 13:09:26 -0700173 ss << s;
Andy Hung06f3aba2019-12-03 16:36:42 -0800174 ll -= l;
175 }
176 return { ss.str(), lines - ll };
177}
178
Andy Hung0f7ad8c2020-01-03 13:24:34 -0800179void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
180{
181 auto actions = mActions.getActionsForItem(item); // internally locked.
182 // Execute actions with no lock held.
183 for (const auto& action : actions) {
184 (*action)(item);
185 }
186}
187
Andy Hungea186fa2020-01-09 18:13:15 -0800188// HELPER METHODS
189
190std::string AudioAnalytics::getThreadFromTrack(const std::string& track) const
191{
192 int32_t threadId_int32{};
193 if (mAnalyticsState->timeMachine().get(
194 track, AMEDIAMETRICS_PROP_THREADID, &threadId_int32) != NO_ERROR) {
195 return {};
196 }
197 return std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD) + std::to_string(threadId_int32);
198}
199
Andy Hungce9b6632020-04-28 20:15:17 -0700200// DeviceUse helper class.
201void AudioAnalytics::DeviceUse::endAudioIntervalGroup(
202 const std::shared_ptr<const android::mediametrics::Item> &item, bool isTrack) const {
203 const std::string& key = item->getKey();
204 const std::string id = key.substr(
205 (isTrack ? sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_TRACK)
206 : sizeof(AMEDIAMETRICS_KEY_PREFIX_AUDIO_THREAD))
207 - 1);
208 // deliver statistics
209 int64_t deviceTimeNs = 0;
210 mAudioAnalytics.mAnalyticsState->timeMachine().get(
211 key, AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs);
212 std::string encoding;
213 mAudioAnalytics.mAnalyticsState->timeMachine().get(
214 key, AMEDIAMETRICS_PROP_ENCODING, &encoding);
215 int32_t frameCount = 0;
216 mAudioAnalytics.mAnalyticsState->timeMachine().get(
217 key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount);
218 int32_t intervalCount = 0;
219 mAudioAnalytics.mAnalyticsState->timeMachine().get(
220 key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount);
221 std::string outputDevices;
222 mAudioAnalytics.mAnalyticsState->timeMachine().get(
223 key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
224 int32_t sampleRate = 0;
225 mAudioAnalytics.mAnalyticsState->timeMachine().get(
226 key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate);
227 int32_t underrun = 0;
228 mAudioAnalytics.mAnalyticsState->timeMachine().get(
229 key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
230
231 // Get connected device name if from bluetooth.
232 bool isBluetooth = false;
233 std::string name;
234 if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
235 isBluetooth = true;
236 mAudioAnalytics.mAnalyticsState->timeMachine().get(
237 "audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &name);
238 }
239
240 // We may have several devices. We only list the first device.
241 // TODO: consider whether we should list all the devices separated by |
242 std::string firstDevice = "unknown";
243 auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(outputDevices);
244 if (devaddrvec.size() != 0) {
245 firstDevice = devaddrvec[0].first;
246 // DO NOT show the address.
247 }
248
249 if (isTrack) {
250 std::string callerName;
251 mAudioAnalytics.mAnalyticsState->timeMachine().get(
252 key, AMEDIAMETRICS_PROP_CALLERNAME, &callerName);
253 std::string contentType;
254 mAudioAnalytics.mAnalyticsState->timeMachine().get(
255 key, AMEDIAMETRICS_PROP_CONTENTTYPE, &contentType);
256 double deviceLatencyMs = 0.;
257 mAudioAnalytics.mAnalyticsState->timeMachine().get(
258 key, AMEDIAMETRICS_PROP_DEVICELATENCYMS, &deviceLatencyMs);
259 double deviceStartupMs = 0.;
260 mAudioAnalytics.mAnalyticsState->timeMachine().get(
261 key, AMEDIAMETRICS_PROP_DEVICESTARTUPMS, &deviceStartupMs);
262 double deviceVolume = 0.;
263 mAudioAnalytics.mAnalyticsState->timeMachine().get(
264 key, AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume);
265 std::string packageName;
266 int64_t versionCode = 0;
267 int32_t uid = -1;
268 mAudioAnalytics.mAnalyticsState->timeMachine().get(
269 key, AMEDIAMETRICS_PROP_ALLOWUID, &uid);
270 if (uid != -1) {
271 std::tie(packageName, versionCode) =
272 MediaMetricsService::getSanitizedPackageNameAndVersionCode(uid);
273 }
274 double playbackPitch = 0.;
275 mAudioAnalytics.mAnalyticsState->timeMachine().get(
276 key, AMEDIAMETRICS_PROP_PLAYBACK_PITCH, &playbackPitch);
277 double playbackSpeed = 0.;
278 mAudioAnalytics.mAnalyticsState->timeMachine().get(
279 key, AMEDIAMETRICS_PROP_PLAYBACK_SPEED, &playbackSpeed);
280 int32_t selectedDeviceId = 0;
281 mAudioAnalytics.mAnalyticsState->timeMachine().get(
282 key, AMEDIAMETRICS_PROP_SELECTEDDEVICEID, &selectedDeviceId);
283
284 std::string usage;
285 mAudioAnalytics.mAnalyticsState->timeMachine().get(
286 key, AMEDIAMETRICS_PROP_USAGE, &usage);
287
288 ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s "
289 "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
290 "sampleRate:%d underrun:%d "
291 "callerName:%s contentType:%s "
292 "deviceLatencyMs:%lf deviceStartupMs:%lf deviceVolume:%lf"
293 "packageName:%s playbackPitch:%lf playbackSpeed:%lf "
294 "selectedDevceId:%d usage:%s",
295 key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(),
296 (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
297 sampleRate, underrun,
298 callerName.c_str(), contentType.c_str(),
299 deviceLatencyMs, deviceStartupMs, deviceVolume,
300 packageName.c_str(), playbackPitch, playbackSpeed,
301 selectedDeviceId, usage.c_str());
302#ifdef STATSD
303 if (mAudioAnalytics.mDeliverStatistics) {
304 (void)android::util::stats_write(
305 android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED
306 /* timestamp, */
307 /* mediaApexVersion, */
308 , firstDevice.c_str()
309 , name.c_str()
310 , deviceTimeNs
311 , encoding.c_str()
312 , frameCount
313 , intervalCount
314 , sampleRate
315 , underrun
316
317 , packageName.c_str()
318 , (float)deviceLatencyMs
319 , (float)deviceStartupMs
320 , (float)deviceVolume
321 , selectedDeviceId
322 , usage.c_str()
323 , contentType.c_str()
324 , callerName.c_str()
325 );
326 }
327#endif
328 } else {
329
330 std::string flags;
331 mAudioAnalytics.mAnalyticsState->timeMachine().get(
332 key, AMEDIAMETRICS_PROP_FLAGS, &flags);
333
334 ALOGD("(key=%s) id:%s endAudioIntervalGroup device:%s name:%s "
335 "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
336 "sampleRate:%d underrun:%d "
337 "flags:%s",
338 key.c_str(), id.c_str(), firstDevice.c_str(), name.c_str(),
339 (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
340 sampleRate, underrun,
341 flags.c_str());
342#ifdef STATSD
343 if (mAudioAnalytics.mDeliverStatistics) {
344 (void)android::util::stats_write(
345 android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED
346 /* timestamp, */
347 /* mediaApexVersion, */
348 , firstDevice.c_str()
349 , name.c_str()
350 , deviceTimeNs
351 , encoding.c_str()
352 , frameCount
353 , intervalCount
354 , sampleRate
355 , underrun
356 );
357 }
358#endif
359 }
360
361 // Report this as needed.
362 if (isBluetooth) {
363 // report this for Bluetooth
364 }
365}
366
367// DeviceConnection helper class.
368void AudioAnalytics::DeviceConnection::a2dpConnected(
369 const std::shared_ptr<const android::mediametrics::Item> &item) {
370 const std::string& key = item->getKey();
371
372 const int64_t connectedAtNs = item->getTimestamp();
373 {
374 std::lock_guard l(mLock);
375 mA2dpTimeConnectedNs = connectedAtNs;
376 ++mA2dpConnectedAttempts;
377 }
378 std::string name;
379 item->get(AMEDIAMETRICS_PROP_NAME, &name);
380 ALOGD("(key=%s) a2dp connected device:%s "
381 "connectedAtNs:%lld",
382 key.c_str(), name.c_str(),
383 (long long)connectedAtNs);
384 // Note - we need to be able to cancel a timed event
385 mAudioAnalytics.mTimedAction.postIn(std::chrono::seconds(5), [this](){ expire(); });
386 // This sets the time we were connected. Now we look for the delta in the future.
387}
388
389void AudioAnalytics::DeviceConnection::createPatch(
390 const std::shared_ptr<const android::mediametrics::Item> &item) {
391 std::lock_guard l(mLock);
392 if (mA2dpTimeConnectedNs == 0) return; // ignore
393 const std::string& key = item->getKey();
394 std::string outputDevices;
395 item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
396 if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH") != std::string::npos) {
397 // TODO compare address
398 const int64_t timeDiff = item->getTimestamp() - mA2dpTimeConnectedNs;
399 ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff);
400 mA2dpTimeConnectedNs = 0; // reset counter.
401 ++mA2dpConnectedSuccesses;
402 }
403}
404
405void AudioAnalytics::DeviceConnection::expire() {
406 std::lock_guard l(mLock);
407 if (mA2dpTimeConnectedNs == 0) return; // ignore
408
409 // An expiration may occur because there is no audio playing.
410 // TODO: disambiguate this case.
411 ALOGD("A2DP device connection expired");
412 ++mA2dpConnectedFailures; // this is not a true failure.
413 mA2dpTimeConnectedNs = 0;
414}
415
Andy Hung06f3aba2019-12-03 16:36:42 -0800416} // namespace android