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