0

I am trying to make a basic mixer app, which will involve up to 4 sounds playing at the same time.

Here is the class I am using to achieve this:

#define log_print __android_log_print

static SuperpoweredAndroidAudioIO *audioIO;
static SuperpoweredAdvancedAudioPlayer *playerA, *playerB, *playerC, *playerD;
static float *floatBuffer;

// This is called periodically by the audio engine.
static bool audioProcessing (
        void * __unused clientdata, // custom pointer
        short int *audio,           // buffer of interleaved samples
        int numberOfFrames,         // number of frames to process
        int __unused samplerate     // sampling rate
) {
    if(playerA->process(floatBuffer, false, (unsigned int)numberOfFrames)
       || playerB->process(floatBuffer, false, (unsigned int)numberOfFrames)
       || playerC->process(floatBuffer, false, (unsigned int)numberOfFrames)
       || playerD->process(floatBuffer, false, (unsigned int)numberOfFrames)) {
        SuperpoweredFloatToShortInt(floatBuffer, audio, (unsigned int)numberOfFrames);
        return true;
    } else {
        return false;
    }
}

// Called by the playerA.
static void playerEventCallbackA (
        void * __unused clientData,
        SuperpoweredAdvancedAudioPlayerEvent event,
        void *value
) {
    switch (event) {
        case SuperpoweredAdvancedAudioPlayerEvent_LoadSuccess:
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_LoadError:
            log_print(ANDROID_LOG_ERROR, "Player", "Open error: %s", (char *)value);
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_EOF:
            playerA->seek(0);    // loop track
            break;
        default:;
    };
}
static void playerEventCallbackB (
        void * __unused clientData,
        SuperpoweredAdvancedAudioPlayerEvent event,
        void *value
) {
    switch (event) {
        case SuperpoweredAdvancedAudioPlayerEvent_LoadSuccess:
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_LoadError:
            log_print(ANDROID_LOG_ERROR, "Player", "Open error: %s", (char *)value);
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_EOF:
            playerB->seek(0);    // loop track
            break;
        default:;
    };
}
static void playerEventCallbackC (
        void * __unused clientData,
        SuperpoweredAdvancedAudioPlayerEvent event,
        void *value
) {
    switch (event) {
        case SuperpoweredAdvancedAudioPlayerEvent_LoadSuccess:
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_LoadError:
            log_print(ANDROID_LOG_ERROR, "Player", "Open error: %s", (char *)value);
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_EOF:
            playerC->seek(0);    // loop track
            break;
        default:;
    };
}
static void playerEventCallbackD (
        void * __unused clientData,
        SuperpoweredAdvancedAudioPlayerEvent event,
        void *value
) {
    switch (event) {
        case SuperpoweredAdvancedAudioPlayerEvent_LoadSuccess:
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_LoadError:
            log_print(ANDROID_LOG_ERROR, "Player", "Open error: %s", (char *)value);
            break;
        case SuperpoweredAdvancedAudioPlayerEvent_EOF:
            playerD->seek(0);    // loop track
            break;
        default:;
    };
}

// StartAudio - Start audio engine and initialize playerA.
extern "C" JNIEXPORT void
Java_com_connorschwing_CustomAudioManager_StartAudio (
        JNIEnv * __unused env,
        jobject  __unused obj,
        jint samplerate,
        jint buffersize
) {
    SuperpoweredInitialize(
            "ExampleLicenseKey-WillExpire-OnNextUpdate",
            false, // enableAudioAnalysis (using SuperpoweredAnalyzer, SuperpoweredLiveAnalyzer, SuperpoweredWaveform or SuperpoweredBandpassFilterbank)
            false, // enableFFTAndFrequencyDomain (using SuperpoweredFrequencyDomain, SuperpoweredFFTComplex, SuperpoweredFFTReal or SuperpoweredPolarFFT)
            false, // enableAudioTimeStretching (using SuperpoweredTimeStretching)
            false, // enableAudioEffects (using any SuperpoweredFX class)
            true, // enableAudioPlayerAndDecoder (using SuperpoweredAdvancedAudioPlayer or SuperpoweredDecoder)
            false, // enableCryptographics (using Superpowered::RSAPublicKey, Superpowered::RSAPrivateKey, Superpowered::hasher or Superpowered::AES)
            false  // enableNetworking (using Superpowered::httpRequest)
    );

    // Allocate audio buffer.
    floatBuffer = (float *)malloc(sizeof(float) * 2 * buffersize);

    // Initialize playerA and pass callback function.
    playerA = new SuperpoweredAdvancedAudioPlayer (
            NULL,                           // clientData
            playerEventCallbackA,            // callback function
            (unsigned int)samplerate,       // sampling rate
            0                               // cachedPointCount
    );
    // Initialize playerA and pass callback function.
    playerB = new SuperpoweredAdvancedAudioPlayer (
            NULL,                           // clientData
            playerEventCallbackA,            // callback function
            (unsigned int)samplerate,       // sampling rate
            0                               // cachedPointCount
    );
    // Initialize playerA and pass callback function.
    playerC = new SuperpoweredAdvancedAudioPlayer (
            NULL,                           // clientData
            playerEventCallbackA,            // callback function
            (unsigned int)samplerate,       // sampling rate
            0                               // cachedPointCount
    );
    // Initialize playerA and pass callback function.
    playerD = new SuperpoweredAdvancedAudioPlayer (
            NULL,                           // clientData
            playerEventCallbackA,            // callback function
            (unsigned int)samplerate,       // sampling rate
            0                               // cachedPointCount
    );

    // Initialize audio with audio callback function.
    audioIO = new SuperpoweredAndroidAudioIO (
            samplerate,                     // sampling rate
            buffersize,                     // buffer size
            false,                          // enableInput
            true,                           // enableOutput
            audioProcessing,                // process callback function
            NULL,                           // clientData
            -1,                             // inputStreamType (-1 = default)
            SL_ANDROID_STREAM_MEDIA         // outputStreamType (-1 = default)
    );
}

// OpenFile - Open file in playerA, specifying offset and length.
extern "C" JNIEXPORT void
Java_com_connorschwing_CustomAudioManager_OpenFile (
        JNIEnv *env,
        jobject __unused obj,
        jstring path,       // path to APK file
        jint channelID      // which player to use
) {
    log_print(ANDROID_LOG_INFO, "Player", "~~CHECKPOINT: %d", channelID);
    const char *str = env->GetStringUTFChars(path, 0);
    switch (channelID) {
        case 0:
            playerA->open(str);
            log_print(ANDROID_LOG_INFO, "Player", "~~Opening: %s", str);
            break;
        case 1:
            playerB->open(str);
            log_print(ANDROID_LOG_INFO, "Player", "~~Opening: %s", str);
            break;
        case 2:
            playerC->open(str);
            break;
        case 3:
            playerD->open(str);
            break;
        default: ;
    }
    env->ReleaseStringUTFChars(path, str);
}

// TogglePlayback - Toggle Play/Pause state of the playerA.
extern "C" JNIEXPORT void
Java_com_connorschwing_CustomAudioManager_TogglePlayback (
        JNIEnv * __unused env,
        jobject __unused obj,
        jint channelID,
        jboolean playing
) {
    switch (channelID) {
        case -1:
            if(playing){
                log_print(ANDROID_LOG_INFO, "Player", "~~PLAYING ALL: %d", channelID);
                playerA->play(true);
                playerB->play(true);
                playerC->play(true);
                playerD->play(true);
                SuperpoweredCPU::setSustainedPerformanceMode(playerA->playing);  // prevent dropouts
                SuperpoweredCPU::setSustainedPerformanceMode(playerB->playing);  // prevent dropouts
                SuperpoweredCPU::setSustainedPerformanceMode(playerC->playing);  // prevent dropouts
                SuperpoweredCPU::setSustainedPerformanceMode(playerD->playing);  // prevent dropouts
            } else {
                playerA->pause();
                    playerA->seek(0);
                playerB->pause();
                    playerB->seek(0);
                playerC->pause();
                    playerC->seek(0);
                playerD->pause();
                    playerD->seek(0);
            }
            break;
        case 0:
            if(playing){
                playerA->play(false);
                SuperpoweredCPU::setSustainedPerformanceMode(playerA->playing);  // prevent dropouts
            } else {
                playerA->pause();
                playerA->seek(0);
            }
            break;
        case 1:
            if(playing){
                playerB->play(false);
                SuperpoweredCPU::setSustainedPerformanceMode(playerB->playing);  // prevent dropouts
            } else playerB->pause();
            break;
        case 2:
            if(playing){
                playerC->play(false);
                SuperpoweredCPU::setSustainedPerformanceMode(playerC->playing);  // prevent dropouts
            } else playerC->pause();
            break;
        case 3:
            if(playing){
                playerD->play(false);
                SuperpoweredCPU::setSustainedPerformanceMode(playerD->playing);  // prevent dropouts
            } else playerD->pause();
            break;
        default:;
    }
}

// onBackground - Put audio processing to sleep.
extern "C" JNIEXPORT void
Java_com_connorschwing_CustomAudioManager_onBackground(
        JNIEnv *__unused env,
        jobject __unused obj
) {
    audioIO->onBackground();
}

// onForeground - Resume audio processing.
extern "C" JNIEXPORT void
Java_com_connorschwing_CustomAudioManager_onForeground(
        JNIEnv *__unused env,
        jobject __unused obj
) {
    audioIO->onForeground();
}

// Cleanup - Free resources.
extern "C" JNIEXPORT void
Java_com_connorschwing_CustomAudioManager_Cleanup(
        JNIEnv *__unused env,
        jobject __unused obj
) {
    delete audioIO;
    delete playerA;
    delete playerB;
    delete playerC;
    delete playerD;
    free(floatBuffer);
}

It works very well for one track. I can record a sound, open it in the player, and have it play and loop. However, if I record on another track and start playing player B, then I get no sound out of player B. I can only hear one track at a time. I understand that I have to somehow combine these players into one buffer, but I am not sure how to go about doing that. Right now it just calls player->play(false) on each one back to back.

How should I change this code to allow multiple players to run at the same time?

Solution attempt 1 Instead of using true/false for the bufferAdd value in each cell, I simply use the call itself for another player. These calls to player->process() will return a boolean which will be set as the bufferAdd value for the next call

if((playerD->process(floatBuffer, playerC->process(floatBuffer, playerB->process(floatBuffer, playerA->process(floatBuffer, false, (unsigned int)numberOfFrames), (unsigned int)numberOfFrames), (unsigned int)numberOfFrames) , (unsigned int)numberOfFrames)))
{
    SuperpoweredFloatToShortInt(floatBuffer, audio, (unsigned int)numberOfFrames);
    return true;
}

Solution attempt 2 Check the status of each player, if it is processing then process the other 3

if(playerA->process(floatBuffer, false, (unsigned int)numberOfFrames))
{
    playerB->process(floatBuffer, true, (unsigned int)numberOfFrames);
    playerC->process(floatBuffer, true, (unsigned int)numberOfFrames);
    playerD->process(floatBuffer, true, (unsigned int)numberOfFrames);
    SuperpoweredFloatToShortInt(floatBuffer, audio, (unsigned int)numberOfFrames);
    return true;
} else if(playerB->process(floatBuffer, false, (unsigned int)numberOfFrames)) {
    playerA->process(floatBuffer, true, (unsigned int) numberOfFrames);
    playerC->process(floatBuffer, true, (unsigned int) numberOfFrames);
    playerD->process(floatBuffer, true, (unsigned int) numberOfFrames);
    SuperpoweredFloatToShortInt(floatBuffer, audio, (unsigned int) numberOfFrames);
    return true;
} else if(playerC->process(floatBuffer, false, (unsigned int)numberOfFrames)) {
    playerB->process(floatBuffer, true, (unsigned int) numberOfFrames);
    playerA->process(floatBuffer, true, (unsigned int) numberOfFrames);
    playerD->process(floatBuffer, true, (unsigned int) numberOfFrames);
    SuperpoweredFloatToShortInt(floatBuffer, audio, (unsigned int) numberOfFrames);
    return true;
} else if(playerD->process(floatBuffer, false, (unsigned int)numberOfFrames)) {
    playerB->process(floatBuffer, true, (unsigned int) numberOfFrames);
    playerC->process(floatBuffer, true, (unsigned int) numberOfFrames);
    playerA->process(floatBuffer, true, (unsigned int) numberOfFrames);
    SuperpoweredFloatToShortInt(floatBuffer, audio, (unsigned int) numberOfFrames);
    return true;
} else return false;
leonheess
  • 16,068
  • 14
  • 77
  • 112
Connor S
  • 353
  • 2
  • 12

3 Answers3

1
  1. In your audioProcessing function where you call player[ABCD]->process(), always call every player's process() function regardless any state. (In your current code the || (OR) operator doesn't call the next process() if the previous one returned with true.)
  2. The second parameter of player->process is called "bufferAdd". If true, it adds to the audio in the buffer, if false, it replaces the contents of the buffer. (In your current code it's false, therefore the players will not mix together.) Set "bufferAdd" to true if any of the previous players did output audio (returned with true from process()). Therefore only the first player's bufferAdd can be fixed to false, since there no "previous" players.
Gabor Szanto
  • 1,329
  • 8
  • 12
  • Thank you for taking the time to reply. I have added my attempted solution to the question. It now will play all tracks, however it will only do so if they all contain audio. I am having trouble figuring out how to test the output of the process function and using that to determine the bufferAdd value. If I test it, for example if(playerB->process()), then I have already called the process function and will have to call it again with a proper flag. This creates a huge issue with logic if I am trying to allow any of the players to contain audio – Connor S Jul 08 '19 at 00:40
  • It is worth noting that all players will be started/stopped together, but not all 4 may contain audio. Player C by itself could contain audio for example, or players A, B, and D. I'm not sure how to translate this into logic for the audioProcessing function. Any help you can offer will be very, very much appreciated. – Connor S Jul 08 '19 at 00:41
  • Looks like I got it. Just put the new answer in the question. Thanks again! – Connor S Jul 08 '19 at 00:57
1

A more elegant solution can be:

bool silence = playerA->process(floatBuffer, false, ...);
silence |= playerB->process(floatBuffer, !silence, ...);
silence |= playerC->process(floatBuffer, !silence, ...);
silence |= playerD->process(floatBuffer, !silence, ...);
Gabor Szanto
  • 1,329
  • 8
  • 12
0

@ConnorS Here's a snippet that worked for me with 4 players and a StereoMixer:

bool CrossExample::process(short int *output, unsigned int numberOfFrames, unsigned int samplerate) {


player1->outputSamplerate = player2->outputSamplerate = player3->outputSamplerate = player4->outputSamplerate;


// Get audio from the players into a buffer on the stack.
float outputBuffer[numberOfFrames * 2];
bool silence = player1->processStereo(outputBuffer, false, numberOfFrames, volA);
if (player2->processStereo(outputBuffer, !silence, numberOfFrames, volA)) silence = false;
if (player3->processStereo(outputBuffer, !silence, numberOfFrames, volA)) silence = false;
if (player4->processStereo(outputBuffer, !silence, numberOfFrames, volA)) silence = false;

    mixer->process(outputBuffer,
            NULL,
            NULL,
            NULL,
            outputBuffer,
            numberOfFrames);
};

// The output buffer is ready now, let's write the finished audio into the requested buffer.
if (!silence) Superpowered::FloatToShortInt(outputBuffer, output, numberOfFrames);
return !silence; //this equals true

}

janu935
  • 71
  • 1
  • 4