|  | /* | 
|  | * Copyright (C) 2020 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | //#define LOG_NDEBUG 0 | 
|  | #define LOG_TAG "AudioPowerUsage" | 
|  | #include <utils/Log.h> | 
|  |  | 
|  | #include "AudioAnalytics.h" | 
|  | #include "MediaMetricsService.h" | 
|  | #include "StringUtils.h" | 
|  | #include <map> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <audio_utils/clock.h> | 
|  | #include <cutils/properties.h> | 
|  | #include <statslog.h> | 
|  | #include <sys/timerfd.h> | 
|  | #include <system/audio.h> | 
|  |  | 
|  | // property to disable audio power use metrics feature, default is enabled | 
|  | #define PROP_AUDIO_METRICS_DISABLED "persist.media.audio_metrics.power_usage_disabled" | 
|  | #define AUDIO_METRICS_DISABLED_DEFAULT (false) | 
|  |  | 
|  | // property to set how long to send audio power use metrics data to statsd, default is 24hrs | 
|  | #define PROP_AUDIO_METRICS_INTERVAL_HR "persist.media.audio_metrics.interval_hr" | 
|  | #define INTERVAL_HR_DEFAULT (24) | 
|  |  | 
|  | // for Audio Power Usage Metrics | 
|  | #define AUDIO_POWER_USAGE_KEY_AUDIO_USAGE     "audio.power.usage" | 
|  |  | 
|  | #define AUDIO_POWER_USAGE_PROP_DEVICE         "device"     // int32 | 
|  | #define AUDIO_POWER_USAGE_PROP_DURATION_NS    "durationNs" // int64 | 
|  | #define AUDIO_POWER_USAGE_PROP_TYPE           "type"       // int32 | 
|  | #define AUDIO_POWER_USAGE_PROP_VOLUME         "volume"     // double | 
|  |  | 
|  | namespace android::mediametrics { | 
|  |  | 
|  | /* static */ | 
|  | bool AudioPowerUsage::typeFromString(const std::string& type_string, int32_t& type) { | 
|  | static std::map<std::string, int32_t> typeTable = { | 
|  | { "AUDIO_STREAM_VOICE_CALL",          VOIP_CALL_TYPE }, | 
|  | { "AUDIO_STREAM_SYSTEM",              MEDIA_TYPE }, | 
|  | { "AUDIO_STREAM_RING",                RINGTONE_NOTIFICATION_TYPE }, | 
|  | { "AUDIO_STREAM_MUSIC",               MEDIA_TYPE }, | 
|  | { "AUDIO_STREAM_ALARM",               ALARM_TYPE }, | 
|  | { "AUDIO_STREAM_NOTIFICATION",        RINGTONE_NOTIFICATION_TYPE }, | 
|  |  | 
|  | { "AUDIO_CONTENT_TYPE_SPEECH",        VOIP_CALL_TYPE }, | 
|  | { "AUDIO_CONTENT_TYPE_MUSIC",         MEDIA_TYPE }, | 
|  | { "AUDIO_CONTENT_TYPE_MOVIE",         MEDIA_TYPE }, | 
|  | { "AUDIO_CONTENT_TYPE_SONIFICATION",  RINGTONE_NOTIFICATION_TYPE }, | 
|  |  | 
|  | { "AUDIO_USAGE_MEDIA",                MEDIA_TYPE }, | 
|  | { "AUDIO_USAGE_VOICE_COMMUNICATION",  VOIP_CALL_TYPE }, | 
|  | { "AUDIO_USAGE_ALARM",                ALARM_TYPE }, | 
|  | { "AUDIO_USAGE_NOTIFICATION",         RINGTONE_NOTIFICATION_TYPE }, | 
|  |  | 
|  | { "AUDIO_SOURCE_CAMCORDER",           CAMCORDER_TYPE }, | 
|  | { "AUDIO_SOURCE_VOICE_COMMUNICATION", VOIP_CALL_TYPE }, | 
|  | { "AUDIO_SOURCE_DEFAULT",             RECORD_TYPE }, | 
|  | { "AUDIO_SOURCE_MIC",                 RECORD_TYPE }, | 
|  | { "AUDIO_SOURCE_UNPROCESSED",         RECORD_TYPE }, | 
|  | { "AUDIO_SOURCE_VOICE_RECOGNITION",   RECORD_TYPE }, | 
|  | }; | 
|  |  | 
|  | auto it = typeTable.find(type_string); | 
|  | if (it == typeTable.end()) { | 
|  | type = UNKNOWN_TYPE; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | type = it->second; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* static */ | 
|  | bool AudioPowerUsage::deviceFromString(const std::string& device_string, int32_t& device) { | 
|  | static std::map<std::string, int32_t> deviceTable = { | 
|  | { "AUDIO_DEVICE_OUT_EARPIECE",             OUTPUT_EARPIECE }, | 
|  | { "AUDIO_DEVICE_OUT_SPEAKER_SAFE",         OUTPUT_SPEAKER_SAFE }, | 
|  | { "AUDIO_DEVICE_OUT_SPEAKER",              OUTPUT_SPEAKER }, | 
|  | { "AUDIO_DEVICE_OUT_WIRED_HEADSET",        OUTPUT_WIRED_HEADSET }, | 
|  | { "AUDIO_DEVICE_OUT_WIRED_HEADPHONE",      OUTPUT_WIRED_HEADSET }, | 
|  | { "AUDIO_DEVICE_OUT_BLUETOOTH_SCO",        OUTPUT_BLUETOOTH_SCO }, | 
|  | { "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP",       OUTPUT_BLUETOOTH_A2DP }, | 
|  | { "AUDIO_DEVICE_OUT_USB_HEADSET",          OUTPUT_USB_HEADSET }, | 
|  | { "AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET", OUTPUT_BLUETOOTH_SCO }, | 
|  |  | 
|  | { "AUDIO_DEVICE_IN_BUILTIN_MIC",           INPUT_BUILTIN_MIC }, | 
|  | { "AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET", INPUT_BLUETOOTH_SCO }, | 
|  | { "AUDIO_DEVICE_IN_WIRED_HEADSET",         INPUT_WIRED_HEADSET_MIC }, | 
|  | { "AUDIO_DEVICE_IN_USB_DEVICE",            INPUT_USB_HEADSET_MIC }, | 
|  | { "AUDIO_DEVICE_IN_BACK_MIC",              INPUT_BUILTIN_BACK_MIC }, | 
|  | }; | 
|  |  | 
|  | auto it = deviceTable.find(device_string); | 
|  | if (it == deviceTable.end()) { | 
|  | device = 0; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | device = it->second; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int32_t AudioPowerUsage::deviceFromStringPairs(const std::string& device_strings) { | 
|  | int32_t deviceMask = 0; | 
|  | const auto devaddrvec = stringutils::getDeviceAddressPairs(device_strings); | 
|  | for (const auto &[device, addr] : devaddrvec) { | 
|  | int32_t combo_device = 0; | 
|  | deviceFromString(device, combo_device); | 
|  | deviceMask |= combo_device; | 
|  | } | 
|  | return deviceMask; | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::sendItem(const std::shared_ptr<const mediametrics::Item>& item) const | 
|  | { | 
|  | int32_t type; | 
|  | if (!item->getInt32(AUDIO_POWER_USAGE_PROP_TYPE, &type)) return; | 
|  |  | 
|  | int32_t audio_device; | 
|  | if (!item->getInt32(AUDIO_POWER_USAGE_PROP_DEVICE, &audio_device)) return; | 
|  |  | 
|  | int64_t duration_ns; | 
|  | if (!item->getInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, &duration_ns)) return; | 
|  |  | 
|  | double volume; | 
|  | if (!item->getDouble(AUDIO_POWER_USAGE_PROP_VOLUME, &volume)) return; | 
|  |  | 
|  | const int32_t duration_secs = (int32_t)(duration_ns / NANOS_PER_SECOND); | 
|  | const float average_volume = (float)volume; | 
|  | const int result = android::util::stats_write(android::util::AUDIO_POWER_USAGE_DATA_REPORTED, | 
|  | audio_device, | 
|  | duration_secs, | 
|  | average_volume, | 
|  | type); | 
|  |  | 
|  | std::stringstream log; | 
|  | log << "result:" << result << " {" | 
|  | << " mediametrics_audio_power_usage_data_reported:" | 
|  | << android::util::AUDIO_POWER_USAGE_DATA_REPORTED | 
|  | << " audio_device:" << audio_device | 
|  | << " duration_secs:" << duration_secs | 
|  | << " average_volume:" << average_volume | 
|  | << " type:" << type | 
|  | << " }"; | 
|  | mStatsdLog->log(android::util::AUDIO_POWER_USAGE_DATA_REPORTED, log.str()); | 
|  | } | 
|  |  | 
|  | bool AudioPowerUsage::saveAsItem_l( | 
|  | int32_t device, int64_t duration_ns, int32_t type, double average_vol) | 
|  | { | 
|  | ALOGV("%s: (%#x, %d, %lld, %f)", __func__, device, type, | 
|  | (long long)duration_ns, average_vol ); | 
|  | if (duration_ns == 0) { | 
|  | return true; // skip duration 0 usage | 
|  | } | 
|  | if (device == 0) { | 
|  | return true; //ignore unknown device | 
|  | } | 
|  |  | 
|  | for (const auto& item : mItems) { | 
|  | int32_t item_type = 0, item_device = 0; | 
|  | double item_volume = 0.; | 
|  | int64_t item_duration_ns = 0; | 
|  | item->getInt32(AUDIO_POWER_USAGE_PROP_DEVICE, &item_device); | 
|  | item->getInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, &item_duration_ns); | 
|  | item->getInt32(AUDIO_POWER_USAGE_PROP_TYPE, &item_type); | 
|  | item->getDouble(AUDIO_POWER_USAGE_PROP_VOLUME, &item_volume); | 
|  |  | 
|  | // aggregate by device and type | 
|  | if (item_device == device && item_type == type) { | 
|  | int64_t final_duration_ns = item_duration_ns + duration_ns; | 
|  | double final_volume = (device & INPUT_DEVICE_BIT) ? 1.0: | 
|  | ((item_volume * (double)item_duration_ns + | 
|  | average_vol * (double)duration_ns) / (double)final_duration_ns); | 
|  |  | 
|  | item->setInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, final_duration_ns); | 
|  | item->setDouble(AUDIO_POWER_USAGE_PROP_VOLUME, final_volume); | 
|  | item->setTimestamp(systemTime(SYSTEM_TIME_REALTIME)); | 
|  |  | 
|  | ALOGV("%s: update (%#x, %d, %lld, %f) --> (%lld, %f)", __func__, | 
|  | device, type, | 
|  | (long long)item_duration_ns, item_volume, | 
|  | (long long)final_duration_ns, final_volume); | 
|  |  | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | auto sitem = std::make_shared<mediametrics::Item>(AUDIO_POWER_USAGE_KEY_AUDIO_USAGE); | 
|  | sitem->setTimestamp(systemTime(SYSTEM_TIME_REALTIME)); | 
|  | sitem->setInt32(AUDIO_POWER_USAGE_PROP_DEVICE, device); | 
|  | sitem->setInt64(AUDIO_POWER_USAGE_PROP_DURATION_NS, duration_ns); | 
|  | sitem->setInt32(AUDIO_POWER_USAGE_PROP_TYPE, type); | 
|  | sitem->setDouble(AUDIO_POWER_USAGE_PROP_VOLUME, average_vol); | 
|  | mItems.emplace_back(sitem); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool AudioPowerUsage::saveAsItems_l( | 
|  | int32_t device, int64_t duration_ns, int32_t type, double average_vol) | 
|  | { | 
|  | ALOGV("%s: (%#x, %d, %lld, %f)", __func__, device, type, | 
|  | (long long)duration_ns, average_vol ); | 
|  | if (duration_ns == 0) { | 
|  | return true; // skip duration 0 usage | 
|  | } | 
|  | if (device == 0) { | 
|  | return true; //ignore unknown device | 
|  | } | 
|  |  | 
|  | bool ret = false; | 
|  | const int32_t input_bit = device & INPUT_DEVICE_BIT; | 
|  | int32_t device_bits = device ^ input_bit; | 
|  |  | 
|  | while (device_bits != 0) { | 
|  | int32_t tmp_device = device_bits & -device_bits; // get lowest bit | 
|  | device_bits ^= tmp_device;  // clear lowest bit | 
|  | tmp_device |= input_bit;    // restore input bit | 
|  | ret = saveAsItem_l(tmp_device, duration_ns, type, average_vol); | 
|  |  | 
|  | ALOGV("%s: device %#x recorded, remaining device_bits = %#x", __func__, | 
|  | tmp_device, device_bits); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::checkTrackRecord( | 
|  | const std::shared_ptr<const mediametrics::Item>& item, bool isTrack) | 
|  | { | 
|  | const std::string key = item->getKey(); | 
|  |  | 
|  | int64_t deviceTimeNs = 0; | 
|  | if (!item->getInt64(AMEDIAMETRICS_PROP_DEVICETIMENS, &deviceTimeNs)) { | 
|  | return; | 
|  | } | 
|  | double deviceVolume = 1.; | 
|  | if (isTrack && !item->getDouble(AMEDIAMETRICS_PROP_DEVICEVOLUME, &deviceVolume)) { | 
|  | return; | 
|  | } | 
|  | int32_t type = 0; | 
|  | std::string type_string; | 
|  | if ((isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_STREAMTYPE, &type_string) == OK) || | 
|  | (!isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_SOURCE, &type_string) == OK)) { | 
|  | typeFromString(type_string, type); | 
|  |  | 
|  | if (isTrack && type == UNKNOWN_TYPE && | 
|  | mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_USAGE, &type_string) == OK) { | 
|  | typeFromString(type_string, type); | 
|  | } | 
|  | if (isTrack && type == UNKNOWN_TYPE && | 
|  | mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_CONTENTTYPE, &type_string) == OK) { | 
|  | typeFromString(type_string, type); | 
|  | } | 
|  | ALOGV("type = %s => %d", type_string.c_str(), type); | 
|  | } | 
|  |  | 
|  | int32_t device = 0; | 
|  | std::string device_strings; | 
|  | if ((isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &device_strings) == OK) || | 
|  | (!isTrack && mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_INPUTDEVICES, &device_strings) == OK)) { | 
|  |  | 
|  | device = deviceFromStringPairs(device_strings); | 
|  | ALOGV("device = %s => %d", device_strings.c_str(), device); | 
|  | } | 
|  | std::lock_guard l(mLock); | 
|  | saveAsItems_l(device, deviceTimeNs, type, deviceVolume); | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::checkMode(const std::shared_ptr<const mediametrics::Item>& item) | 
|  | { | 
|  | std::string mode; | 
|  | if (!item->getString(AMEDIAMETRICS_PROP_AUDIOMODE, &mode)) return; | 
|  |  | 
|  | std::lock_guard l(mLock); | 
|  | if (mode == mMode) return;  // no change in mode. | 
|  |  | 
|  | if (mMode == "AUDIO_MODE_IN_CALL") { // leaving call mode | 
|  | const int64_t endCallNs = item->getTimestamp(); | 
|  | const int64_t durationNs = endCallNs - mDeviceTimeNs; | 
|  | if (durationNs > 0) { | 
|  | mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) + | 
|  | mVoiceVolume * double(endCallNs - mVolumeTimeNs)) / (double)durationNs; | 
|  | saveAsItems_l(mPrimaryDevice, durationNs, VOICE_CALL_TYPE, mDeviceVolume); | 
|  | } | 
|  | } else if (mode == "AUDIO_MODE_IN_CALL") { // entering call mode | 
|  | mStartCallNs = item->getTimestamp(); // advisory only | 
|  |  | 
|  | mDeviceVolume = 0; | 
|  | mVolumeTimeNs = mStartCallNs; | 
|  | mDeviceTimeNs = mStartCallNs; | 
|  | } | 
|  | ALOGV("%s: new mode:%s  old mode:%s", __func__, mode.c_str(), mMode.c_str()); | 
|  | mMode = mode; | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::checkVoiceVolume(const std::shared_ptr<const mediametrics::Item>& item) | 
|  | { | 
|  | double voiceVolume = 0.; | 
|  | if (!item->getDouble(AMEDIAMETRICS_PROP_VOICEVOLUME, &voiceVolume)) return; | 
|  |  | 
|  | std::lock_guard l(mLock); | 
|  | if (voiceVolume == mVoiceVolume) return;  // no change in volume | 
|  |  | 
|  | // we only track average device volume when we are in-call | 
|  | if (mMode == "AUDIO_MODE_IN_CALL") { | 
|  | const int64_t timeNs = item->getTimestamp(); | 
|  | const int64_t durationNs = timeNs - mDeviceTimeNs; | 
|  | if (durationNs > 0) { | 
|  | mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) + | 
|  | mVoiceVolume * double(timeNs - mVolumeTimeNs)) / (double)durationNs; | 
|  | mVolumeTimeNs = timeNs; | 
|  | } | 
|  | } | 
|  | ALOGV("%s: new voice volume:%lf  old voice volume:%lf", __func__, voiceVolume, mVoiceVolume); | 
|  | mVoiceVolume = voiceVolume; | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::checkCreatePatch(const std::shared_ptr<const mediametrics::Item>& item) | 
|  | { | 
|  | std::string outputDevices; | 
|  | if (!item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices)) return; | 
|  |  | 
|  | const std::string& key = item->getKey(); | 
|  | std::string flags; | 
|  | if (mAudioAnalytics->mAnalyticsState->timeMachine().get( | 
|  | key, AMEDIAMETRICS_PROP_FLAGS, &flags) != OK) return; | 
|  |  | 
|  | if (flags.find("AUDIO_OUTPUT_FLAG_PRIMARY") == std::string::npos) return; | 
|  |  | 
|  | const int32_t device = deviceFromStringPairs(outputDevices); | 
|  |  | 
|  | std::lock_guard l(mLock); | 
|  | if (mPrimaryDevice == device) return; | 
|  |  | 
|  | if (mMode == "AUDIO_MODE_IN_CALL") { | 
|  | // Save statistics | 
|  | const int64_t endDeviceNs = item->getTimestamp(); | 
|  | const int64_t durationNs = endDeviceNs - mDeviceTimeNs; | 
|  | if (durationNs > 0) { | 
|  | mDeviceVolume = (mDeviceVolume * double(mVolumeTimeNs - mDeviceTimeNs) + | 
|  | mVoiceVolume * double(endDeviceNs - mVolumeTimeNs)) / (double)durationNs; | 
|  | saveAsItems_l(mPrimaryDevice, durationNs, VOICE_CALL_TYPE, mDeviceVolume); | 
|  | } | 
|  | // reset statistics | 
|  | mDeviceVolume = 0; | 
|  | mDeviceTimeNs = endDeviceNs; | 
|  | mVolumeTimeNs = endDeviceNs; | 
|  | } | 
|  | ALOGV("%s: new primary device:%#x  old primary device:%#x", __func__, device, mPrimaryDevice); | 
|  | mPrimaryDevice = device; | 
|  | } | 
|  |  | 
|  | AudioPowerUsage::AudioPowerUsage( | 
|  | AudioAnalytics *audioAnalytics, const std::shared_ptr<StatsdLog>& statsdLog) | 
|  | : mAudioAnalytics(audioAnalytics) | 
|  | , mStatsdLog(statsdLog) | 
|  | , mDisabled(property_get_bool(PROP_AUDIO_METRICS_DISABLED, AUDIO_METRICS_DISABLED_DEFAULT)) | 
|  | , mIntervalHours(property_get_int32(PROP_AUDIO_METRICS_INTERVAL_HR, INTERVAL_HR_DEFAULT)) | 
|  | { | 
|  | ALOGD("%s", __func__); | 
|  | ALOGI_IF(mDisabled, "AudioPowerUsage is disabled."); | 
|  | collect(); // send items | 
|  | } | 
|  |  | 
|  | AudioPowerUsage::~AudioPowerUsage() | 
|  | { | 
|  | ALOGD("%s", __func__); | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::clear() | 
|  | { | 
|  | std::lock_guard _l(mLock); | 
|  | mItems.clear(); | 
|  | } | 
|  |  | 
|  | void AudioPowerUsage::collect() | 
|  | { | 
|  | std::lock_guard _l(mLock); | 
|  | for (const auto &item : mItems) { | 
|  | sendItem(item); | 
|  | } | 
|  | mItems.clear(); | 
|  | mAudioAnalytics->mTimedAction.postIn( | 
|  | mIntervalHours <= 0 ? std::chrono::seconds(5) : std::chrono::hours(mIntervalHours), | 
|  | [this](){ collect(); }); | 
|  | } | 
|  |  | 
|  | std::pair<std::string, int32_t> AudioPowerUsage::dump(int limit) const { | 
|  | if (limit <= 2) { | 
|  | return {{}, 0}; | 
|  | } | 
|  | std::lock_guard _l(mLock); | 
|  | if (mDisabled) { | 
|  | return {"AudioPowerUsage disabled\n", 1}; | 
|  | } | 
|  | if (mItems.empty()) { | 
|  | return {"AudioPowerUsage empty\n", 1}; | 
|  | } | 
|  |  | 
|  | int slot = 1; | 
|  | std::stringstream ss; | 
|  | ss << "AudioPowerUsage:\n"; | 
|  | for (const auto &item : mItems) { | 
|  | if (slot >= limit - 1) { | 
|  | ss << "-- AudioPowerUsage may be truncated!\n"; | 
|  | ++slot; | 
|  | break; | 
|  | } | 
|  | ss << " " << slot << " " << item->toString() << "\n"; | 
|  | slot++; | 
|  | } | 
|  | return { ss.str(), slot }; | 
|  | } | 
|  |  | 
|  | } // namespace android::mediametrics |