/* * 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 #include #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 auto sampleRate = (uint32_t)lround(double(mSound->getSampleRate()) * rate); 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, 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) { ALOGV("%s: track streamID: %d", __func__, streamID); 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) { ALOGV("%s: track(%p) streamID: %d", __func__, mAudioTrack.get(), (int)mStreamID); if (mAudioTrack != nullptr) { mAudioTrack->stop(); } mSound.reset(); mState = IDLE; } } void Stream::clearAudioTrack() { sp release; // release outside of lock. std::lock_guard lock(mLock); // 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. release = mAudioTrack; // or std::swap if we had move semantics. 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 releaseTracks[2]; { ALOGV("%s: track streamID: %d", __func__, (int)getStreamID()); // 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, int32_t nextStreamID, float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate, sp releaseTracks[2]) { // These tracks are released without the lock. sp &oldTrack = releaseTracks[0]; sp &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 auto sampleRate = (uint32_t)lround(double(sound->getSampleRate()) * rate); 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 == nullptr) { // 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; // NOLINTNEXTLINE(performance-no-int-to-ptr) void* userData = reinterpret_cast((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 android::content::AttributionSourceState attributionSource; attributionSource.packageName = mStreamManager->getOpPackageName(); attributionSource.token = sp::make(); // TODO b/182469354 make consistent with AudioRecord, add util for native source 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*/, attributionSource, mStreamManager->getAttributes(), false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/); // Set caller name so it can be logged in destructor. // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_SOUNDPOOL newTrack->setCallerName("soundpool"); 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 auto userAsInt = (uintptr_t)user; // NOLINTNEXTLINE(performance-no-int-to-ptr) auto stream = reinterpret_cast(userAsInt & ~1); stream->callback(event, info, int(userAsInt & 1), 0 /* tries */); } void Stream::callback(int event, void* info, int toggle, int tries) { int32_t activeStreamIDToRestart = 0; { std::unique_lock lock(mLock); ALOGV("%s track(%p) streamID %d", __func__, mAudioTrack.get(), (int)mStreamID); 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(); ALOGV("%s streamID %d going to pair stream", __func__, (int)mStreamID); 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 { // TODO: consider std::try_lock() - ok for now for ALOGV. ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d", getPairStream(), mState, (int)getStreamID(), getSoundID(), mPriority, mLoop); } } // namespace android::soundpool