1 /*
2  * Copyright (C) 2014 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"
19 
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <sys/stat.h>
23 
24 #include <atomic>
25 #include <future>
26 #include <mutex>
27 #include <set>
28 #include <vector>
29 
30 #include <audio_utils/clock.h>
31 #include <binder/ProcessState.h>
32 #include <media/stagefright/MediaExtractorFactory.h>
33 #include <soundpool/SoundPool.h> // direct include, this is not an NDK feature.
34 #include <system/audio.h>
35 #include <utils/Log.h>
36 
37 using namespace android;
38 
39 // Errors and diagnostic messages all go to stdout.
40 
41 namespace {
42 
usage(const char * name)43 void usage(const char *name)
44 {
45     printf("Usage: %s "
46             "[-i #iterations] [-l #loop] [-p #playback_seconds] [-s #streams] [-t #threads] "
47             "[-z #snoozeSec] <input-file>+\n", name);
48     printf("Uses soundpool to load and play a file (the first 10 seconds)\n");
49     printf("    -i #iterations, default 1\n");
50     printf("    -l #loop looping mode, -1 forever\n");
51     printf("    -p #playback_seconds, default 10\n");
52     printf("    -r #repeat soundIDs (0 or more times), default 0\n");
53     printf("    -s #streams for concurrent sound playback, default 20\n");
54     printf("    -t #threads, default 1\n");
55     printf("    -z #snoozeSec after stopping, -1 forever, default 0\n");
56     printf("    <input-file>+ files to be played\n");
57 }
58 
59 std::atomic_int32_t gErrors{};
60 std::atomic_int32_t gWarnings{};
61 
printEvent(const SoundPoolEvent * event)62 void printEvent(const SoundPoolEvent *event) {
63     printf("{ msg:%d  id:%d  status:%d }\n", event->mMsg, event->mArg1, event->mArg2);
64 }
65 
66 class CallbackManager {
67 public:
getNumberEvents(int32_t soundID)68     int32_t getNumberEvents(int32_t soundID) {
69         std::lock_guard lock(mLock);
70         return mEvents[soundID] > 0;
71     }
72 
setSoundPool(SoundPool * soundPool)73     void setSoundPool(SoundPool* soundPool) {
74         std::lock_guard lock(mLock);
75         mSoundPool = soundPool;
76     }
77 
callback(SoundPoolEvent event,const SoundPool * soundPool)78     void callback(SoundPoolEvent event, const SoundPool *soundPool) {
79         std::lock_guard lock(mLock);
80         printEvent(&event);
81         if (soundPool != mSoundPool) {
82             printf("ERROR: mismatched soundpool: %p\n", soundPool);
83             ++gErrors;
84             return;
85         }
86         if (event.mMsg != 1 /* SoundPoolEvent::SOUND_LOADED */) {
87             printf("ERROR: invalid event msg: %d\n", event.mMsg);
88             ++gErrors;
89             return;
90         }
91         if (event.mArg2 != 0) {
92             printf("ERROR: event status(%d) != 0\n", event.mArg2);
93             ++gErrors;
94             return;
95         }
96         if (event.mArg1 <= 0) {
97             printf("ERROR: event soundID(%d) < 0\n", event.mArg1);
98             ++gErrors;
99             return;
100         }
101         ++mEvents[event.mArg1];
102     }
103 
104 private:
105     std::mutex mLock;
106     SoundPool *mSoundPool = nullptr;
107     std::map<int32_t /* soundID */, int32_t /* count */> mEvents;
108 } gCallbackManager;
109 
110 
StaticCallbackManager(SoundPoolEvent event,SoundPool * soundPool,void * user)111 void StaticCallbackManager(SoundPoolEvent event, SoundPool* soundPool, void* user) {
112     ((CallbackManager *)user)->callback(event, soundPool);
113 }
114 
testStreams(SoundPool * soundPool,const std::vector<const char * > & filenames,int loop,int repeat,int playSec)115 void testStreams(SoundPool *soundPool, const std::vector<const char *> &filenames,
116                  int loop, int repeat, int playSec)
117 {
118     const int64_t startTimeNs = systemTime();
119     std::vector<int32_t> soundIDs;
120     for (auto filename : filenames) {
121         struct stat st;
122         if (stat(filename, &st) < 0) {
123             printf("ERROR: cannot stat %s\n", filename);
124             return;
125         }
126         const uint64_t length = uint64_t(st.st_size);
127         const int inp = open(filename, O_RDONLY);
128         if (inp < 0) {
129             printf("ERROR: cannot open %s\n", filename);
130             return;
131         }
132         printf("loading (%s) size (%llu)\n", filename, (unsigned long long)length);
133         const int32_t soundID = soundPool->load(
134                 inp, 0 /*offset*/, length, 0 /*priority - unused*/);
135         if (soundID == 0) {
136             printf("ERROR: cannot load %s\n", filename);
137             return;
138         }
139         close(inp);
140         soundIDs.emplace_back(soundID);
141         printf("loaded %s soundID(%d)\n", filename, soundID);
142     }
143     const int64_t requestLoadTimeNs = systemTime();
144     printf("\nrequestLoadTimeMs: %d\n",
145             (int)((requestLoadTimeNs - startTimeNs) / NANOS_PER_MILLISECOND));
146 
147     // create stream & get Id (playing)
148     const float maxVol = 1.f;
149     const float silentVol = 0.f;
150     const int priority = 0; // lowest
151     const float rate = 1.f;  // normal
152 
153     // Loading is done by a SoundPool Worker thread.
154     // TODO: Use SoundPool::setCallback() for wait
155 
156     for (int32_t soundID : soundIDs) {
157         for (int i = 0; i <= repeat; ++i) {
158             while (true) {
159                 const int32_t streamID =
160                     soundPool->play(soundID, silentVol, silentVol, priority, 0 /*loop*/, rate);
161                 if (streamID != 0) {
162                     const int32_t events = gCallbackManager.getNumberEvents(soundID);
163                     if (events != 1) {
164                        printf("WARNING: successful play for streamID:%d soundID:%d"
165                               " but callback events(%d) != 1\n", streamID, soundID, events);
166                        ++gWarnings;
167                     }
168                     soundPool->stop(streamID);
169                     break;
170                 }
171                 usleep(1000);
172             }
173             printf("[%d]", soundID);
174             fflush(stdout);
175         }
176     }
177 
178     const int64_t loadTimeNs = systemTime();
179     printf("\nloadTimeMs: %d\n", (int)((loadTimeNs - startTimeNs) / NANOS_PER_MILLISECOND));
180 
181     // check and play (overlap with above).
182     std::vector<int32_t> streamIDs;
183     for (int32_t soundID : soundIDs) {
184         for (int i = 0; i <= repeat; ++i) {
185             printf("\nplaying soundID=%d", soundID);
186             const int32_t streamID =
187                     soundPool->play(soundID, maxVol, maxVol, priority, loop, rate);
188             if (streamID == 0) {
189                 printf(" failed!  ERROR");
190                 ++gErrors;
191             } else {
192                 printf(" streamID=%d", streamID);
193                 streamIDs.emplace_back(streamID);
194             }
195         }
196     }
197     const int64_t playTimeNs = systemTime();
198     printf("\nplayTimeMs: %d\n", (int)((playTimeNs - loadTimeNs) / NANOS_PER_MILLISECOND));
199 
200     for (int i = 0; i < playSec; ++i) {
201         sleep(1);
202         printf(".");
203         fflush(stdout);
204     }
205 
206     for (int32_t streamID : streamIDs) {
207         soundPool->stop(streamID);
208     }
209 
210     for (int32_t soundID : soundIDs) {
211         soundPool->unload(soundID);
212     }
213     printf("\nDone!\n");
214 }
215 
216 } // namespace
217 
main(int argc,char * argv[])218 int main(int argc, char *argv[])
219 {
220     const char * const me = argv[0];
221 
222     int iterations = 1;
223     int loop = 0;        // disable looping
224     int maxStreams = 40; // change to have more concurrent playback streams
225     int playSec = 10;
226     int repeat = 0;
227     int snoozeSec = 0;
228     int threadCount = 1;
229     for (int ch; (ch = getopt(argc, argv, "i:l:p:r:s:t:z:")) != -1; ) {
230         switch (ch) {
231         case 'i':
232             iterations = atoi(optarg);
233             break;
234         case 'l':
235             loop = atoi(optarg);
236             break;
237         case 'p':
238             playSec = atoi(optarg);
239             break;
240         case 'r':
241             repeat = atoi(optarg);
242             break;
243         case 's':
244             maxStreams = atoi(optarg);
245             break;
246         case 't':
247             threadCount = atoi(optarg);
248             break;
249         case 'z':
250             snoozeSec = atoi(optarg);
251             break;
252         default:
253             usage(me);
254             return EXIT_FAILURE;
255         }
256     }
257 
258     argc -= optind;
259     argv += optind;
260     if (argc <= 0) {
261         usage(me);
262         return EXIT_FAILURE;
263     }
264 
265     std::vector<const char *> filenames(argv, argv + argc);
266 
267     android::ProcessState::self()->startThreadPool();
268 
269     // O and later requires data sniffer registration for proper file type detection
270     MediaExtractorFactory::LoadExtractors();
271 
272     // create soundpool
273     audio_attributes_t aa = {
274         .content_type = AUDIO_CONTENT_TYPE_MUSIC,
275         .usage = AUDIO_USAGE_MEDIA,
276     };
277     auto soundPool = std::make_unique<SoundPool>(maxStreams, aa);
278 
279     gCallbackManager.setSoundPool(soundPool.get());
280     soundPool->setCallback(StaticCallbackManager, &gCallbackManager);
281 
282     const int64_t startTimeNs = systemTime();
283 
284     for (int it = 0; it < iterations; ++it) {
285         // One instance:
286         // testStreams(soundPool.get(), filenames, loop, playSec);
287 
288         // Test multiple instances
289         std::vector<std::future<void>> threads(threadCount);
290         printf("testing %zu threads\n", threads.size());
291         for (auto &thread : threads) {
292             thread = std::async(std::launch::async,
293                     [&]{ testStreams(soundPool.get(), filenames, loop, repeat, playSec);});
294         }
295         // automatically joins.
296     }
297 
298     const int64_t endTimeNs = systemTime();
299 
300     // snooze before cleaning up to examine soundpool dumpsys state after stop
301     for (int i = 0; snoozeSec < 0 || i < snoozeSec; ++i) {
302         printf("z");
303         fflush(stdout);
304         sleep(1);
305     };
306 
307     gCallbackManager.setSoundPool(nullptr);
308     soundPool.reset();
309 
310     printf("total time in ms: %lld\n", (endTimeNs - startTimeNs) / NANOS_PER_MILLISECOND);
311     if (gWarnings != 0) {
312         printf("%d warnings!\n", gWarnings.load());
313     }
314     if (gErrors != 0) {
315         printf("%d errors!\n", gErrors.load());
316         return EXIT_FAILURE;
317     }
318     return EXIT_SUCCESS;
319 }
320