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. |
Andy Hung | 66262b1 | 2019-11-18 13:29:57 -0800 | [diff] [blame] | 41 | static constexpr bool kPlayOnCallingThread = true; |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 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) { |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 151 | for (auto stream : mAvailableStreams) { |
| 152 | if (stream->getSoundID() == soundID) { |
| 153 | newStream = stream; |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 154 | ALOGV("%s: found soundID %d in available queue", __func__, soundID); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 155 | break; |
| 156 | } |
| 157 | } |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 158 | if (newStream == nullptr) { |
| 159 | ALOGV("%s: found stream in available queue", __func__); |
| 160 | newStream = *mAvailableStreams.begin(); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 161 | } |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 162 | newStream->setStopTimeNs(systemTime()); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 163 | fromAvailableQueue = true; |
| 164 | } |
| 165 | |
| 166 | // also look in the streams restarting (if the paired stream doesn't have a pending play) |
| 167 | if (newStream == nullptr || newStream->getSoundID() != soundID) { |
| 168 | for (auto [unused , stream] : mRestartStreams) { |
| 169 | if (!stream->getPairStream()->hasSound()) { |
| 170 | if (stream->getSoundID() == soundID) { |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 171 | ALOGV("%s: found soundID %d in restart queue", __func__, soundID); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 172 | newStream = stream; |
Andy Hung | 66262b1 | 2019-11-18 13:29:57 -0800 | [diff] [blame] | 173 | fromAvailableQueue = false; |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 174 | break; |
| 175 | } else if (newStream == nullptr) { |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 176 | ALOGV("%s: found stream in restart queue", __func__); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 177 | newStream = stream; |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | // no available streams, look for one to steal from the active list |
| 184 | if (newStream == nullptr) { |
| 185 | for (auto stream : mActiveStreams) { |
| 186 | if (stream->getPriority() <= priority) { |
| 187 | if (newStream == nullptr |
| 188 | || newStream->getPriority() > stream->getPriority()) { |
| 189 | newStream = stream; |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 190 | ALOGV("%s: found stream in active queue", __func__); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 191 | } |
| 192 | } |
| 193 | } |
| 194 | if (newStream != nullptr) { // we need to mute as it is still playing. |
| 195 | (void)newStream->requestStop(newStream->getStreamID()); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | // none found, look for a stream that is restarting, evict one. |
| 200 | if (newStream == nullptr) { |
| 201 | for (auto [unused, stream] : mRestartStreams) { |
| 202 | if (stream->getPairPriority() <= priority) { |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 203 | ALOGV("%s: evict stream from restart queue", __func__); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 204 | newStream = stream; |
| 205 | break; |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads. |
| 211 | |
| 212 | if (newStream == nullptr) { |
| 213 | ALOGD("%s: unable to find stream, returning 0", __func__); |
| 214 | return 0; // unable to find available stream |
| 215 | } |
| 216 | |
| 217 | Stream *pairStream = newStream->getPairStream(); |
| 218 | streamID = getNextIdForStream(pairStream); |
Andy Hung | 457ed3a | 2019-11-19 16:44:13 -0800 | [diff] [blame^] | 219 | ALOGV("%s: newStream:%p pairStream:%p, streamID:%d", |
| 220 | __func__, newStream, pairStream, streamID); |
Andy Hung | e7937b9 | 2019-08-28 21:02:23 -0700 | [diff] [blame] | 221 | pairStream->setPlay( |
| 222 | streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate); |
| 223 | if (fromAvailableQueue && kPlayOnCallingThread) { |
| 224 | removeFromQueues_l(newStream); |
| 225 | mProcessingStreams.emplace(newStream); |
| 226 | lock.unlock(); |
| 227 | if (Stream* nextStream = newStream->playPairStream()) { |
| 228 | lock.lock(); |
| 229 | ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID()); |
| 230 | addToActiveQueue_l(nextStream); |
| 231 | } else { |
| 232 | lock.lock(); |
| 233 | mAvailableStreams.insert(newStream); |
| 234 | streamID = 0; |
| 235 | } |
| 236 | mProcessingStreams.erase(newStream); |
| 237 | } else { |
| 238 | launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l(); |
| 239 | } |
| 240 | sanityCheckQueue_l(); |
| 241 | ALOGV("%s: mStreamManagerLock released", __func__); |
| 242 | } // lock |
| 243 | |
| 244 | if (launchThread) { |
| 245 | const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); }); |
| 246 | ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id); |
| 247 | } |
| 248 | ALOGV("%s: returning %d", __func__, streamID); |
| 249 | return streamID; |
| 250 | } |
| 251 | |
| 252 | void StreamManager::moveToRestartQueue( |
| 253 | Stream* stream, int32_t activeStreamIDToMatch) |
| 254 | { |
| 255 | ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)", |
| 256 | __func__, stream->getStreamID(), activeStreamIDToMatch); |
| 257 | bool restart; |
| 258 | { |
| 259 | std::lock_guard lock(mStreamManagerLock); |
| 260 | sanityCheckQueue_l(); |
| 261 | if (mProcessingStreams.count(stream) > 0 || |
| 262 | mProcessingStreams.count(stream->getPairStream()) > 0) { |
| 263 | ALOGD("%s: attempting to restart processing stream(%d)", |
| 264 | __func__, stream->getStreamID()); |
| 265 | restart = false; |
| 266 | } else { |
| 267 | moveToRestartQueue_l(stream, activeStreamIDToMatch); |
| 268 | restart = needMoreThreads_l(); |
| 269 | } |
| 270 | sanityCheckQueue_l(); |
| 271 | } |
| 272 | if (restart) { |
| 273 | const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); }); |
| 274 | ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id); |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | bool StreamManager::moveToRestartQueue_l( |
| 279 | Stream* stream, int32_t activeStreamIDToMatch) |
| 280 | { |
| 281 | ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)", |
| 282 | __func__, stream->getStreamID(), activeStreamIDToMatch); |
| 283 | if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) { |
| 284 | return false; |
| 285 | } |
| 286 | const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch); |
| 287 | if (found < 0) return false; |
| 288 | |
| 289 | LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found); |
| 290 | |
| 291 | addToRestartQueue_l(stream); |
| 292 | mStreamManagerCondition.notify_one(); |
| 293 | return true; |
| 294 | } |
| 295 | |
| 296 | ssize_t StreamManager::removeFromQueues_l( |
| 297 | Stream* stream, int32_t activeStreamIDToMatch) { |
| 298 | size_t found = 0; |
| 299 | for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) { |
| 300 | if (*it == stream) { |
| 301 | mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe). |
| 302 | ++found; |
| 303 | break; |
| 304 | } |
| 305 | } |
| 306 | // activeStreamIDToMatch is nonzero indicates we proceed only if found. |
| 307 | if (found == 0 && activeStreamIDToMatch > 0) { |
| 308 | return -1; // special code: not present on active streams, ignore restart request |
| 309 | } |
| 310 | |
| 311 | for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) { |
| 312 | if (it->second == stream) { |
| 313 | mRestartStreams.erase(it); |
| 314 | ++found; |
| 315 | break; |
| 316 | } |
| 317 | } |
| 318 | found += mAvailableStreams.erase(stream); |
| 319 | |
| 320 | // streams on mProcessingStreams are undergoing processing by the StreamManager thread |
| 321 | // and do not participate in normal stream migration. |
| 322 | return found; |
| 323 | } |
| 324 | |
| 325 | void StreamManager::addToRestartQueue_l(Stream *stream) { |
| 326 | mRestartStreams.emplace(stream->getStopTimeNs(), stream); |
| 327 | } |
| 328 | |
| 329 | void StreamManager::addToActiveQueue_l(Stream *stream) { |
| 330 | if (kStealActiveStream_OldestFirst) { |
| 331 | mActiveStreams.push_back(stream); // oldest to newest |
| 332 | } else { |
| 333 | mActiveStreams.push_front(stream); // newest to oldest |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | void StreamManager::run(int32_t id) |
| 338 | { |
| 339 | ALOGV("%s(%d) entering", __func__, id); |
| 340 | int64_t waitTimeNs = kWaitTimeBeforeCloseNs; |
| 341 | std::unique_lock lock(mStreamManagerLock); |
| 342 | while (!mQuit) { |
| 343 | mStreamManagerCondition.wait_for( |
| 344 | lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs)); |
| 345 | ALOGV("%s(%d) awake", __func__, id); |
| 346 | |
| 347 | sanityCheckQueue_l(); |
| 348 | |
| 349 | if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) { |
| 350 | break; // end the thread |
| 351 | } |
| 352 | |
| 353 | waitTimeNs = kWaitTimeBeforeCloseNs; |
| 354 | while (!mQuit && !mRestartStreams.empty()) { |
| 355 | const nsecs_t nowNs = systemTime(); |
| 356 | auto it = mRestartStreams.begin(); |
| 357 | Stream* const stream = it->second; |
| 358 | const int64_t diffNs = stream->getStopTimeNs() - nowNs; |
| 359 | if (diffNs > 0) { |
| 360 | waitTimeNs = std::min(waitTimeNs, diffNs); |
| 361 | break; |
| 362 | } |
| 363 | mRestartStreams.erase(it); |
| 364 | mProcessingStreams.emplace(stream); |
| 365 | lock.unlock(); |
| 366 | stream->stop(); |
| 367 | ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID()); |
| 368 | if (Stream* nextStream = stream->playPairStream()) { |
| 369 | ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID()); |
| 370 | lock.lock(); |
| 371 | if (nextStream->getStopTimeNs() > 0) { |
| 372 | // the next stream was stopped before we can move it to the active queue. |
| 373 | ALOGV("%s(%d) stopping started streamID:%d", |
| 374 | __func__, id, nextStream->getStreamID()); |
| 375 | moveToRestartQueue_l(nextStream); |
| 376 | } else { |
| 377 | addToActiveQueue_l(nextStream); |
| 378 | } |
| 379 | } else { |
| 380 | lock.lock(); |
| 381 | mAvailableStreams.insert(stream); |
| 382 | } |
| 383 | mProcessingStreams.erase(stream); |
| 384 | sanityCheckQueue_l(); |
| 385 | } |
| 386 | } |
| 387 | ALOGV("%s(%d) exiting", __func__, id); |
| 388 | } |
| 389 | |
| 390 | void StreamManager::dump() const |
| 391 | { |
| 392 | forEach([](const Stream *stream) { stream->dump(); }); |
| 393 | } |
| 394 | |
| 395 | void StreamManager::sanityCheckQueue_l() const |
| 396 | { |
| 397 | // We want to preserve the invariant that each stream pair is exactly on one of the queues. |
| 398 | const size_t availableStreams = mAvailableStreams.size(); |
| 399 | const size_t restartStreams = mRestartStreams.size(); |
| 400 | const size_t activeStreams = mActiveStreams.size(); |
| 401 | const size_t processingStreams = mProcessingStreams.size(); |
| 402 | const size_t managedStreams = availableStreams + restartStreams + activeStreams |
| 403 | + processingStreams; |
| 404 | const size_t totalStreams = getStreamMapSize() >> 1; |
| 405 | LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams, |
| 406 | "%s: mAvailableStreams:%zu + mRestartStreams:%zu + " |
| 407 | "mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu", |
| 408 | __func__, availableStreams, restartStreams, activeStreams, processingStreams, |
| 409 | managedStreams, totalStreams); |
| 410 | ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + " |
| 411 | "mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)", |
| 412 | __func__, availableStreams, restartStreams, activeStreams, processingStreams, |
| 413 | managedStreams, totalStreams); |
| 414 | } |
| 415 | |
| 416 | } // namespace android::soundpool |