Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //#define LOG_NDEBUG 0 |
| 18 | #define LOG_TAG "SoundPool::StreamManager" |
| 19 | #include <utils/Log.h> |
| 20 | |
| 21 | #include "StreamManager.h" |
| 22 | |
| 23 | #include <audio_utils/clock.h> |
| 24 | #include <audio_utils/roundup.h> |
| 25 | |
| 26 | namespace android::soundpool { |
| 27 | |
| 28 | // kMaxStreams is number that should be less than the current AudioTrack max per UID of 40. |
| 29 | // It is the maximum number of AudioTrack resources allowed in the SoundPool. |
| 30 | // We suggest a value at least 4 or greater to allow CTS tests to pass. |
| 31 | static constexpr int32_t kMaxStreams = 32; |
| 32 | |
| 33 | // kStealActiveStream_OldestFirst = false historically (Q and earlier) |
| 34 | // Changing to true could break app expectations but could change behavior beneficially. |
| 35 | // In R, we change this to true, as it is the correct way per SoundPool documentation. |
| 36 | static constexpr bool kStealActiveStream_OldestFirst = true; |
| 37 | |
| 38 | // kPlayOnCallingThread = true prior to R. |
| 39 | // Changing to false means calls to play() are almost instantaneous instead of taking around |
| 40 | // ~10ms to launch the AudioTrack. It is perhaps 100x faster. |
| 41 | static constexpr bool kPlayOnCallingThread = false; |
| 42 | |
| 43 | // Amount of time for a StreamManager thread to wait before closing. |
| 44 | static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND; |
| 45 | |
| 46 | //////////// |
| 47 | |
| 48 | StreamMap::StreamMap(int32_t streams) { |
| 49 | ALOGV("%s(%d)", __func__, streams); |
| 50 | if (streams > kMaxStreams) { |
| 51 | ALOGW("%s: requested %d streams, clamping to %d", __func__, streams, kMaxStreams); |
| 52 | streams = kMaxStreams; |
| 53 | } else if (streams < 1) { |
| 54 | ALOGW("%s: requested %d streams, clamping to 1", __func__, streams); |
| 55 | streams = 1; |
| 56 | } |
| 57 | mStreamPoolSize = streams * 2; |
| 58 | mStreamPool.reset(new Stream[mStreamPoolSize]); |
| 59 | // we use a perfect hash table with 2x size to map StreamIDs to Stream pointers. |
| 60 | mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2)); |
| 61 | } |
| 62 | |
| 63 | Stream* StreamMap::findStream(int32_t streamID) const |
| 64 | { |
| 65 | Stream *stream = lookupStreamFromId(streamID); |
| 66 | return stream != nullptr && stream->getStreamID() == streamID ? stream : nullptr; |
| 67 | } |
| 68 | |
| 69 | size_t StreamMap::streamPosition(const Stream* stream) const |
| 70 | { |
| 71 | ptrdiff_t index = stream - mStreamPool.get(); |
| 72 | LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize, |
| 73 | "%s: stream position out of range: %td", __func__, index); |
| 74 | return (size_t)index; |
| 75 | } |
| 76 | |
| 77 | Stream* StreamMap::lookupStreamFromId(int32_t streamID) const |
| 78 | { |
| 79 | return streamID > 0 ? mPerfectHash->getValue(streamID).load() : nullptr; |
| 80 | } |
| 81 | |
| 82 | int32_t StreamMap::getNextIdForStream(Stream* stream) const { |
| 83 | // even though it is const, it mutates the internal hash table. |
| 84 | const int32_t id = mPerfectHash->generateKey( |
| 85 | stream, |
| 86 | [] (Stream *stream) { |
| 87 | return stream == nullptr ? 0 : stream->getStreamID(); |
| 88 | }, /* getKforV() */ |
| 89 | stream->getStreamID() /* oldID */); |
| 90 | return id; |
| 91 | } |
| 92 | |
| 93 | //////////// |
| 94 | |
| 95 | StreamManager::StreamManager( |
| 96 | int32_t streams, size_t threads, const audio_attributes_t* attributes) |
| 97 | : StreamMap(streams) |
| 98 | , mAttributes(*attributes) |
| 99 | { |
| 100 | ALOGV("%s(%d, %zu, ...)", __func__, streams, threads); |
| 101 | forEach([this](Stream *stream) { |
| 102 | stream->setStreamManager(this); |
| 103 | if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available. |
| 104 | mAvailableStreams.insert(stream); |
| 105 | } |
| 106 | }); |
| 107 | |
| 108 | mThreadPool = std::make_unique<ThreadPool>( |
| 109 | std::min(threads, (size_t)std::thread::hardware_concurrency()), |
| 110 | "SoundPool_"); |
| 111 | } |
| 112 | |
| 113 | StreamManager::~StreamManager() |
| 114 | { |
| 115 | ALOGV("%s", __func__); |
| 116 | { |
| 117 | std::unique_lock lock(mStreamManagerLock); |
| 118 | mQuit = true; |
| 119 | mStreamManagerCondition.notify_all(); |
| 120 | } |
| 121 | mThreadPool->quit(); |
| 122 | |
| 123 | // call stop on the stream pool |
| 124 | forEach([](Stream *stream) { stream->stop(); }); |
| 125 | |
| 126 | // This invokes the destructor on the AudioTracks - |
| 127 | // we do it here to ensure that AudioTrack callbacks will not occur |
| 128 | // afterwards. |
| 129 | forEach([](Stream *stream) { stream->clearAudioTrack(); }); |
| 130 | } |
| 131 | |
| 132 | |
| 133 | int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound, |
| 134 | int32_t soundID, float leftVolume, float rightVolume, |
| 135 | int32_t priority, int32_t loop, float rate) |
| 136 | { |
| 137 | ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)", |
| 138 | __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate); |
| 139 | bool launchThread = false; |
| 140 | int32_t streamID = 0; |
| 141 | |
| 142 | { // for lock |
| 143 | std::unique_lock lock(mStreamManagerLock); |
| 144 | Stream *newStream = nullptr; |
| 145 | bool fromAvailableQueue = false; |
| 146 | ALOGV("%s: mStreamManagerLock lock acquired", __func__); |
| 147 | |
| 148 | sanityCheckQueue_l(); |
| 149 | // find an available stream, prefer one that has matching sound id. |
| 150 | if (mAvailableStreams.size() > 0) { |
| 151 | newStream = *mAvailableStreams.begin(); |
| 152 | for (auto stream : mAvailableStreams) { |
| 153 | if (stream->getSoundID() == soundID) { |
| 154 | newStream = stream; |
| 155 | break; |
| 156 | } |
| 157 | } |
| 158 | if (newStream != nullptr) { |
| 159 | newStream->setStopTimeNs(systemTime()); |
| 160 | } |
| 161 | fromAvailableQueue = true; |
| 162 | } |
| 163 | |
| 164 | // also look in the streams restarting (if the paired stream doesn't have a pending play) |
| 165 | if (newStream == nullptr || newStream->getSoundID() != soundID) { |
| 166 | for (auto [unused , stream] : mRestartStreams) { |
| 167 | if (!stream->getPairStream()->hasSound()) { |
| 168 | if (stream->getSoundID() == soundID) { |
| 169 | newStream = stream; |
| 170 | break; |
| 171 | } else if (newStream == nullptr) { |
| 172 | newStream = stream; |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | // no available streams, look for one to steal from the active list |
| 179 | if (newStream == nullptr) { |
| 180 | for (auto stream : mActiveStreams) { |
| 181 | if (stream->getPriority() <= priority) { |
| 182 | if (newStream == nullptr |
| 183 | || newStream->getPriority() > stream->getPriority()) { |
| 184 | newStream = stream; |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | if (newStream != nullptr) { // we need to mute as it is still playing. |
| 189 | (void)newStream->requestStop(newStream->getStreamID()); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | // none found, look for a stream that is restarting, evict one. |
| 194 | if (newStream == nullptr) { |
| 195 | for (auto [unused, stream] : mRestartStreams) { |
| 196 | if (stream->getPairPriority() <= priority) { |
| 197 | newStream = stream; |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads. |
| 204 | |
| 205 | if (newStream == nullptr) { |
| 206 | ALOGD("%s: unable to find stream, returning 0", __func__); |
| 207 | return 0; // unable to find available stream |
| 208 | } |
| 209 | |
| 210 | Stream *pairStream = newStream->getPairStream(); |
| 211 | streamID = getNextIdForStream(pairStream); |
| 212 | pairStream->setPlay( |
| 213 | streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate); |
| 214 | if (fromAvailableQueue && kPlayOnCallingThread) { |
| 215 | removeFromQueues_l(newStream); |
| 216 | mProcessingStreams.emplace(newStream); |
| 217 | lock.unlock(); |
| 218 | if (Stream* nextStream = newStream->playPairStream()) { |
| 219 | lock.lock(); |
| 220 | ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID()); |
| 221 | addToActiveQueue_l(nextStream); |
| 222 | } else { |
| 223 | lock.lock(); |
| 224 | mAvailableStreams.insert(newStream); |
| 225 | streamID = 0; |
| 226 | } |
| 227 | mProcessingStreams.erase(newStream); |
| 228 | } else { |
| 229 | launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l(); |
| 230 | } |
| 231 | sanityCheckQueue_l(); |
| 232 | ALOGV("%s: mStreamManagerLock released", __func__); |
| 233 | } // lock |
| 234 | |
| 235 | if (launchThread) { |
| 236 | const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); }); |
| 237 | ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id); |
| 238 | } |
| 239 | ALOGV("%s: returning %d", __func__, streamID); |
| 240 | return streamID; |
| 241 | } |
| 242 | |
| 243 | void StreamManager::moveToRestartQueue( |
| 244 | Stream* stream, int32_t activeStreamIDToMatch) |
| 245 | { |
| 246 | ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)", |
| 247 | __func__, stream->getStreamID(), activeStreamIDToMatch); |
| 248 | bool restart; |
| 249 | { |
| 250 | std::lock_guard lock(mStreamManagerLock); |
| 251 | sanityCheckQueue_l(); |
| 252 | if (mProcessingStreams.count(stream) > 0 || |
| 253 | mProcessingStreams.count(stream->getPairStream()) > 0) { |
| 254 | ALOGD("%s: attempting to restart processing stream(%d)", |
| 255 | __func__, stream->getStreamID()); |
| 256 | restart = false; |
| 257 | } else { |
| 258 | moveToRestartQueue_l(stream, activeStreamIDToMatch); |
| 259 | restart = needMoreThreads_l(); |
| 260 | } |
| 261 | sanityCheckQueue_l(); |
| 262 | } |
| 263 | if (restart) { |
| 264 | const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); }); |
| 265 | ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | bool StreamManager::moveToRestartQueue_l( |
| 270 | Stream* stream, int32_t activeStreamIDToMatch) |
| 271 | { |
| 272 | ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)", |
| 273 | __func__, stream->getStreamID(), activeStreamIDToMatch); |
| 274 | if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) { |
| 275 | return false; |
| 276 | } |
| 277 | const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch); |
| 278 | if (found < 0) return false; |
| 279 | |
| 280 | LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found); |
| 281 | |
| 282 | addToRestartQueue_l(stream); |
| 283 | mStreamManagerCondition.notify_one(); |
| 284 | return true; |
| 285 | } |
| 286 | |
| 287 | ssize_t StreamManager::removeFromQueues_l( |
| 288 | Stream* stream, int32_t activeStreamIDToMatch) { |
| 289 | size_t found = 0; |
| 290 | for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) { |
| 291 | if (*it == stream) { |
| 292 | mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe). |
| 293 | ++found; |
| 294 | break; |
| 295 | } |
| 296 | } |
| 297 | // activeStreamIDToMatch is nonzero indicates we proceed only if found. |
| 298 | if (found == 0 && activeStreamIDToMatch > 0) { |
| 299 | return -1; // special code: not present on active streams, ignore restart request |
| 300 | } |
| 301 | |
| 302 | for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) { |
| 303 | if (it->second == stream) { |
| 304 | mRestartStreams.erase(it); |
| 305 | ++found; |
| 306 | break; |
| 307 | } |
| 308 | } |
| 309 | found += mAvailableStreams.erase(stream); |
| 310 | |
| 311 | // streams on mProcessingStreams are undergoing processing by the StreamManager thread |
| 312 | // and do not participate in normal stream migration. |
| 313 | return found; |
| 314 | } |
| 315 | |
| 316 | void StreamManager::addToRestartQueue_l(Stream *stream) { |
| 317 | mRestartStreams.emplace(stream->getStopTimeNs(), stream); |
| 318 | } |
| 319 | |
| 320 | void StreamManager::addToActiveQueue_l(Stream *stream) { |
| 321 | if (kStealActiveStream_OldestFirst) { |
| 322 | mActiveStreams.push_back(stream); // oldest to newest |
| 323 | } else { |
| 324 | mActiveStreams.push_front(stream); // newest to oldest |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | void StreamManager::run(int32_t id) |
| 329 | { |
| 330 | ALOGV("%s(%d) entering", __func__, id); |
| 331 | int64_t waitTimeNs = kWaitTimeBeforeCloseNs; |
| 332 | std::unique_lock lock(mStreamManagerLock); |
| 333 | while (!mQuit) { |
| 334 | mStreamManagerCondition.wait_for( |
| 335 | lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs)); |
| 336 | ALOGV("%s(%d) awake", __func__, id); |
| 337 | |
| 338 | sanityCheckQueue_l(); |
| 339 | |
| 340 | if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) { |
| 341 | break; // end the thread |
| 342 | } |
| 343 | |
| 344 | waitTimeNs = kWaitTimeBeforeCloseNs; |
| 345 | while (!mQuit && !mRestartStreams.empty()) { |
| 346 | const nsecs_t nowNs = systemTime(); |
| 347 | auto it = mRestartStreams.begin(); |
| 348 | Stream* const stream = it->second; |
| 349 | const int64_t diffNs = stream->getStopTimeNs() - nowNs; |
| 350 | if (diffNs > 0) { |
| 351 | waitTimeNs = std::min(waitTimeNs, diffNs); |
| 352 | break; |
| 353 | } |
| 354 | mRestartStreams.erase(it); |
| 355 | mProcessingStreams.emplace(stream); |
| 356 | lock.unlock(); |
| 357 | stream->stop(); |
| 358 | ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID()); |
| 359 | if (Stream* nextStream = stream->playPairStream()) { |
| 360 | ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID()); |
| 361 | lock.lock(); |
| 362 | if (nextStream->getStopTimeNs() > 0) { |
| 363 | // the next stream was stopped before we can move it to the active queue. |
| 364 | ALOGV("%s(%d) stopping started streamID:%d", |
| 365 | __func__, id, nextStream->getStreamID()); |
| 366 | moveToRestartQueue_l(nextStream); |
| 367 | } else { |
| 368 | addToActiveQueue_l(nextStream); |
| 369 | } |
| 370 | } else { |
| 371 | lock.lock(); |
| 372 | mAvailableStreams.insert(stream); |
| 373 | } |
| 374 | mProcessingStreams.erase(stream); |
| 375 | sanityCheckQueue_l(); |
| 376 | } |
| 377 | } |
| 378 | ALOGV("%s(%d) exiting", __func__, id); |
| 379 | } |
| 380 | |
| 381 | void StreamManager::dump() const |
| 382 | { |
| 383 | forEach([](const Stream *stream) { stream->dump(); }); |
| 384 | } |
| 385 | |
| 386 | void StreamManager::sanityCheckQueue_l() const |
| 387 | { |
| 388 | // We want to preserve the invariant that each stream pair is exactly on one of the queues. |
| 389 | const size_t availableStreams = mAvailableStreams.size(); |
| 390 | const size_t restartStreams = mRestartStreams.size(); |
| 391 | const size_t activeStreams = mActiveStreams.size(); |
| 392 | const size_t processingStreams = mProcessingStreams.size(); |
| 393 | const size_t managedStreams = availableStreams + restartStreams + activeStreams |
| 394 | + processingStreams; |
| 395 | const size_t totalStreams = getStreamMapSize() >> 1; |
| 396 | LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams, |
| 397 | "%s: mAvailableStreams:%zu + mRestartStreams:%zu + " |
| 398 | "mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu", |
| 399 | __func__, availableStreams, restartStreams, activeStreams, processingStreams, |
| 400 | managedStreams, totalStreams); |
| 401 | ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + " |
| 402 | "mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)", |
| 403 | __func__, availableStreams, restartStreams, activeStreams, processingStreams, |
| 404 | managedStreams, totalStreams); |
| 405 | } |
| 406 | |
| 407 | } // namespace android::soundpool |