blob: 480148658fe32a75d31b5f41bb96164cec3b9130 [file] [log] [blame]
/*
* 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.
*/
#pragma once
#include <array>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <android-base/thread_annotations.h>
#include <audio_utils/BiquadFilter.h>
#include <system/audio.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
namespace android::audio_utils {
/**
* Class used to capture the MEL (momentary exposure levels) values as defined
* by IEC62368-1 3rd edition. MELs are computed for each second.
*/
class MelProcessor : public RefBase {
public:
static constexpr int kCascadeBiquadNumber = 3;
/** Should represent the minimal value after which a 1% CSD change can occur. */
static constexpr int32_t kMaxMelValues = 3;
/**
* An interface through which the MelProcessor client will be notified about
* important events.
*/
class MelCallback : public virtual RefBase {
public:
~MelCallback() override = default;
/**
* Called with a time-continuous vector of computed MEL values
*
* \param mels contains MELs (one per second) with values above RS1.
* \param offset the offset in mels for new MEL data.
* \param length the number of valid MEL values in the vector starting at offset. The
* maximum number of elements in mels is defined in the MelProcessor
* constructor.
* \param deviceId id of device where the samples were processed
*/
virtual void onNewMelValues(const std::vector<float>& mels,
size_t offset,
size_t length,
audio_port_handle_t deviceId) const = 0;
/**
* Called when the momentary exposure exceeds the RS2 upper bound.
*
* Note: RS2 is configurable vie MelProcessor#setOutputRs2UpperBound.
*/
virtual void onMomentaryExposure(float currentMel, audio_port_handle_t deviceId) const = 0;
};
/**
* \brief Creates a MelProcessor object.
*
* \param sampleRate sample rate of the audio data.
* \param channelCount channel count of the audio data.
* \param format format of the audio data. It must be allowed by
* audio_utils_is_compute_mel_format_supported()
* else the constructor will abort.
* \param callback reports back the new mel values.
* \param deviceId the device ID for the MEL callbacks
* \param rs2Value initial RS2 upper bound to use
* \param maxMelsCallback the number of max elements a callback can have.
*/
MelProcessor(uint32_t sampleRate,
uint32_t channelCount,
audio_format_t format,
const sp<MelCallback>& callback,
audio_port_handle_t deviceId,
float rs2Value,
size_t maxMelsCallback = kMaxMelValues);
/**
* Sets the output RS2 upper bound for momentary exposure warnings. Default value
* is 100dBA as specified in IEC62368-1 3rd edition. Must not be higher than
* 100dBA and not lower than 80dBA.
*
* \param rs2Value to use for momentary exposure
* \return NO_ERROR if rs2Value is between 80dBA amd 100dBA or BAD_VALUE
* otherwise
*/
status_t setOutputRs2UpperBound(float rs2Value);
/** Returns the RS2 upper bound used for momentary exposures. */
float getOutputRs2UpperBound() const;
/** Updates the device id. */
void setDeviceId(audio_port_handle_t deviceId);
/** Returns the device id. */
audio_port_handle_t getDeviceId();
/** Update the format to use for the input frames to process. */
void updateAudioFormat(uint32_t sampleRate, uint32_t channelCount, audio_format_t newFormat);
/**
* \brief Computes the MEL values for the given buffer and triggers a
* callback with time-continuous MEL values when: MEL buffer is full or if
* there is a discontinue in MEL calculation (e.g.: MEL is under RS1)
*
* \param buffer pointer to the audio data buffer.
* \param bytes buffer size in bytes.
*
* \return the number of bytes that were processed. Note: the method will
* output 0 if the processor is paused or the sample rate is not supported.
*/
int32_t process(const void* buffer, size_t bytes);
/**
* Pauses the processing of MEL values. Process calls after this will be
* ignored until resume.
*/
void pause();
/** Resumes the processing of MEL values. */
void resume();
/**
* Sets the given attenuation for the MEL calculation. This can be used when
* the audio framework is operating in absolute volume mode.
*
* @param attenuationDB attenuation to use on computed MEL values
*/
void setAttenuation(float attenuationDB);
void onLastStrongRef(const void* id) override;
private:
/** Struct to store the possible callback data. */
struct MelCallbackData {
// used for momentaryExposure callback
float mMel = 0.f;
// used for newMelValues callback
std::vector<float> mMels = std::vector<float>(kMaxMelValues);
// represents the number of valid MEL values in mMels
size_t mMelsSize = 0;
// port of deviceId for this callback
audio_port_handle_t mPort = AUDIO_PORT_HANDLE_NONE;
};
// class used to asynchronously execute all MelProcessor callbacks
class MelWorker {
public:
static constexpr int kRingBufferSize = 32;
MelWorker(std::string threadName, const wp<MelCallback>& callback)
: mCallback(callback),
mThreadName(std::move(threadName)),
mCallbackRingBuffer(kRingBufferSize) {};
void run();
// blocks until the MelWorker thread is stopped
void stop();
// callback methods for new MEL values
void momentaryExposure(float mel, audio_port_handle_t port);
void newMelValues(const std::vector<float>& mels,
size_t melsSize,
audio_port_handle_t port);
static void incRingBufferIndex(std::atomic_size_t& idx);
bool ringBufferIsFull() const;
const wp<MelCallback> mCallback;
const std::string mThreadName;
std::vector<MelCallbackData> mCallbackRingBuffer GUARDED_BY(mCondVarMutex);
std::atomic_size_t mRbReadPtr = 0;
std::atomic_size_t mRbWritePtr = 0;
std::thread mThread;
std::condition_variable mCondVar;
std::mutex mCondVarMutex;
bool mStopRequested GUARDED_BY(mCondVarMutex) = false;
};
std::string pointerString() const;
void createBiquads_l() REQUIRES(mLock);
bool isSampleRateSupported_l() const REQUIRES(mLock);
void applyAWeight_l(const void* buffer, size_t frames) REQUIRES(mLock);
float getCombinedChannelEnergy_l() REQUIRES(mLock);
void addMelValue_l(float mel) REQUIRES(mLock);
const wp<MelCallback> mCallback; // callback to notify about new MEL values
// and momentary exposure warning
// does not own the callback, must outlive
MelWorker mMelWorker; // spawns thread for asynchronous callbacks,
// worker is thread-safe
mutable std::mutex mLock; // monitor mutex
// audio data sample rate
uint32_t mSampleRate GUARDED_BY(mLock);
// number of audio frames per MEL value
size_t mFramesPerMelValue GUARDED_BY(mLock);
// audio data channel count
uint32_t mChannelCount GUARDED_BY(mLock);
// audio data format
audio_format_t mFormat GUARDED_BY(mLock);
// contains the A-weighted input samples to be processed
std::vector<float> mAWeightSamples GUARDED_BY(mLock);
// contains the input samples converted to float
std::vector<float> mFloatSamples GUARDED_BY(mLock);
// local energy accumulation
std::vector<float> mCurrentChannelEnergy GUARDED_BY(mLock);
// accumulated MEL values
std::vector<float> mMelValues GUARDED_BY(mLock);
// current index to store the MEL values
uint32_t mCurrentIndex GUARDED_BY(mLock);
using DefaultBiquadFilter = BiquadFilter<float, true, details::DefaultBiquadConstOptions>;
// Biquads used for the A-weighting
std::array<std::unique_ptr<DefaultBiquadFilter>, kCascadeBiquadNumber>
mCascadedBiquads GUARDED_BY(mLock);
std::atomic<float> mAttenuationDB = 0.f;
// device id used for the callbacks
std::atomic<audio_port_handle_t> mDeviceId;
// Value used for momentary exposure
std::atomic<float> mRs2UpperBound;
// number of samples in the energy
std::atomic_size_t mCurrentSamples;
std::atomic_bool mPaused;
};
} // namespace android::audio_utils