blob: 212662fb6b96b6a59e433e1745e9424b5db8bdbc [file] [log] [blame]
/*
* 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(" -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 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) {
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) {
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 snoozeSec = 0;
int threadCount = 1;
for (int ch; (ch = getopt(argc, argv, "i:l:p: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 '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, 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;
}