/* * 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 #include #include #include #include #include #include #include #include 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& mels, size_t offset, size_t length, audio_port_handle_t deviceId, bool attenuated) 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& 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) EXCLUDES(mLock); /** * \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(); /** Signals to drain the remaining mel data. Does not wait. */ void drain(); /** Signals to drain the remaining mel data and waits for completion. * If more data is being delivered, wait time may be long. */ void drainAndWait(); /** * 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 mMels = std::vector(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& callback) : mCallback(callback), mThreadName(std::move(threadName)), mCallbackRingBuffer(kRingBufferSize) {}; void run() EXCLUDES(mCondVarMutex); // blocks until the MelWorker thread is stopped void stop() EXCLUDES(mCondVarMutex); // Signals the MelWorker to wake up to process // any remaining queued data. Like notify() but with lock held. void drain() EXCLUDES(mCondVarMutex); // callback methods for new MEL values void momentaryExposure(float mel, audio_port_handle_t port); void newMelValues(const std::vector& mels, size_t melsSize, audio_port_handle_t port); std::string getThreadName() const { return mThreadName; } void notify() { mCondVar.notify_one(); } bool ringBufferIsEmpty() const { return mRbReadPtr == mRbWritePtr; } private: static size_t nextRingBufferIndex(size_t idx) { return idx >= kRingBufferSize - 1 ? 0 : idx + 1; } bool ringBufferIsFull() const; const wp mCallback; const std::string mThreadName; std::mutex mCondVarMutex; std::condition_variable mCondVar; // mRbReadPtr, mRbWritePtr, mCallbackRingBuffer form a lock free queue. std::vector mCallbackRingBuffer; // reader / writer on different indices // reader updated only, aligned to cache line. alignas(64 /* std::hardware_destructive_interference_size */) std::atomic_size_t mRbReadPtr = 0; // writer updated only, aligned to cache line. alignas(64 /* std::hardware_destructive_interference_size */) std::atomic_size_t mRbWritePtr = 0; bool mStopRequested GUARDED_BY(mCondVarMutex) = false; std::thread mThread; }; 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 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); // number of samples in the energy size_t mCurrentSamples GUARDED_BY(mLock) = 0; // contains the A-weighted input samples to be processed std::vector mAWeightSamples GUARDED_BY(mLock); // contains the input samples converted to float std::vector mFloatSamples GUARDED_BY(mLock); // local energy accumulation std::vector mCurrentChannelEnergy GUARDED_BY(mLock); // accumulated MEL values std::vector mMelValues GUARDED_BY(mLock); // current index to store the MEL values uint32_t mCurrentIndex GUARDED_BY(mLock) = 0; using DefaultBiquadFilter = BiquadFilter; // Biquads used for the A-weighting std::array, kCascadeBiquadNumber> mCascadedBiquads GUARDED_BY(mLock); std::atomic mAttenuationDB = 0.f; // device id used for the callbacks std::atomic mDeviceId; // Value used for momentary exposure std::atomic mRs2UpperBound; // Skip processing data. std::atomic_bool mPaused = false; }; } // namespace android::audio_utils