Howdy all,
I’m working on a project to try piping FMOD’s output to a Discord Bot’s Voice Chat output. To do this, I’ve followed a few of the DSP examples and advice from primarily C# and Unity threads on this forum doing much the same thing. I’ve got it partly working, but I’m struggling with making everything run correctly and in real-time (i.e. it takes longer to fill my PCM buffer than for my bot to play it back, and the played-back audio is at correct pitch but sounds like someone scrubbing quickly forward through a piece of audio.
I would love some help understanding what I’m doing wrong, and/or if there’s a better approach. I considered Output Plugins but couldn’t wrap my head around them nor figure out exactly how to write and use one for my purposes. Below is the essence of my code, with some irrelevant functions and definitions removed. Full code visible on Github for more context.
Using FMOD API 2.02.16.
//---FMOD Declarations---//
FMOD::Studio::System* pSystem = nullptr; //overall system
FMOD::System* pCoreSystem = nullptr; //overall core system
FMOD::Studio::Bank* pMasterBank = nullptr; //Master Bank
FMOD::Studio::Bank* pMasterStringsBank = nullptr; //Master Strings
FMOD::Studio::Bus* pMasterBus = nullptr; //Master bus
FMOD::ChannelGroup* pMasterBusGroup = nullptr; //Channel Group of the master bus
FMOD::DSP* mCaptureDSP = nullptr; //DSP to attach to Master Channel Group for stealing output
//---Misc Bot Declarations---//
dpp::discord_voice_client* currentClient = nullptr;
std::vector<uint16_t> myPCMData; //Main buffer of PCM audio data, which FMOD adds to and D++ cuts "frames" from
std::mutex pcmDataMutex;
bool isRunning = true;
bool isConnected = false;
bool fromSilence = true;
//FMOD and Audio Functions
FMOD_RESULT F_CALLBACK captureDSPReadCallback(FMOD_DSP_STATE* dsp_state, float* inbuffer, float* outbuffer, unsigned int length, int inchannels, int* outchannels) {
//inchannels and *outchannels = 2, error checking for that excluded from example
FMOD::DSP* thisdsp = (FMOD::DSP*)dsp_state->instance;
std::vector<uint16_t> pcmdata;
if (isConnected) { //Evals True when connected to Voice Chat
std::lock_guard lk(pcmDataMutex);
for (unsigned int samp = 0; samp < length; samp++) {
for (int chan = 0; chan < *outchannels; chan++) {
outbuffer[(samp * *outchannels) + chan] = 0.0f; //"Mutes" system output.
pcmdata.push_back(floatToPCM(inbuffer[(samp*inchannels) + chan]));
}
}
//Pass PCM data to our larger buffer
myPCMData.insert(myPCMData.end(), pcmdata.cbegin(), pcmdata.cend());
}
else {
return FMOD_ERR_DSP_SILENCE; //Unsure if I'm using this correctly
}
return FMOD_OK;
}
void init() {
//FMOD Init
ERRCHECK(FMOD::Studio::System::create(&pSystem));
ERRCHECK(pSystem->initialize(128, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, nullptr));
ERRCHECK(pSystem->getCoreSystem(&pCoreSystem));
//Load Master Bank and Master Strings
ERRCHECK(pSystem->loadBankFile(..., FMOD_STUDIO_LOAD_BANK_NORMAL, &pMasterBank));
ERRCHECK(pSystem->loadBankFile(..., FMOD_STUDIO_LOAD_BANK_NORMAL, &pMasterStringsBank));
//Also get the Master Bus, set volume, and get the related Channel Group
ERRCHECK(pSystem->getBus("bus:/", &pMasterBus));
ERRCHECK(pMasterBus->setVolume(dBToFloat(-10.0f)));
ERRCHECK(pMasterBus->lockChannelGroup()); //Tell the Master Channel Group to always exist even when events arn't playing...
ERRCHECK(pSystem->flushCommands()); //And wait until all previous commands are done (ensuring Channel Group exists)...
ERRCHECK(pMasterBus->getChannelGroup(&pMasterBusGroup)); //Or else this fails immediately, and we'll have DSP problems.
//Define and create our capture DSP on the Master Channel Group.
//Copied from FMOD's examples, unsure why this particular configuration works and why it must be in brackets
{
FMOD_DSP_DESCRIPTION dspdesc;
memset(&dspdesc, 0, sizeof(dspdesc));
strncpy_s(dspdesc.name, "LH_captureDSP", sizeof(dspdesc.name));
dspdesc.version = 0x00010000;
dspdesc.numinputbuffers = 8;
dspdesc.numoutputbuffers = 8;
dspdesc.read = captureDSPReadCallback;
//dspdesc.userdata = (void*)0x12345678;
ERRCHECK(pCoreSystem->createDSP(&dspdesc, &mCaptureDSP));
}
ERRCHECK(pMasterBusGroup->addDSP(FMOD_CHANNELCONTROL_DSP_TAIL, mCaptureDSP)); //Adds the newly defined dsp
// Create event instance
std::cout << "Creating Test Event Instance...";
ERRCHECK(pSystem->getEvent("event:/Master/Music/TitleTheme", &pEventDescription));
ERRCHECK(pEventDescription->createInstance(&pEventInstance));
ERRCHECK(pEventInstance->start()); //Start test audio event...
ERRCHECK(pEventInstance->release()); //...and release its resources when it stops playing.
}
int main() {
init();
/* Start the bot */
bot.start();
//Program loop
while (isRunning) {
//Update FMOD processes
pSystem->update();
//Send PCM data to D++, if applicable
//Evals True when bot is in voice chat
if (isConnected) {
//protect all data in this block. Not usually a problem, but all the same.
std::lock_guard lk(pcmDataMutex);
//If larger than the amount needed for an Opus packet
if (myPCMData.size() > dpp::send_audio_raw_max_length) {
while (myPCMData.size() > dpp::send_audio_raw_max_length) {
currentClient->send_audio_raw(myPCMData.data(), dpp::send_audio_raw_max_length); //Send usable chunk of main buffer
myPCMData.erase(myPCMData.begin(), myPCMData.begin() + dpp::send_audio_raw_max_length); //Trim main buffer of the data just sent
}
}
}
Sleep(10);
}
// Program quit. We never actually reach here as it stands,
// but we'll deal with proper protocol for this when it all actually works.
pMasterBusGroup->removeDSP(mCaptureDSP);
mCaptureDSP->release();
//Unload and release System
pSystem->unloadAll();
pSystem->release();
return 0;
}