337 lines
11 KiB
C++
337 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2017 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.
|
|
*/
|
|
|
|
// Try to trigger bugs by playing randomly on multiple streams.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <vector>
|
|
|
|
#include <aaudio/AAudio.h>
|
|
#include "AAudioArgsParser.h"
|
|
#include "AAudioExampleUtils.h"
|
|
#include "AAudioSimplePlayer.h"
|
|
#include "SineGenerator.h"
|
|
|
|
#define DEFAULT_TIMEOUT_NANOS (1 * NANOS_PER_SECOND)
|
|
|
|
#define NUM_LOOPS 1000
|
|
#define MAX_MICROS_DELAY (2 * 1000 * 1000)
|
|
|
|
// TODO Consider adding an input stream.
|
|
#define PROB_START (0.20)
|
|
#define PROB_PAUSE (PROB_START + 0.10)
|
|
#define PROB_FLUSH (PROB_PAUSE + 0.10)
|
|
#define PROB_STOP (PROB_FLUSH + 0.10)
|
|
#define PROB_CLOSE (PROB_STOP + 0.10)
|
|
static_assert(PROB_CLOSE < 0.9, "Probability sum too high.");
|
|
|
|
aaudio_data_callback_result_t AAudioMonkeyDataCallback(
|
|
AAudioStream *stream,
|
|
void *userData,
|
|
void *audioData,
|
|
int32_t numFrames);
|
|
|
|
void AAudioMonkeyErrorCallbackProc(
|
|
AAudioStream * /* stream */,
|
|
void *userData,
|
|
aaudio_result_t error);
|
|
|
|
|
|
// This function is not thread safe. Only use this from a single thread.
|
|
double nextRandomDouble() {
|
|
return drand48();
|
|
}
|
|
|
|
class AAudioMonkey : public AAudioSimplePlayer {
|
|
public:
|
|
|
|
AAudioMonkey(int index, AAudioArgsParser *argParser)
|
|
: mArgParser(argParser)
|
|
, mIndex(index) {}
|
|
|
|
aaudio_result_t open() {
|
|
printf("Monkey # %d ---------------------------------------------- OPEN\n", mIndex);
|
|
double offset = mIndex * 50;
|
|
mSine1.setup(440.0, 48000);
|
|
mSine1.setSweep(300.0 + offset, 600.0 + offset, 5.0);
|
|
mSine2.setup(660.0, 48000);
|
|
mSine2.setSweep(350.0 + offset, 900.0 + offset, 7.0);
|
|
|
|
aaudio_result_t result = AAudioSimplePlayer::open(*mArgParser,
|
|
AAudioMonkeyDataCallback,
|
|
AAudioMonkeyErrorCallbackProc,
|
|
this);
|
|
if (result != AAUDIO_OK) {
|
|
printf("ERROR - player.open() returned %d\n", result);
|
|
}
|
|
|
|
mArgParser->compareWithStream(getStream());
|
|
return result;
|
|
}
|
|
|
|
bool isOpen() {
|
|
return (getStream() != nullptr);
|
|
|
|
}
|
|
/**
|
|
*
|
|
* @return true if stream passes tests
|
|
*/
|
|
bool validate() {
|
|
if (!isOpen()) return true; // closed is OK
|
|
|
|
// update and query stream state
|
|
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
|
|
aaudio_result_t result = AAudioStream_waitForStateChange(getStream(),
|
|
AAUDIO_STREAM_STATE_UNKNOWN, &state, 0);
|
|
if (result == AAUDIO_ERROR_DISCONNECTED) {
|
|
printf("WARNING - AAudioStream_waitForStateChange returned DISCONNECTED\n");
|
|
return true; // OK
|
|
}
|
|
if (result != AAUDIO_OK) {
|
|
printf("ERROR - AAudioStream_waitForStateChange returned %d\n", result);
|
|
return false;
|
|
}
|
|
|
|
int64_t framesRead = AAudioStream_getFramesRead(getStream());
|
|
int64_t framesWritten = AAudioStream_getFramesWritten(getStream());
|
|
int32_t xRuns = AAudioStream_getXRunCount(getStream());
|
|
// Print status
|
|
printf("%30s, framesWritten = %8lld, framesRead = %8lld, xRuns = %d\n",
|
|
AAudio_convertStreamStateToText(state),
|
|
(unsigned long long) framesWritten,
|
|
(unsigned long long) framesRead,
|
|
xRuns);
|
|
|
|
if (state != AAUDIO_STREAM_STATE_STARTING && framesWritten < framesRead) {
|
|
printf("WARNING - UNDERFLOW - diff = %d !!!!!!!!!!!!\n",
|
|
(int) (framesWritten - framesRead));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
aaudio_result_t invoke() {
|
|
aaudio_result_t result = AAUDIO_OK;
|
|
if (!isOpen()) {
|
|
result = open();
|
|
if (result != AAUDIO_OK) return result;
|
|
}
|
|
|
|
if (!validate()) {
|
|
return -1;
|
|
}
|
|
|
|
// update and query stream state
|
|
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
|
|
state = AAudioStream_getState(getStream());
|
|
if (state < 0) {
|
|
printf("ERROR - AAudioStream_getState returned %d\n", state);
|
|
return state;
|
|
}
|
|
|
|
if (state == AAUDIO_STREAM_STATE_DISCONNECTED) {
|
|
printf("#%d, Closing disconnected stream.\n", getIndex());
|
|
result = close();
|
|
return result;
|
|
}
|
|
|
|
double dice = nextRandomDouble();
|
|
// Select an action based on a weighted probability.
|
|
printf(" "); // indent action
|
|
if (dice < PROB_START) {
|
|
printf("start\n");
|
|
result = AAudioStream_requestStart(getStream());
|
|
} else if (dice < PROB_PAUSE) {
|
|
printf("pause\n");
|
|
result = AAudioStream_requestPause(getStream());
|
|
} else if (dice < PROB_FLUSH) {
|
|
printf("flush\n");
|
|
result = AAudioStream_requestFlush(getStream());
|
|
} else if (dice < PROB_STOP) {
|
|
printf("stop\n");
|
|
result = AAudioStream_requestStop(getStream());
|
|
} else if (dice < PROB_CLOSE) {
|
|
printf("close\n");
|
|
result = close();
|
|
} else {
|
|
printf("do nothing\n");
|
|
}
|
|
|
|
if (result == AAUDIO_ERROR_INVALID_STATE) {
|
|
printf(" got AAUDIO_ERROR_INVALID_STATE - expected from a monkey\n");
|
|
result = AAUDIO_OK;
|
|
}
|
|
if (result == AAUDIO_OK && isOpen()) {
|
|
if (!validate()) {
|
|
result = -1;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
aaudio_data_callback_result_t renderAudio(
|
|
AAudioStream *stream,
|
|
void *audioData,
|
|
int32_t numFrames) {
|
|
|
|
int32_t samplesPerFrame = AAudioStream_getChannelCount(stream);
|
|
// This code only plays on the first one or two channels.
|
|
// TODO Support arbitrary number of channels.
|
|
switch (AAudioStream_getFormat(stream)) {
|
|
case AAUDIO_FORMAT_PCM_I16: {
|
|
int16_t *audioBuffer = (int16_t *) audioData;
|
|
// Render sine waves as shorts to first channel.
|
|
mSine1.render(&audioBuffer[0], samplesPerFrame, numFrames);
|
|
// Render sine waves to second channel if there is one.
|
|
if (samplesPerFrame > 1) {
|
|
mSine2.render(&audioBuffer[1], samplesPerFrame, numFrames);
|
|
}
|
|
}
|
|
break;
|
|
case AAUDIO_FORMAT_PCM_FLOAT: {
|
|
float *audioBuffer = (float *) audioData;
|
|
// Render sine waves as floats to first channel.
|
|
mSine1.render(&audioBuffer[0], samplesPerFrame, numFrames);
|
|
// Render sine waves to second channel if there is one.
|
|
if (samplesPerFrame > 1) {
|
|
mSine2.render(&audioBuffer[1], samplesPerFrame, numFrames);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return AAUDIO_CALLBACK_RESULT_STOP;
|
|
}
|
|
return AAUDIO_CALLBACK_RESULT_CONTINUE;
|
|
}
|
|
|
|
int getIndex() const {
|
|
return mIndex;
|
|
}
|
|
|
|
private:
|
|
const AAudioArgsParser *mArgParser;
|
|
const int mIndex;
|
|
SineGenerator mSine1;
|
|
SineGenerator mSine2;
|
|
};
|
|
|
|
// Callback function that fills the audio output buffer.
|
|
aaudio_data_callback_result_t AAudioMonkeyDataCallback(
|
|
AAudioStream *stream,
|
|
void *userData,
|
|
void *audioData,
|
|
int32_t numFrames
|
|
) {
|
|
// should not happen but just in case...
|
|
if (userData == nullptr) {
|
|
printf("ERROR - AAudioMonkeyDataCallback needs userData\n");
|
|
return AAUDIO_CALLBACK_RESULT_STOP;
|
|
}
|
|
AAudioMonkey *monkey = (AAudioMonkey *) userData;
|
|
return monkey->renderAudio(stream, audioData, numFrames);
|
|
}
|
|
|
|
void AAudioMonkeyErrorCallbackProc(
|
|
AAudioStream * /* stream */,
|
|
void *userData,
|
|
aaudio_result_t error) {
|
|
AAudioMonkey *monkey = (AAudioMonkey *) userData;
|
|
printf("#%d, Error Callback, error: %d\n", monkey->getIndex(), (int)error);
|
|
}
|
|
|
|
static void usage() {
|
|
AAudioArgsParser::usage();
|
|
printf(" -i{seed} Initial random seed\n");
|
|
printf(" -t{count} number of monkeys in the Troop\n");
|
|
}
|
|
|
|
int main(int argc, const char **argv) {
|
|
AAudioArgsParser argParser;
|
|
std::vector<AAudioMonkey> monkeys;
|
|
aaudio_result_t result;
|
|
int numMonkeys = 1;
|
|
|
|
// Make printf print immediately so that debug info is not stuck
|
|
// in a buffer if we hang or crash.
|
|
setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
|
|
|
|
printf("%s - Monkeys\n", argv[0]);
|
|
|
|
long int seed = (long int)getNanoseconds(); // different every time by default
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
const char *arg = argv[i];
|
|
if (argParser.parseArg(arg)) {
|
|
// Handle options that are not handled by the ArgParser
|
|
if (arg[0] == '-') {
|
|
char option = arg[1];
|
|
switch (option) {
|
|
case 'i':
|
|
seed = atol(&arg[2]);
|
|
break;
|
|
case 't':
|
|
numMonkeys = atoi(&arg[2]);
|
|
break;
|
|
default:
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
}
|
|
} else {
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
srand48(seed);
|
|
printf("seed = %ld, nextRandomDouble() = %f\n", seed, nextRandomDouble());
|
|
|
|
for (int m = 0; m < numMonkeys; m++) {
|
|
monkeys.emplace_back(m, &argParser);
|
|
}
|
|
|
|
for (int i = 0; i < NUM_LOOPS; i++) {
|
|
// pick a random monkey and invoke it
|
|
double dice = nextRandomDouble();
|
|
int monkeyIndex = floor(dice * numMonkeys);
|
|
printf("----------- Monkey #%d\n", monkeyIndex);
|
|
result = monkeys[monkeyIndex].invoke();
|
|
if (result != AAUDIO_OK) {
|
|
goto error;
|
|
}
|
|
|
|
// sleep some random time
|
|
dice = nextRandomDouble();
|
|
dice = dice * dice * dice; // skew towards smaller delays
|
|
int micros = (int) (dice * MAX_MICROS_DELAY);
|
|
usleep(micros);
|
|
|
|
// TODO consider making this multi-threaded, one thread per monkey, to catch more bugs
|
|
}
|
|
|
|
printf("PASS\n");
|
|
return EXIT_SUCCESS;
|
|
|
|
error:
|
|
printf("FAIL - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
|
|
usleep(1000 * 1000); // give me time to stop the logcat
|
|
return EXIT_FAILURE;
|
|
}
|
|
|