Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 67ed9daf authored by Phil Burk's avatar Phil Burk
Browse files

aaudio example: loop and add noise blip at start

Loop while starting and stopping or pausing/flushing.
Check framesWritten - framesRead.

Blip so we can tell if the beginning of the sound was cut off.
Add -t option for the high pitched prefix tone.

Test: this is a test
Change-Id: I2a642be67f4e80caad015254d006a0153b46e3f7
parent 5b769acf
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
#ifndef AAUDIO_EXAMPLE_ARGS_PARSER_H
#define AAUDIO_EXAMPLE_ARGS_PARSER_H

#define MAX_CHANNELS                     8

#include <cctype>
#include <unistd.h>
#include <stdio.h>
@@ -39,6 +41,10 @@ public:
    }

    void setChannelCount(int32_t channelCount) {
        if (channelCount > MAX_CHANNELS) {
            printf("Sorry, MAX of %d channels!\n", MAX_CHANNELS);
            channelCount = MAX_CHANNELS;
        }
        mChannelCount = channelCount;
    }

+81 −39
Original line number Diff line number Diff line
@@ -19,11 +19,10 @@
#ifndef AAUDIO_SIMPLE_PLAYER_H
#define AAUDIO_SIMPLE_PLAYER_H

#include <unistd.h>
#include <sched.h>
#include <unistd.h>

#include <aaudio/AAudio.h>
#include <atomic>
#include "AAudioArgsParser.h"
#include "SineGenerator.h"

@@ -70,13 +69,6 @@ public:
    }

    // TODO Extract a common base class for record and playback.
    /**
     * Also known as "sample rate"
     * Only call this after open() has been called.
     */
    int32_t getFramesPerSecond() const {
        return getSampleRate(); // alias
    }

    /**
     * Only call this after open() has been called.
@@ -172,6 +164,7 @@ public:
        result = AAudioStreamBuilder_openStream(builder, &mStream);

        AAudioStreamBuilder_delete(builder);

        return result;
    }

@@ -219,6 +212,28 @@ public:
        return result;
    }

    // Pause the stream. AAudio will stop calling your callback function.
    aaudio_result_t pause() {
        aaudio_result_t result = AAudioStream_requestPause(mStream);
        if (result != AAUDIO_OK) {
            printf("ERROR - AAudioStream_requestPause() returned %d %s\n",
                   result, AAudio_convertResultToText(result));
        }
        int32_t xRunCount = AAudioStream_getXRunCount(mStream);
        printf("AAudioStream_getXRunCount %d\n", xRunCount);
        return result;
    }

    // Flush the stream. AAudio will stop calling your callback function.
    aaudio_result_t flush() {
        aaudio_result_t result = AAudioStream_requestFlush(mStream);
        if (result != AAUDIO_OK) {
            printf("ERROR - AAudioStream_requestFlush() returned %d %s\n",
                   result, AAudio_convertResultToText(result));
        }
        return result;
    }

    AAudioStream *getStream() const {
        return mStream;
    }
@@ -232,14 +247,16 @@ private:

typedef struct SineThreadedData_s {

    SineGenerator  sineOsc1;
    SineGenerator  sineOsc2;
    SineGenerator      sineOscillators[MAX_CHANNELS];
    Timestamp          timestamps[MAX_TIMESTAMPS];
    int64_t            framesTotal = 0;
    int64_t            nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES;
    int32_t            minNumFrames = INT32_MAX;
    int32_t            maxNumFrames = 0;
    int32_t            timestampCount = 0; // in timestamps
    int32_t            sampleRate = 48000;
    int32_t            prefixToneFrames = 0;
    bool               sweepSetup = false;

    int                scheduler = 0;
    bool               schedulerChecked = false;
@@ -249,6 +266,30 @@ typedef struct SineThreadedData_s {
    int32_t            callbackCount = 0;
    WakeUp             waker{AAUDIO_OK};

    /**
     * Set sampleRate first.
     */
    void setupSineBlip() {
        for (int i = 0; i < MAX_CHANNELS; ++i) {
            double centerFrequency = 880.0 * (i + 2);
            sineOscillators[i].setup(centerFrequency, sampleRate);
            sineOscillators[i].setSweep(centerFrequency, centerFrequency, 0.0);
        }
    }

    void setupSineSweeps() {
        for (int i = 0; i < MAX_CHANNELS; ++i) {
            double centerFrequency = 220.0 * (i + 2);
            sineOscillators[i].setup(centerFrequency, sampleRate);
            double minFrequency = centerFrequency * 2.0 / 3.0;
            // Change range slightly so they will go out of phase.
            double maxFrequency = centerFrequency * 3.0 / 2.0;
            double sweepSeconds = 5.0 + i;
            sineOscillators[i].setSweep(minFrequency, maxFrequency, sweepSeconds);
        }
        sweepSetup = true;
    }

} SineThreadedData_t;

// Callback function that fills the audio output buffer.
@@ -265,9 +306,11 @@ aaudio_data_callback_result_t SimplePlayerDataCallbackProc(
        return AAUDIO_CALLBACK_RESULT_STOP;
    }
    SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
    sineData->callbackCount++;

    sineData->framesTotal += numFrames;
    // Play an initial high tone so we can tell whether the beginning was truncated.
    if (!sineData->sweepSetup && sineData->framesTotal >= sineData->prefixToneFrames) {
        sineData->setupSineSweeps();
    }

    if (sineData->forceUnderruns) {
        if (sineData->framesTotal > sineData->nextFrameToGlitch) {
@@ -301,26 +344,23 @@ aaudio_data_callback_result_t SimplePlayerDataCallbackProc(
    }

    int32_t samplesPerFrame = AAudioStream_getChannelCount(stream);
    // This code only plays on the first one or two channels.
    // TODO Support arbitrary number of channels.


    int numActiveOscilators = (samplesPerFrame > MAX_CHANNELS) ? MAX_CHANNELS : samplesPerFrame;
    switch (AAudioStream_getFormat(stream)) {
        case AAUDIO_FORMAT_PCM_I16: {
            int16_t *audioBuffer = (int16_t *) audioData;
            // Render sine waves as shorts to first channel.
            sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames);
            // Render sine waves to second channel if there is one.
            if (samplesPerFrame > 1) {
                sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames);
            for (int i = 0; i < numActiveOscilators; ++i) {
                sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame,
                                                    numFrames);
            }
        }
            break;
        case AAUDIO_FORMAT_PCM_FLOAT: {
            float *audioBuffer = (float *) audioData;
            // Render sine waves as floats to first channel.
            sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames);
            // Render sine waves to second channel if there is one.
            if (samplesPerFrame > 1) {
                sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames);
            for (int i = 0; i < numActiveOscilators; ++i) {
                sineData->sineOscillators[i].render(&audioBuffer[i], samplesPerFrame,
                                                    numFrames);
            }
        }
            break;
@@ -328,6 +368,8 @@ aaudio_data_callback_result_t SimplePlayerDataCallbackProc(
            return AAUDIO_CALLBACK_RESULT_STOP;
    }

    sineData->callbackCount++;
    sineData->framesTotal += numFrames;
    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

+10 −9
Original line number Diff line number Diff line
@@ -31,20 +31,20 @@ public:
    }

    void setSweep(double frequencyLow, double frequencyHigh, double seconds) {
        mSweeping = seconds > 0.0;
        if (mSweeping) {
            mPhaseIncrementLow = frequencyLow * M_PI * 2 / mFrameRate;
            mPhaseIncrementHigh = frequencyHigh * M_PI * 2 / mFrameRate;

            double numFrames = seconds * mFrameRate;
            mUpScaler = pow((frequencyHigh / frequencyLow), (1.0 / numFrames));
            mDownScaler = 1.0 / mUpScaler;
        mGoingUp = true;
        mSweeping = true;
        }
    }

    void render(int16_t *buffer, int32_t channelStride, int32_t numFrames) {
        int sampleIndex = 0;
        for (int i = 0; i < numFrames; i++) {
            buffer[sampleIndex] = (int16_t) (32767 * sin(mPhase) * mAmplitude);
            buffer[sampleIndex] = (int16_t) (INT16_MAX * sin(mPhase) * mAmplitude);
            sampleIndex += channelStride;
            advancePhase();
        }
@@ -61,6 +61,7 @@ public:
    void setAmplitude(double amplitude) {
        mAmplitude = amplitude;
    }

    double getAmplitude() const {
        return mAmplitude;
    }
+11 −11
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ int main(int argc, const char **argv)
    int32_t  framesToPlay = 0;
    int32_t  framesLeft = 0;
    int32_t  xRunCount = 0;
    int      numActiveOscilators = 0;
    float   *floatData = nullptr;
    int16_t *shortData = nullptr;

@@ -77,8 +78,8 @@ int main(int argc, const char **argv)
    actualSampleRate = AAudioStream_getSampleRate(aaudioStream);
    actualDataFormat = AAudioStream_getFormat(aaudioStream);

    myData.sineOsc1.setup(440.0, actualSampleRate);
    myData.sineOsc2.setup(660.0, actualSampleRate);
    myData.sampleRate = actualSampleRate;
    myData.setupSineSweeps();

    // Some DMA might use very short bursts of 16 frames. We don't need to write such small
    // buffers. But it helps to use a multiple of the burst size for predictable scheduling.
@@ -117,19 +118,18 @@ int main(int argc, const char **argv)
    // Play for a while.
    framesToPlay = actualSampleRate * argParser.getDurationSeconds();
    framesLeft = framesToPlay;
    numActiveOscilators = (actualChannelCount > MAX_CHANNELS) ? MAX_CHANNELS : actualChannelCount;
    while (framesLeft > 0) {

        // Render as FLOAT or PCM
        if (actualDataFormat == AAUDIO_FORMAT_PCM_FLOAT) {
            // Render sine waves to left and right channels.
            myData.sineOsc1.render(&floatData[0], actualChannelCount, framesPerWrite);
            if (actualChannelCount > 1) {
                myData.sineOsc2.render(&floatData[1], actualChannelCount, framesPerWrite);
            for (int i = 0; i < numActiveOscilators; ++i) {
                myData.sineOscillators[i].render(&floatData[i], actualChannelCount,
                                                  framesPerWrite);
            }
        } else if (actualDataFormat == AAUDIO_FORMAT_PCM_I16) {
            // Render sine waves to left and right channels.
            myData.sineOsc1.render(&shortData[0], actualChannelCount, framesPerWrite);
            if (actualChannelCount > 1) {
                myData.sineOsc2.render(&shortData[1], actualChannelCount, framesPerWrite);
            for (int i = 0; i < numActiveOscilators; ++i) {
                myData.sineOscillators[i].render(&shortData[i], actualChannelCount,
                                                  framesPerWrite);
            }
        }

+129 −46
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@
#include <aaudio/AAudio.h>
#include "AAudioExampleUtils.h"
#include "AAudioSimplePlayer.h"
#include "../../utils/AAudioSimplePlayer.h"

/**
 * Open stream, play some sine waves, then close the stream.
@@ -36,37 +35,39 @@
 * @param argParser
 * @return AAUDIO_OK or negative error code
 */
static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser)
static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser,
                                         int32_t loopCount,
                                         int32_t prefixToneMsec)
{
    SineThreadedData_t myData;
    AAudioSimplePlayer &player = myData.simplePlayer;
    aaudio_result_t    result = AAUDIO_OK;
    bool               disconnected = false;
    bool               bailOut = false;
    int64_t            startedAtNanos;

    printf("----------------------- run complete test --------------------------\n");
    myData.schedulerChecked = false;
    myData.callbackCount = 0;
    // TODO add a command line option for the forceUnderruns
    myData.forceUnderruns = false; // set true to test AAudioStream_getXRunCount()

    result = player.open(argParser,
                         SimplePlayerDataCallbackProc, SimplePlayerErrorCallbackProc, &myData);
    if (result != AAUDIO_OK) {
        fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
        fprintf(stderr, "ERROR -  player.open() returned %s\n",
                AAudio_convertResultToText(result));
        goto error;
    }

    argParser.compareWithStream(player.getStream());

    // Setup sine wave generators.
    {
        int32_t actualSampleRate = player.getSampleRate();
        myData.sineOsc1.setup(440.0, actualSampleRate);
        myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
        myData.sineOsc1.setAmplitude(0.2);
        myData.sineOsc2.setup(660.0, actualSampleRate);
        myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
        myData.sineOsc2.setAmplitude(0.2);
    myData.sampleRate = player.getSampleRate();
    myData.prefixToneFrames = prefixToneMsec * myData.sampleRate / 1000;
    if (myData.prefixToneFrames > 0) {
        myData.setupSineBlip();
    } else {
        myData.setupSineSweeps();
    }

#if 0
@@ -78,6 +79,23 @@ static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser)
    }
#endif

    for (int loopIndex = 0; loopIndex < loopCount; loopIndex++) {
        // Only play data on every other loop so we can hear if there is stale data.
        double amplitude;
        int32_t durationSeconds;
        if ((loopIndex & 1) == 0) {
            printf("--------------- SINE ------\n");
            amplitude = 0.2;
            durationSeconds = argParser.getDurationSeconds();
        } else {
            printf("--------------- QUIET -----\n");
            amplitude = 0.0;
            durationSeconds = 2; // just wait briefly when quiet
        }
        for (int i = 0; i < MAX_CHANNELS; ++i) {
            myData.sineOscillators[i].setAmplitude(amplitude);
        }

        result = player.start();
        if (result != AAUDIO_OK) {
            fprintf(stderr, "ERROR - player.start() returned %d\n", result);
@@ -85,35 +103,69 @@ static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser)
        }

        // Play a sine wave in the background.
    printf("Sleep for %d seconds while audio plays in a callback thread.\n",
           argParser.getDurationSeconds());
        printf("Sleep for %d seconds while audio plays in a callback thread. %d of %d\n",
               argParser.getDurationSeconds(), (loopIndex + 1), loopCount);
        startedAtNanos = getNanoseconds(CLOCK_MONOTONIC);
    for (int second = 0; second < argParser.getDurationSeconds(); second++)
    {
        for (int second = 0; second < durationSeconds; second++) {
            // Sleep a while. Wake up early if there is an error, for example a DISCONNECT.
            long ret = myData.waker.wait(AAUDIO_OK, NANOS_PER_SECOND);
        int64_t millis = (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND;
            int64_t millis =
                    (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND;
            result = myData.waker.get();
            printf("wait() returns %ld, aaudio_result = %d, at %6d millis"
               ", second = %d, framesWritten = %8d, underruns = %d\n",
                           ", second = %3d, framesWritten = %8d, underruns = %d\n",
                   ret, result, (int) millis,
                   second,
                   (int) AAudioStream_getFramesWritten(player.getStream()),
                   (int) AAudioStream_getXRunCount(player.getStream()));
            if (result != AAUDIO_OK) {
            if (result == AAUDIO_ERROR_DISCONNECTED) {
                disconnected = true;
            }
                disconnected = (result == AAUDIO_ERROR_DISCONNECTED);
                bailOut = true;
                break;
            }
        }
        printf("AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));

    printf("call stop() callback # = %d\n", myData.callbackCount);
        // Alternate between using stop or pause for each sine/quiet pair.
        // Repeat this pattern: {sine-stop-quiet-stop-sine-pause-quiet-pause}
        if ((loopIndex & 2) == 0) {
            printf("STOP, callback # = %d\n", myData.callbackCount);
            result = player.stop();
        } else {
            printf("PAUSE/FLUSH, callback # = %d\n", myData.callbackCount);
            result = player.pause();
            if (result != AAUDIO_OK) {
                goto error;
            }
            result = player.flush();
        }
        if (result != AAUDIO_OK) {
            goto error;
        }

        if (bailOut) {
            break;
        }

        {
            aaudio_stream_state_t state = AAudioStream_getState(player.getStream());
            aaudio_stream_state_t finalState = AAUDIO_STREAM_STATE_UNINITIALIZED;
            int64_t timeoutNanos = 2000 * NANOS_PER_MILLISECOND;
            result = AAudioStream_waitForStateChange(player.getStream(), state,
                                                     &finalState, timeoutNanos);
            printf("waitForStateChange returns %s, state = %s\n",
                   AAudio_convertResultToText(result),
                   AAudio_convertStreamStateToText(finalState));
            int64_t written = AAudioStream_getFramesWritten(player.getStream());
            int64_t read = AAudioStream_getFramesRead(player.getStream());
            printf("   framesWritten = %lld, framesRead = %lld, diff = %d\n",
                   (long long) written,
                   (long long) read,
                   (int) (written - read));
        }

    }

    printf("call close()\n");
    result = player.close();
    if (result != AAUDIO_OK) {
@@ -147,23 +199,54 @@ error:
    return disconnected ? AAUDIO_ERROR_DISCONNECTED : result;
}

static void usage() {
    AAudioArgsParser::usage();
    printf("      -l{count} loopCount start/stop, every other one is silent\n");
    printf("      -t{msec} play a high pitched tone at the beginning\n");
}

int main(int argc, const char **argv)
{
    AAudioArgsParser   argParser;
    aaudio_result_t    result;
    int32_t            loopCount = 1;
    int32_t            prefixToneMsec = 0;

    // 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 - Play a sine sweep using an AAudio callback V0.1.2\n", argv[0]);

    if (argParser.parseArgs(argc, argv)) {
        return EXIT_FAILURE;
    printf("%s - Play a sine sweep using an AAudio callback V0.1.3\n", argv[0]);

    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 'l':
                        loopCount = atoi(&arg[2]);
                        break;
                    case 't':
                        prefixToneMsec = atoi(&arg[2]);
                        break;
                    default:
                        usage();
                        exit(EXIT_FAILURE);
                        break;
                }
            } else {
                usage();
                exit(EXIT_FAILURE);
                break;
            }
        }
    }

    // Keep looping until we can complete the test without disconnecting.
    while((result = testOpenPlayClose(argParser)) == AAUDIO_ERROR_DISCONNECTED);
    while((result = testOpenPlayClose(argParser, loopCount, prefixToneMsec))
            == AAUDIO_ERROR_DISCONNECTED);

    return (result) ? EXIT_FAILURE : EXIT_SUCCESS;
}