| /* |
| * Copyright (C) 2014 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" |
| |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| |
| #include <atomic> |
| #include <future> |
| #include <mutex> |
| #include <set> |
| #include <vector> |
| |
| #include <audio_utils/clock.h> |
| #include <binder/ProcessState.h> |
| #include <media/stagefright/MediaExtractorFactory.h> |
| #include <soundpool/SoundPool.h> // direct include, this is not an NDK feature. |
| #include <system/audio.h> |
| #include <utils/Log.h> |
| |
| using namespace android; |
| |
| // Errors and diagnostic messages all go to stdout. |
| |
| namespace { |
| |
| void usage(const char *name) |
| { |
| printf("Usage: %s " |
| "[-i #iterations] [-l #loop] [-p #playback_seconds] [-s #streams] [-t #threads] " |
| "[-z #snoozeSec] <input-file>+\n", name); |
| printf("Uses soundpool to load and play a file (the first 10 seconds)\n"); |
| printf(" -i #iterations, default 1\n"); |
| printf(" -l #loop looping mode, -1 forever\n"); |
| printf(" -p #playback_seconds, default 10\n"); |
| printf(" -r #repeat soundIDs (0 or more times), default 0\n"); |
| printf(" -s #streams for concurrent sound playback, default 20\n"); |
| printf(" -t #threads, default 1\n"); |
| printf(" -z #snoozeSec after stopping, -1 forever, default 0\n"); |
| printf(" <input-file>+ files to be played\n"); |
| } |
| |
| std::atomic_int32_t gErrors{}; |
| std::atomic_int32_t gWarnings{}; |
| |
| void printEvent(const SoundPoolEvent *event) { |
| printf("{ msg:%d id:%d status:%d }\n", event->mMsg, event->mArg1, event->mArg2); |
| } |
| |
| class CallbackManager { |
| public: |
| int32_t getNumberEvents(int32_t soundID) { |
| std::lock_guard lock(mLock); |
| return mEvents[soundID] > 0; |
| } |
| |
| void setSoundPool(SoundPool* soundPool) { |
| std::lock_guard lock(mLock); |
| mSoundPool = soundPool; |
| } |
| |
| void callback(SoundPoolEvent event, const SoundPool *soundPool) { |
| std::lock_guard lock(mLock); |
| printEvent(&event); |
| if (soundPool != mSoundPool) { |
| printf("ERROR: mismatched soundpool: %p\n", soundPool); |
| ++gErrors; |
| return; |
| } |
| if (event.mMsg != 1 /* SoundPoolEvent::SOUND_LOADED */) { |
| printf("ERROR: invalid event msg: %d\n", event.mMsg); |
| ++gErrors; |
| return; |
| } |
| if (event.mArg2 != 0) { |
| printf("ERROR: event status(%d) != 0\n", event.mArg2); |
| ++gErrors; |
| return; |
| } |
| if (event.mArg1 <= 0) { |
| printf("ERROR: event soundID(%d) < 0\n", event.mArg1); |
| ++gErrors; |
| return; |
| } |
| ++mEvents[event.mArg1]; |
| } |
| |
| private: |
| std::mutex mLock; |
| SoundPool *mSoundPool = nullptr; |
| std::map<int32_t /* soundID */, int32_t /* count */> mEvents; |
| } gCallbackManager; |
| |
| |
| void StaticCallbackManager(SoundPoolEvent event, SoundPool* soundPool, void* user) { |
| ((CallbackManager *)user)->callback(event, soundPool); |
| } |
| |
| void testStreams(SoundPool *soundPool, const std::vector<const char *> &filenames, |
| int loop, int repeat, int playSec) |
| { |
| const int64_t startTimeNs = systemTime(); |
| std::vector<int32_t> soundIDs; |
| for (auto filename : filenames) { |
| struct stat st; |
| if (stat(filename, &st) < 0) { |
| printf("ERROR: cannot stat %s\n", filename); |
| return; |
| } |
| const uint64_t length = uint64_t(st.st_size); |
| const int inp = open(filename, O_RDONLY); |
| if (inp < 0) { |
| printf("ERROR: cannot open %s\n", filename); |
| return; |
| } |
| printf("loading (%s) size (%llu)\n", filename, (unsigned long long)length); |
| const int32_t soundID = soundPool->load( |
| inp, 0 /*offset*/, length, 0 /*priority - unused*/); |
| if (soundID == 0) { |
| printf("ERROR: cannot load %s\n", filename); |
| return; |
| } |
| close(inp); |
| soundIDs.emplace_back(soundID); |
| printf("loaded %s soundID(%d)\n", filename, soundID); |
| } |
| const int64_t requestLoadTimeNs = systemTime(); |
| printf("\nrequestLoadTimeMs: %d\n", |
| (int)((requestLoadTimeNs - startTimeNs) / NANOS_PER_MILLISECOND)); |
| |
| // create stream & get Id (playing) |
| const float maxVol = 1.f; |
| const float silentVol = 0.f; |
| const int priority = 0; // lowest |
| const float rate = 1.f; // normal |
| |
| // Loading is done by a SoundPool Worker thread. |
| // TODO: Use SoundPool::setCallback() for wait |
| |
| for (int32_t soundID : soundIDs) { |
| for (int i = 0; i <= repeat; ++i) { |
| while (true) { |
| const int32_t streamID = |
| soundPool->play(soundID, silentVol, silentVol, priority, 0 /*loop*/, rate); |
| if (streamID != 0) { |
| const int32_t events = gCallbackManager.getNumberEvents(soundID); |
| if (events != 1) { |
| printf("WARNING: successful play for streamID:%d soundID:%d" |
| " but callback events(%d) != 1\n", streamID, soundID, events); |
| ++gWarnings; |
| } |
| soundPool->stop(streamID); |
| break; |
| } |
| usleep(1000); |
| } |
| printf("[%d]", soundID); |
| fflush(stdout); |
| } |
| } |
| |
| const int64_t loadTimeNs = systemTime(); |
| printf("\nloadTimeMs: %d\n", (int)((loadTimeNs - startTimeNs) / NANOS_PER_MILLISECOND)); |
| |
| // check and play (overlap with above). |
| std::vector<int32_t> streamIDs; |
| for (int32_t soundID : soundIDs) { |
| for (int i = 0; i <= repeat; ++i) { |
| printf("\nplaying soundID=%d", soundID); |
| const int32_t streamID = |
| soundPool->play(soundID, maxVol, maxVol, priority, loop, rate); |
| if (streamID == 0) { |
| printf(" failed! ERROR"); |
| ++gErrors; |
| } else { |
| printf(" streamID=%d", streamID); |
| streamIDs.emplace_back(streamID); |
| } |
| } |
| } |
| const int64_t playTimeNs = systemTime(); |
| printf("\nplayTimeMs: %d\n", (int)((playTimeNs - loadTimeNs) / NANOS_PER_MILLISECOND)); |
| |
| for (int i = 0; i < playSec; ++i) { |
| sleep(1); |
| printf("."); |
| fflush(stdout); |
| } |
| |
| for (int32_t streamID : streamIDs) { |
| soundPool->stop(streamID); |
| } |
| |
| for (int32_t soundID : soundIDs) { |
| soundPool->unload(soundID); |
| } |
| printf("\nDone!\n"); |
| } |
| |
| } // namespace |
| |
| int main(int argc, char *argv[]) |
| { |
| const char * const me = argv[0]; |
| |
| int iterations = 1; |
| int loop = 0; // disable looping |
| int maxStreams = 40; // change to have more concurrent playback streams |
| int playSec = 10; |
| int repeat = 0; |
| int snoozeSec = 0; |
| int threadCount = 1; |
| for (int ch; (ch = getopt(argc, argv, "i:l:p:r:s:t:z:")) != -1; ) { |
| switch (ch) { |
| case 'i': |
| iterations = atoi(optarg); |
| break; |
| case 'l': |
| loop = atoi(optarg); |
| break; |
| case 'p': |
| playSec = atoi(optarg); |
| break; |
| case 'r': |
| repeat = atoi(optarg); |
| break; |
| case 's': |
| maxStreams = atoi(optarg); |
| break; |
| case 't': |
| threadCount = atoi(optarg); |
| break; |
| case 'z': |
| snoozeSec = atoi(optarg); |
| break; |
| default: |
| usage(me); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| if (argc <= 0) { |
| usage(me); |
| return EXIT_FAILURE; |
| } |
| |
| std::vector<const char *> filenames(argv, argv + argc); |
| |
| android::ProcessState::self()->startThreadPool(); |
| |
| // O and later requires data sniffer registration for proper file type detection |
| MediaExtractorFactory::LoadExtractors(); |
| |
| // create soundpool |
| audio_attributes_t aa = { |
| .content_type = AUDIO_CONTENT_TYPE_MUSIC, |
| .usage = AUDIO_USAGE_MEDIA, |
| }; |
| auto soundPool = std::make_unique<SoundPool>(maxStreams, aa); |
| |
| gCallbackManager.setSoundPool(soundPool.get()); |
| soundPool->setCallback(StaticCallbackManager, &gCallbackManager); |
| |
| const int64_t startTimeNs = systemTime(); |
| |
| for (int it = 0; it < iterations; ++it) { |
| // One instance: |
| // testStreams(soundPool.get(), filenames, loop, playSec); |
| |
| // Test multiple instances |
| std::vector<std::future<void>> threads(threadCount); |
| printf("testing %zu threads\n", threads.size()); |
| for (auto &thread : threads) { |
| thread = std::async(std::launch::async, |
| [&]{ testStreams(soundPool.get(), filenames, loop, repeat, playSec);}); |
| } |
| // automatically joins. |
| } |
| |
| const int64_t endTimeNs = systemTime(); |
| |
| // snooze before cleaning up to examine soundpool dumpsys state after stop |
| for (int i = 0; snoozeSec < 0 || i < snoozeSec; ++i) { |
| printf("z"); |
| fflush(stdout); |
| sleep(1); |
| }; |
| |
| gCallbackManager.setSoundPool(nullptr); |
| soundPool.reset(); |
| |
| printf("total time in ms: %lld\n", (endTimeNs - startTimeNs) / NANOS_PER_MILLISECOND); |
| if (gWarnings != 0) { |
| printf("%d warnings!\n", gWarnings.load()); |
| } |
| if (gErrors != 0) { |
| printf("%d errors!\n", gErrors.load()); |
| return EXIT_FAILURE; |
| } |
| return EXIT_SUCCESS; |
| } |