You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
9.2 KiB
282 lines
9.2 KiB
/*
|
|
* Copyright 2022 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 "audio_utils_MelAggregator"
|
|
|
|
#include <audio_utils/MelAggregator.h>
|
|
#include <audio_utils/power.h>
|
|
#include <cinttypes>
|
|
#include <iterator>
|
|
#include <utils/Log.h>
|
|
|
|
namespace android::audio_utils {
|
|
namespace {
|
|
|
|
/** Min value after which the MEL values are aggregated to CSD. */
|
|
constexpr float kMinCsdRecordToStore = 0.01f;
|
|
|
|
/** Threshold for 100% CSD expressed in Pa^2s. */
|
|
constexpr float kCsdThreshold = 5760.0f; // 1.6f(Pa^2h) * 3600.0f(s);
|
|
|
|
/** Reference energy used for dB calculation in Pa^2. */
|
|
constexpr float kReferenceEnergyPa = 4e-10;
|
|
|
|
/**
|
|
* Checking the intersection of the time intervals of v1 and v2. Each MelRecord v
|
|
* spawns an interval [t1, t2) if and only if:
|
|
* v.timestamp == t1 && v.mels.size() == t2 - t1
|
|
**/
|
|
std::pair<int64_t, int64_t> intersectRegion(const MelRecord& v1, const MelRecord& v2)
|
|
{
|
|
const int64_t maxStart = std::max(v1.timestamp, v2.timestamp);
|
|
const int64_t v1End = v1.timestamp + v1.mels.size();
|
|
const int64_t v2End = v2.timestamp + v2.mels.size();
|
|
const int64_t minEnd = std::min(v1End, v2End);
|
|
return {maxStart, minEnd};
|
|
}
|
|
|
|
float aggregateMels(const float mel1, const float mel2) {
|
|
return audio_utils_power_from_energy(powf(10.f, mel1 / 10.f) + powf(10.f, mel2 / 10.f));
|
|
}
|
|
|
|
float averageMelEnergy(const float mel1,
|
|
const int64_t duration1,
|
|
const float mel2,
|
|
const int64_t duration2) {
|
|
return audio_utils_power_from_energy((powf(10.f, mel1 / 10.f) * duration1
|
|
+ powf(10.f, mel2 / 10.f) * duration2) / (duration1 + duration2));
|
|
}
|
|
|
|
float melToCsd(float mel) {
|
|
float energy = powf(10.f, mel / 10.0f);
|
|
return kReferenceEnergyPa * energy / kCsdThreshold;
|
|
}
|
|
|
|
CsdRecord createRevertedRecord(const CsdRecord& record) {
|
|
return {record.timestamp, record.duration, -record.value, record.averageMel};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int64_t MelAggregator::csdTimeIntervalStored_l()
|
|
{
|
|
return mCsdRecords.rbegin()->second.timestamp + mCsdRecords.rbegin()->second.duration
|
|
- mCsdRecords.begin()->second.timestamp;
|
|
}
|
|
|
|
std::map<int64_t, CsdRecord>::iterator MelAggregator::addNewestCsdRecord_l(int64_t timestamp,
|
|
int64_t duration,
|
|
float csdRecord,
|
|
float averageMel)
|
|
{
|
|
ALOGV("%s: add new csd[%" PRId64 ", %" PRId64 "]=%f for MEL avg %f",
|
|
__func__,
|
|
timestamp,
|
|
duration,
|
|
csdRecord,
|
|
averageMel);
|
|
|
|
mCurrentCsd += csdRecord;
|
|
return mCsdRecords.emplace_hint(mCsdRecords.end(),
|
|
timestamp,
|
|
CsdRecord(timestamp,
|
|
duration,
|
|
csdRecord,
|
|
averageMel));
|
|
}
|
|
|
|
void MelAggregator::removeOldCsdRecords_l(std::vector<CsdRecord>& removeRecords) {
|
|
// Remove older CSD values
|
|
while (!mCsdRecords.empty() && csdTimeIntervalStored_l() > mCsdWindowSeconds) {
|
|
mCurrentCsd -= mCsdRecords.begin()->second.value;
|
|
removeRecords.emplace_back(createRevertedRecord(mCsdRecords.begin()->second));
|
|
mCsdRecords.erase(mCsdRecords.begin());
|
|
}
|
|
}
|
|
|
|
std::vector<CsdRecord> MelAggregator::updateCsdRecords_l()
|
|
{
|
|
std::vector<CsdRecord> newRecords;
|
|
|
|
// only update if we are above threshold
|
|
if (mCurrentMelRecordsCsd < kMinCsdRecordToStore) {
|
|
removeOldCsdRecords_l(newRecords);
|
|
return newRecords;
|
|
}
|
|
|
|
float converted = 0.f;
|
|
float averageMel = 0.f;
|
|
float csdValue = 0.f;
|
|
int64_t duration = 0;
|
|
int64_t timestamp = mMelRecords.begin()->first;
|
|
for (const auto& storedMel: mMelRecords) {
|
|
int melsIdx = 0;
|
|
for (const auto& mel: storedMel.second.mels) {
|
|
averageMel = averageMelEnergy(averageMel, duration, mel, 1.f);
|
|
csdValue += melToCsd(mel);
|
|
++duration;
|
|
if (csdValue >= kMinCsdRecordToStore
|
|
&& mCurrentMelRecordsCsd - converted - csdValue >= kMinCsdRecordToStore) {
|
|
auto it = addNewestCsdRecord_l(timestamp,
|
|
duration,
|
|
csdValue,
|
|
averageMel);
|
|
newRecords.emplace_back(it->second);
|
|
|
|
duration = 0;
|
|
averageMel = 0.f;
|
|
converted += csdValue;
|
|
csdValue = 0.f;
|
|
timestamp = storedMel.first + melsIdx;
|
|
}
|
|
++ melsIdx;
|
|
}
|
|
}
|
|
|
|
if(csdValue > 0) {
|
|
auto it = addNewestCsdRecord_l(timestamp,
|
|
duration,
|
|
csdValue,
|
|
averageMel);
|
|
newRecords.emplace_back(it->second);
|
|
}
|
|
|
|
removeOldCsdRecords_l(newRecords);
|
|
|
|
// reset mel values
|
|
mCurrentMelRecordsCsd = 0.0f;
|
|
mMelRecords.clear();
|
|
|
|
return newRecords;
|
|
}
|
|
|
|
std::vector<CsdRecord> MelAggregator::aggregateAndAddNewMelRecord(const MelRecord& mel)
|
|
{
|
|
std::lock_guard _l(mLock);
|
|
return aggregateAndAddNewMelRecord_l(mel);
|
|
}
|
|
|
|
std::vector<CsdRecord> MelAggregator::aggregateAndAddNewMelRecord_l(const MelRecord& mel)
|
|
{
|
|
for (const auto& m : mel.mels) {
|
|
mCurrentMelRecordsCsd += melToCsd(m);
|
|
}
|
|
ALOGV("%s: current mel values CSD %f", __func__, mCurrentMelRecordsCsd);
|
|
|
|
auto mergeIt = mMelRecords.lower_bound(mel.timestamp);
|
|
|
|
if (mergeIt != mMelRecords.begin()) {
|
|
auto prevMergeIt = std::prev(mergeIt);
|
|
if (prevMergeIt->second.overlapsEnd(mel)) {
|
|
mergeIt = prevMergeIt;
|
|
}
|
|
}
|
|
|
|
int64_t newTimestamp = mel.timestamp;
|
|
std::vector<float> newMels = mel.mels;
|
|
auto mergeStart = mergeIt;
|
|
int overlapStart = 0;
|
|
while(mergeIt != mMelRecords.end()) {
|
|
const auto& [melRecordStart, melRecord] = *mergeIt;
|
|
const auto [regionStart, regionEnd] = intersectRegion(melRecord, mel);
|
|
if (regionStart >= regionEnd) {
|
|
// no intersection
|
|
break;
|
|
}
|
|
|
|
if (melRecordStart < regionStart) {
|
|
newTimestamp = melRecordStart;
|
|
overlapStart = regionStart - melRecordStart;
|
|
newMels.insert(newMels.begin(), melRecord.mels.begin(),
|
|
melRecord.mels.begin() + overlapStart);
|
|
}
|
|
|
|
for (int64_t aggregateTime = regionStart; aggregateTime < regionEnd; ++aggregateTime) {
|
|
const int offsetStored = aggregateTime - melRecordStart;
|
|
const int offsetNew = aggregateTime - mel.timestamp;
|
|
newMels[overlapStart + offsetNew] =
|
|
aggregateMels(melRecord.mels[offsetStored], mel.mels[offsetNew]);
|
|
}
|
|
|
|
const int64_t mergeEndTime = melRecordStart + melRecord.mels.size();
|
|
if (mergeEndTime > regionEnd) {
|
|
newMels.insert(newMels.end(),
|
|
melRecord.mels.end() - mergeEndTime + regionEnd,
|
|
melRecord.mels.end());
|
|
}
|
|
|
|
++mergeIt;
|
|
}
|
|
|
|
auto hint = mergeIt;
|
|
if (mergeStart != mergeIt) {
|
|
hint = mMelRecords.erase(mergeStart, mergeIt);
|
|
}
|
|
|
|
mMelRecords.emplace_hint(hint,
|
|
newTimestamp,
|
|
MelRecord(mel.portId, newMels, newTimestamp));
|
|
|
|
return updateCsdRecords_l();
|
|
}
|
|
|
|
void MelAggregator::reset(float newCsd, const std::vector<CsdRecord>& newRecords)
|
|
{
|
|
std::lock_guard _l(mLock);
|
|
mCsdRecords.clear();
|
|
mMelRecords.clear();
|
|
|
|
mCurrentCsd = newCsd;
|
|
for (const auto& record : newRecords) {
|
|
mCsdRecords.emplace_hint(mCsdRecords.end(), record.timestamp, record);
|
|
}
|
|
}
|
|
|
|
size_t MelAggregator::getCachedMelRecordsSize() const
|
|
{
|
|
std::lock_guard _l(mLock);
|
|
return mMelRecords.size();
|
|
}
|
|
|
|
void MelAggregator::foreachCachedMel(const std::function<void(const MelRecord&)>& f) const
|
|
{
|
|
std::lock_guard _l(mLock);
|
|
for (const auto &melRecord : mMelRecords) {
|
|
f(melRecord.second);
|
|
}
|
|
}
|
|
|
|
float MelAggregator::getCsd() {
|
|
std::lock_guard _l(mLock);
|
|
return mCurrentCsd;
|
|
}
|
|
|
|
size_t MelAggregator::getCsdRecordsSize() const {
|
|
std::lock_guard _l(mLock);
|
|
return mCsdRecords.size();
|
|
}
|
|
|
|
void MelAggregator::foreachCsd(const std::function<void(const CsdRecord&)>& f) const
|
|
{
|
|
std::lock_guard _l(mLock);
|
|
for (const auto &csdRecord : mCsdRecords) {
|
|
f(csdRecord.second);
|
|
}
|
|
}
|
|
|
|
} // namespace android::audio_utils
|