449 lines
16 KiB
C++
449 lines
16 KiB
C++
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
//#define LOG_NDEBUG 0
|
||
|
#define LOG_TAG "SoundPool::Stream"
|
||
|
#include <utils/Log.h>
|
||
|
|
||
|
#include "Stream.h"
|
||
|
|
||
|
#include "StreamManager.h"
|
||
|
|
||
|
namespace android::soundpool {
|
||
|
|
||
|
Stream::~Stream()
|
||
|
{
|
||
|
ALOGV("%s(%p)", __func__, this);
|
||
|
}
|
||
|
|
||
|
void Stream::autoPause()
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (mState == PLAYING) {
|
||
|
ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
|
||
|
mState = PAUSED;
|
||
|
mAutoPaused = true;
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
mAudioTrack->pause();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::autoResume()
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (mAutoPaused) {
|
||
|
if (mState == PAUSED) {
|
||
|
ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
|
||
|
mState = PLAYING;
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
mAudioTrack->start();
|
||
|
}
|
||
|
}
|
||
|
mAutoPaused = false; // New for R: always reset autopause (consistent with API spec).
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::mute(bool muting)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
mMuted = muting;
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
if (mMuted) {
|
||
|
mAudioTrack->setVolume(0.0f, 0.0f);
|
||
|
} else {
|
||
|
mAudioTrack->setVolume(mLeftVolume, mRightVolume);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::pause(int32_t streamID)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
if (mState == PLAYING) {
|
||
|
ALOGV("%s: track streamID: %d", __func__, streamID);
|
||
|
mState = PAUSED;
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
mAudioTrack->pause();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::resume(int32_t streamID)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
if (mState == PAUSED) {
|
||
|
ALOGV("%s: track streamID: %d", __func__, streamID);
|
||
|
mState = PLAYING;
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
mAudioTrack->start();
|
||
|
}
|
||
|
mAutoPaused = false; // TODO: is this right? (ambiguous per spec), move outside?
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::setRate(int32_t streamID, float rate)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
mRate = rate;
|
||
|
if (mAudioTrack != nullptr && mSound != nullptr) {
|
||
|
const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
|
||
|
mAudioTrack->setSampleRate(sampleRate);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::setVolume_l(float leftVolume, float rightVolume)
|
||
|
{
|
||
|
mLeftVolume = leftVolume;
|
||
|
mRightVolume = rightVolume;
|
||
|
if (mAudioTrack != nullptr && !mMuted) {
|
||
|
mAudioTrack->setVolume(leftVolume, rightVolume);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::setVolume(int32_t streamID, float leftVolume, float rightVolume)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
setVolume_l(leftVolume, rightVolume);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::setPriority(int32_t streamID, int32_t priority)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
mPriority = priority;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::setLoop(int32_t streamID, int32_t loop)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
if (mAudioTrack != nullptr && mSound != nullptr) {
|
||
|
const uint32_t loopEnd = mSound->getSizeInBytes() / mSound->getChannelCount() /
|
||
|
(mSound->getFormat() == AUDIO_FORMAT_PCM_16_BIT
|
||
|
? sizeof(int16_t) : sizeof(uint8_t));
|
||
|
mAudioTrack->setLoop(0, loopEnd, loop);
|
||
|
}
|
||
|
mLoop = loop;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::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)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
// We must be idle, or we must be repurposing a pending Stream.
|
||
|
LOG_ALWAYS_FATAL_IF(mState != IDLE && mAudioTrack != nullptr, "State %d must be IDLE", mState);
|
||
|
mSound = sound;
|
||
|
mSoundID = soundID;
|
||
|
mLeftVolume = leftVolume;
|
||
|
mRightVolume = rightVolume;
|
||
|
mPriority = priority;
|
||
|
mLoop = loop;
|
||
|
mRate = rate;
|
||
|
mState = PLAYING;
|
||
|
mAutoPaused = false; // New for R (consistent with Java API spec).
|
||
|
mStreamID = streamID; // prefer this to be the last, as it is an atomic sync point
|
||
|
}
|
||
|
|
||
|
void Stream::setStopTimeNs(int64_t stopTimeNs)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
mStopTimeNs = stopTimeNs;
|
||
|
}
|
||
|
|
||
|
bool Stream::requestStop(int32_t streamID)
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
if (streamID == mStreamID) {
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
if (mState == PLAYING && !mMuted && (mLeftVolume != 0.f || mRightVolume != 0.f)) {
|
||
|
setVolume_l(0.f, 0.f);
|
||
|
mStopTimeNs = systemTime() + kStopWaitTimeNs;
|
||
|
} else {
|
||
|
mStopTimeNs = systemTime();
|
||
|
}
|
||
|
return true; // must be queued on the restart list.
|
||
|
}
|
||
|
stop_l();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void Stream::stop()
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
stop_l();
|
||
|
}
|
||
|
|
||
|
void Stream::stop_l()
|
||
|
{
|
||
|
if (mState != IDLE) {
|
||
|
if (mAudioTrack != nullptr) {
|
||
|
mAudioTrack->stop();
|
||
|
}
|
||
|
mSound.reset();
|
||
|
mState = IDLE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::clearAudioTrack()
|
||
|
{
|
||
|
// This will invoke the destructor which waits for the AudioTrack thread to join,
|
||
|
// and is currently the only safe way to ensure there are no callbacks afterwards.
|
||
|
mAudioTrack.clear();
|
||
|
}
|
||
|
|
||
|
Stream* Stream::getPairStream() const
|
||
|
{
|
||
|
return mStreamManager->getPairStream(this);
|
||
|
}
|
||
|
|
||
|
Stream* Stream::playPairStream() {
|
||
|
Stream* pairStream = getPairStream();
|
||
|
LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
|
||
|
sp<AudioTrack> releaseTracks[2];
|
||
|
{
|
||
|
// TODO: Do we really want to force a simultaneous synchronization between
|
||
|
// the stream and its pair?
|
||
|
|
||
|
// note locking order - the paired stream is obtained before the queued stream.
|
||
|
// we can invert the locking order, but it is slightly more optimal to do it this way.
|
||
|
std::lock_guard lockp(pairStream->mLock);
|
||
|
if (pairStream->mSound == nullptr) {
|
||
|
return nullptr; // no pair sound
|
||
|
}
|
||
|
{
|
||
|
std::lock_guard lock(mLock);
|
||
|
LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
|
||
|
// TODO: do we want a specific set() here?
|
||
|
pairStream->mAudioTrack = mAudioTrack;
|
||
|
pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
|
||
|
pairStream->mToggle = mToggle;
|
||
|
pairStream->mAutoPaused = mAutoPaused; // save autopause state
|
||
|
pairStream->mMuted = mMuted;
|
||
|
mAudioTrack.clear(); // the pair owns the audiotrack.
|
||
|
mSound.reset();
|
||
|
mSoundID = 0;
|
||
|
}
|
||
|
// TODO: do we need a specific play_l() anymore?
|
||
|
const int pairState = pairStream->mState;
|
||
|
pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
|
||
|
pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
|
||
|
pairStream->mLoop, pairStream->mRate, releaseTracks);
|
||
|
if (pairStream->mState == IDLE) {
|
||
|
return nullptr; // AudioTrack error
|
||
|
}
|
||
|
if (pairState == PAUSED) { // reestablish pause
|
||
|
pairStream->mState = PAUSED;
|
||
|
pairStream->mAudioTrack->pause();
|
||
|
}
|
||
|
}
|
||
|
// release tracks outside of Stream lock
|
||
|
return pairStream;
|
||
|
}
|
||
|
|
||
|
void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
|
||
|
float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
|
||
|
sp<AudioTrack> releaseTracks[2])
|
||
|
{
|
||
|
// These tracks are released without the lock.
|
||
|
sp<AudioTrack> &oldTrack = releaseTracks[0];
|
||
|
sp<AudioTrack> &newTrack = releaseTracks[1];
|
||
|
status_t status = NO_ERROR;
|
||
|
|
||
|
{
|
||
|
ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
|
||
|
" priority=%d, loop=%d, rate=%f)",
|
||
|
__func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
|
||
|
priority, loop, rate);
|
||
|
|
||
|
// initialize track
|
||
|
const audio_stream_type_t streamType =
|
||
|
AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
|
||
|
const int32_t channelCount = sound->getChannelCount();
|
||
|
const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
|
||
|
size_t frameCount = 0;
|
||
|
|
||
|
if (loop) {
|
||
|
const audio_format_t format = sound->getFormat();
|
||
|
const size_t frameSize = audio_is_linear_pcm(format)
|
||
|
? channelCount * audio_bytes_per_sample(format) : 1;
|
||
|
frameCount = sound->getSizeInBytes() / frameSize;
|
||
|
}
|
||
|
|
||
|
// check if the existing track has the same sound id.
|
||
|
if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
|
||
|
// the sample rate may fail to change if the audio track is a fast track.
|
||
|
if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
|
||
|
newTrack = mAudioTrack;
|
||
|
ALOGV("%s: reusing track %p for sound %d",
|
||
|
__func__, mAudioTrack.get(), sound->getSoundID());
|
||
|
}
|
||
|
}
|
||
|
if (newTrack == 0) {
|
||
|
// mToggle toggles each time a track is started on a given stream.
|
||
|
// The toggle is concatenated with the Stream address and passed to AudioTrack
|
||
|
// as callback user data. This enables the detection of callbacks received from the old
|
||
|
// audio track while the new one is being started and avoids processing them with
|
||
|
// wrong audio audio buffer size (mAudioBufferSize)
|
||
|
auto toggle = mToggle ^ 1;
|
||
|
void* userData = (void*)((uintptr_t)this | toggle);
|
||
|
audio_channel_mask_t soundChannelMask = sound->getChannelMask();
|
||
|
// When sound contains a valid channel mask, use it as is.
|
||
|
// Otherwise, use stream count to calculate channel mask.
|
||
|
audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
|
||
|
? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
|
||
|
|
||
|
// do not create a new audio track if current track is compatible with sound parameters
|
||
|
|
||
|
newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
|
||
|
channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
|
||
|
staticCallback, userData,
|
||
|
0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
|
||
|
AudioTrack::TRANSFER_DEFAULT,
|
||
|
nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/,
|
||
|
mStreamManager->getAttributes());
|
||
|
|
||
|
oldTrack = mAudioTrack;
|
||
|
status = newTrack->initCheck();
|
||
|
if (status != NO_ERROR) {
|
||
|
ALOGE("%s: error creating AudioTrack", __func__);
|
||
|
// newTrack goes out of scope, so reference count drops to zero
|
||
|
goto exit;
|
||
|
}
|
||
|
// From now on, AudioTrack callbacks received with previous toggle value will be ignored.
|
||
|
mToggle = toggle;
|
||
|
mAudioTrack = newTrack;
|
||
|
ALOGV("%s: using new track %p for sound %d",
|
||
|
__func__, newTrack.get(), sound->getSoundID());
|
||
|
}
|
||
|
if (mMuted) {
|
||
|
newTrack->setVolume(0.0f, 0.0f);
|
||
|
} else {
|
||
|
newTrack->setVolume(leftVolume, rightVolume);
|
||
|
}
|
||
|
newTrack->setLoop(0, frameCount, loop);
|
||
|
mAudioTrack->start();
|
||
|
mSound = sound;
|
||
|
mSoundID = sound->getSoundID();
|
||
|
mPriority = priority;
|
||
|
mLoop = loop;
|
||
|
mLeftVolume = leftVolume;
|
||
|
mRightVolume = rightVolume;
|
||
|
mRate = rate;
|
||
|
mState = PLAYING;
|
||
|
mStopTimeNs = 0;
|
||
|
mStreamID = nextStreamID; // prefer this to be the last, as it is an atomic sync point
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
|
||
|
if (status != NO_ERROR) {
|
||
|
// TODO: should we consider keeping the soundID if the old track is OK?
|
||
|
// Do not attempt to restart this track (should we remove the stream id?)
|
||
|
mState = IDLE;
|
||
|
mSoundID = 0;
|
||
|
mSound.reset();
|
||
|
mAudioTrack.clear(); // actual release from releaseTracks[]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* static */
|
||
|
void Stream::staticCallback(int event, void* user, void* info)
|
||
|
{
|
||
|
const uintptr_t userAsInt = (uintptr_t)user;
|
||
|
Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
|
||
|
stream->callback(event, info, userAsInt & 1, 0 /* tries */);
|
||
|
}
|
||
|
|
||
|
void Stream::callback(int event, void* info, int toggle, int tries)
|
||
|
{
|
||
|
ALOGV("%s streamID %d", __func__, (int)mStreamID);
|
||
|
int32_t activeStreamIDToRestart = 0;
|
||
|
{
|
||
|
std::unique_lock lock(mLock);
|
||
|
|
||
|
if (mAudioTrack == nullptr) {
|
||
|
// The AudioTrack is either with this stream or its pair.
|
||
|
// if this swaps a few times, the toggle is bound to be wrong, so we fail then.
|
||
|
//
|
||
|
// TODO: Modify AudioTrack callbacks to avoid the hacky toggle and retry
|
||
|
// logic here.
|
||
|
if (tries < 3) {
|
||
|
lock.unlock();
|
||
|
getPairStream()->callback(event, info, toggle, tries + 1);
|
||
|
} else {
|
||
|
ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
if (mToggle != toggle) {
|
||
|
ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
|
||
|
return;
|
||
|
}
|
||
|
switch (event) {
|
||
|
case AudioTrack::EVENT_MORE_DATA:
|
||
|
ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
|
||
|
__func__, (int)mStreamID);
|
||
|
break;
|
||
|
case AudioTrack::EVENT_UNDERRUN:
|
||
|
ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
|
||
|
__func__, (int)mStreamID);
|
||
|
break;
|
||
|
case AudioTrack::EVENT_BUFFER_END:
|
||
|
ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
|
||
|
if (mState != IDLE) {
|
||
|
activeStreamIDToRestart = mStreamID;
|
||
|
mStopTimeNs = systemTime();
|
||
|
}
|
||
|
break;
|
||
|
case AudioTrack::EVENT_LOOP_END:
|
||
|
ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
|
||
|
break;
|
||
|
case AudioTrack::EVENT_NEW_IAUDIOTRACK:
|
||
|
ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
|
||
|
break;
|
||
|
default:
|
||
|
ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
|
||
|
break;
|
||
|
}
|
||
|
} // lock ends here. This is on the callback thread, no need to be precise.
|
||
|
if (activeStreamIDToRestart > 0) {
|
||
|
// Restart only if a particular streamID is still current and active.
|
||
|
ALOGV("%s: moveToRestartQueue %d", __func__, activeStreamIDToRestart);
|
||
|
mStreamManager->moveToRestartQueue(this, activeStreamIDToRestart);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Stream::dump() const
|
||
|
{
|
||
|
ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d",
|
||
|
getPairStream(), mState, (int)mStreamID, mSoundID, mPriority, mLoop);
|
||
|
}
|
||
|
|
||
|
} // namespace android::soundpool
|