blob: 6c9ef2e087f87150d3a5354d6d8cbfc5df44f25c [file] [log] [blame]
/*
* Copyright (C) 2019 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 "Sound.h"
#include <any>
#include <android-base/thread_annotations.h>
#include <audio_utils/clock.h>
#include <media/AudioTrack.h>
namespace android::soundpool {
// This is the amount of time to wait after stop is called when stealing an
// AudioTrack to allow the sound to ramp down. If this is 0, glitches
// may occur when stealing an AudioTrack.
inline constexpr int64_t kStopWaitTimeNs = 20 * NANOS_PER_MILLISECOND;
inline constexpr size_t kCacheLineSize = 64; /* std::hardware_constructive_interference_size */
class StreamManager; // forward decl
/**
* A Stream is associated with a StreamID exposed to the app to play a Sound.
*
* The Stream uses monitor locking strategy on mLock.
* https://en.wikipedia.org/wiki/Monitor_(synchronization)
*
* where public methods are guarded by a lock (as needed)
*
* For Java equivalent APIs, see
* https://developer.android.com/reference/android/media/SoundPool
*
* Streams are paired by the StreamManager, so one stream in the pair may be "stopping"
* while the other stream of the pair has been prepared to run
* (and the streamID returned to the app) pending its pair to be stopped.
* The pair of a Stream may be obtained by calling getPairStream(),
* where this->getPairStream()->getPairStream() == this; (pair is a commutative relationship).
*
* playPairStream() and getPairPriority() access the paired stream.
* See also StreamManager.h for details of physical layout implications of paired streams.
*/
class alignas(kCacheLineSize) Stream {
public:
enum state { IDLE, PAUSED, PLAYING };
// The PAUSED, PLAYING state directly corresponds to the AudioTrack state of an active Stream.
//
// The IDLE state indicates an inactive Stream. An IDLE Stream may have a non-nullptr
// AudioTrack, which may be recycled for use if the SoundID matches the next Stream playback.
//
// PAUSED -> PLAYING through resume() (see also autoResume())
// PLAYING -> PAUSED through pause() (see also autoPause())
//
// IDLE is the initial state of a Stream and also when a stream becomes inactive.
// {PAUSED, PLAYING} -> IDLE through stop() (or if the Sound finishes playing)
// IDLE -> PLAYING through play(). (there is no way to start a Stream in paused mode).
~Stream();
void setStreamManager(StreamManager* streamManager) { // non-nullptr
mStreamManager = streamManager; // set in StreamManager constructor, not changed
}
// The following methods are monitor locked by mLock.
//
// For methods taking a streamID:
// if the streamID matches the Stream's mStreamID, then method proceeds
// else the command is ignored with no effect.
// returns true if the stream needs to be explicitly stopped.
bool requestStop(int32_t streamID);
void stop(); // explicit stop(), typically called from the worker thread.
void clearAudioTrack();
void pause(int32_t streamID);
void autoPause(); // see the Java SoundPool.autoPause documentation for details.
void resume(int32_t streamID);
void autoResume();
void mute(bool muting);
void dump() const NO_THREAD_SAFETY_ANALYSIS; // disable for ALOGV (see func for details).
// returns the pair stream if successful, nullptr otherwise.
// garbage is used to release tracks and data outside of any lock.
Stream* playPairStream(std::vector<std::any>& garbage,
int32_t playerIId = PLAYER_PIID_INVALID);
// These parameters are explicitly checked in the SoundPool class
// so never deviate from the Java API specified values.
void setVolume(int32_t streamID, float leftVolume, float rightVolume);
void setRate(int32_t streamID, float rate);
void setPriority(int32_t streamID, int priority);
void setLoop(int32_t streamID, int loop);
void setPlay(int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate);
void setStopTimeNs(int64_t stopTimeNs); // systemTime() clock monotonic.
// The following getters are not locked and have weak consistency.
// These are considered advisory only - being stale is of nuisance.
int32_t getPriority() const NO_THREAD_SAFETY_ANALYSIS { return mPriority; }
int32_t getPairPriority() const NO_THREAD_SAFETY_ANALYSIS {
return getPairStream()->getPriority();
}
int64_t getStopTimeNs() const NO_THREAD_SAFETY_ANALYSIS { return mStopTimeNs; }
// Can change with setPlay()
int32_t getStreamID() const NO_THREAD_SAFETY_ANALYSIS { return mStreamID; }
// Can change with play_l()
int32_t getSoundID() const NO_THREAD_SAFETY_ANALYSIS { return mSoundID; }
bool hasSound() const NO_THREAD_SAFETY_ANALYSIS { return mSound.get() != nullptr; }
// This never changes. See top of header.
Stream* getPairStream() const;
// Stream ID of ourselves, or the pair depending on who holds the AudioTrack
int getCorrespondingStreamID();
protected:
// AudioTrack callback interface implementation
class StreamCallback : public AudioTrack::IAudioTrackCallback {
public:
StreamCallback(Stream * stream, bool toggle) : mStream(stream), mToggle(toggle) {}
size_t onMoreData(const AudioTrack::Buffer& buffer) override;
void onUnderrun() override;
void onLoopEnd(int32_t loopsRemaining) override;
void onMarker(uint32_t markerPosition) override;
void onNewPos(uint32_t newPos) override;
void onBufferEnd() override;
void onNewIAudioTrack() override;
void onStreamEnd() override;
size_t onCanWriteMoreData(const AudioTrack::Buffer& buffer) override;
// Holding a raw ptr is technically unsafe, but, Stream objects persist
// through the lifetime of the StreamManager through the use of a
// unique_ptr<Stream[]>. Ensuring lifetime will cause us to give up
// locality as well as pay RefBase/sp performance cost, which we are
// unwilling to do. Non-owning refs to unique_ptrs are idiomatically raw
// ptrs, as below.
Stream * const mStream;
const bool mToggle;
};
sp<StreamCallback> mCallback;
private:
// garbage is used to release tracks and data outside of any lock.
void play_l(const std::shared_ptr<Sound>& sound, int streamID,
float leftVolume, float rightVolume, int priority, int loop, float rate,
std::vector<std::any>& garbage, int playerIId) REQUIRES(mLock);
void stop_l() REQUIRES(mLock);
void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
// For use with AudioTrack callback.
void onBufferEnd(int toggle, int tries) NO_THREAD_SAFETY_ANALYSIS;
// StreamManager should be set on construction and not changed.
// release mLock before calling into StreamManager
StreamManager* mStreamManager = nullptr;
mutable std::mutex mLock;
std::atomic_int32_t mStreamID GUARDED_BY(mLock) = 0; // Valid streamIDs are always positive.
int mState GUARDED_BY(mLock) = IDLE;
std::shared_ptr<Sound> mSound GUARDED_BY(mLock); // Non-null if playing.
int32_t mSoundID GUARDED_BY(mLock) = 0; // SoundID associated with AudioTrack.
float mLeftVolume GUARDED_BY(mLock) = 0.f;
float mRightVolume GUARDED_BY(mLock) = 0.f;
int32_t mPriority GUARDED_BY(mLock) = INT32_MIN;
int32_t mLoop GUARDED_BY(mLock) = 0;
float mRate GUARDED_BY(mLock) = 0.f;
bool mAutoPaused GUARDED_BY(mLock) = false;
bool mMuted GUARDED_BY(mLock) = false;
sp<AudioTrack> mAudioTrack GUARDED_BY(mLock);
int mToggle GUARDED_BY(mLock) = 0;
int64_t mStopTimeNs GUARDED_BY(mLock) = 0; // if nonzero, time to wait for stop.
};
} // namespace android::soundpool