/* * AudioPlayerNode.cpp */ #include #include #include #include #include #include #include #include #include "rtabmap_audio/AudioFrame.h" #include "rtabmap_audio/AudioFrameFreqSqrdMagn.h" #include #include #define DEFAULT_GUI_USED true #define PLOT_SAMPLING_RATIO 64 // Ring buffer unsigned int g_readPtr = 0; unsigned int g_writePtr = 0; std::vector g_ringBuffer; // interleaved audio unsigned int g_readingLooped = 0; unsigned int g_writingLooped = 0; bool g_warn = true; // Fmod stuff FMOD::System *g_system = 0; FMOD::Sound *g_sound = 0; FMOD::Channel *g_channel = 0; bool initialized = false; // Display stuff UPlot * g_plot = 0; UPlotCurve * g_curveReceiving = 0; UPlotCurve * g_curvePlaying = 0; USpectrogram * g_spectrogram = 0; void ERRCHECK(FMOD_RESULT result) { if (result != FMOD_OK) { ROS_ERROR("FMOD error! (%d) %s\n", result, FMOD_ErrorString(result)); exit(-1); } } void updateCurve(UPlotCurve * curve, void * data, unsigned int dataSize, int channels, int sampleSize) { if(!curve || !data || dataSize == 0) { return; } //ROS_INFO("channels=%d, bitsPerSample=%d", channels, sampleSize); int downSamplingFactor = PLOT_SAMPLING_RATIO*channels; QVector v(dataSize/downSamplingFactor/sampleSize); //ROS_INFO("dataSize=%d downSamplingFactor = %d, v=%d", dataSize, downSamplingFactor, v.size()); if(sampleSize == 1) { char * p = (char*)data; char min,max; for(int i=0; i abs(max) ? min : max; } } else if(sampleSize == 2) { short * p = (short*)data; short min,max; for(int i=0; i abs(max) ? min : max; } } else if(sampleSize == 4) { int * p = (int*)data; int min,max; for(int i=0; i abs(max) ? min : max; } } QMetaObject::invokeMethod(curve, "addValues", Q_ARG(QVector, v)); } FMOD_RESULT F_CALLBACK pcmreadcallback(FMOD_SOUND *sound, void *data, unsigned int datalen) { //ROS_INFO("datalen=%d, g_readPtr=%d", datalen, g_readPtr); unsigned int count; char * buffer = (char *)data; bool okToCpy = false; if(g_readPtr < g_writePtr) { okToCpy = true; } else if(g_readingLooped < g_writingLooped) { okToCpy = true; } if(okToCpy) { g_warn = true; unsigned int start = g_readPtr; for(count=0; countisVisible() && g_curvePlaying) { int channels, bitsPerSample; FMOD_Sound_GetFormat(sound, 0, 0, &channels, &bitsPerSample); updateCurve(g_curvePlaying, &g_ringBuffer[start], datalen, channels, bitsPerSample/8); } //ROS_INFO("datalen=%d, writePtr=%d, readPtr=%d", datalen, g_writePtr, g_readPtr); } else if(g_warn) { g_warn = false; ROS_WARN("Empty buffer : stream down? (this warning is shown only one time)"); //fill data with zeros for(unsigned int i=0; igetVersion(&version); ERRCHECK(result); if (version < FMOD_VERSION) { printf("Error! You are using an old version of FMOD %08x. This program requires %08x\n", version, FMOD_VERSION); return false; } result = g_system->init(32, FMOD_INIT_NORMAL, 0); ERRCHECK(result); memset(&createsoundexinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO)); createsoundexinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO); /* required. */ createsoundexinfo.length = decoderBufferSize; /* Length of PCM data in bytes of whole song (for Sound::getLength) */ createsoundexinfo.numchannels = channels; /* Number of channels in the sound. */ createsoundexinfo.defaultfrequency = fs; /* Default playback rate of sound. */ createsoundexinfo.decodebuffersize = (decoderBufferSize / bytesPerSample) / channels; /* Chunk size of stream update in samples. This will be the amount of data passed to the user callback. */ if(bytesPerSample == 1) { createsoundexinfo.format = FMOD_SOUND_FORMAT_PCM8; /* Data format of sound. */ } else if(bytesPerSample == 2) { createsoundexinfo.format = FMOD_SOUND_FORMAT_PCM16; /* Data format of sound. */ } else if(bytesPerSample == 4) { createsoundexinfo.format = FMOD_SOUND_FORMAT_PCM32; /* Data format of sound. */ } else { ROS_ERROR("Sample size format (%d) must be 1, 2 or 4", bytesPerSample); return false; } createsoundexinfo.pcmreadcallback = pcmreadcallback; /* User callback for reading. */ createsoundexinfo.pcmsetposcallback = pcmsetposcallback; /* User callback for seeking. */ result = g_system->createSound(0, mode, &createsoundexinfo, &g_sound); ERRCHECK(result); /* Play the sound. */ result = g_system->playSound(FMOD_CHANNEL_FREE, g_sound, 0, &g_channel); ERRCHECK(result); return true; } void frameReceivedCallback(const rtabmap_audio::AudioFramePtr & msg) { if(msg->data.size()) { if(!g_ringBuffer.size()) { g_ringBuffer = std::vector(msg->data.size() * 5 * msg->nChannels * msg->sampleSize, 0); } //ROS_INFO("Received audio frame size=(%d), writePtr=%d", msg->data.size(), g_writePtr); unsigned int start = g_writePtr; if(g_writePtr + msg->data.size() <= g_ringBuffer.size()) { memcpy(g_ringBuffer.data()+g_writePtr, msg->data.data(), msg->data.size()); g_writePtr += msg->data.size(); g_writePtr %= g_ringBuffer.size(); } else { unsigned int size2 = (g_writePtr + msg->data.size()) - g_ringBuffer.size(); unsigned int size1 = msg->data.size() - size2; memcpy(g_ringBuffer.data()+g_writePtr, msg->data.data(), size1); memcpy(g_ringBuffer.data(), msg->data.data() + size1, size2); g_writePtr = size2; } if(g_writePtr < start) { ++g_writingLooped; } if(!initialized) { //wait for second frame if(g_writePtr > msg->data.size() * 2) { initialized = initAudioPlayer(msg->data.size(), msg->fs, msg->nChannels, msg->sampleSize); if(!initialized) { ROS_ERROR("Cannot initialize the audio player..."); exit(-1); } } else if(g_plot) { if(g_plot->isVisible() && ((msg->data.size() / msg->nChannels) / msg->sampleSize) % PLOT_SAMPLING_RATIO != 0) { g_plot->setVisible(false); ROS_WARN("Frame length (%d) must be a multiple of %d to show audio plot...", ((msg->data.size() / msg->nChannels) / msg->sampleSize), PLOT_SAMPLING_RATIO); } else if(g_curveReceiving && g_curvePlaying) { QMetaObject::invokeMethod(g_curveReceiving, "setXIncrement", Q_ARG(float, float(PLOT_SAMPLING_RATIO)/float(msg->fs))); QMetaObject::invokeMethod(g_curvePlaying, "setXIncrement", Q_ARG(float, float(PLOT_SAMPLING_RATIO)/float(msg->fs))); } } } if(g_plot && g_plot->isVisible() && g_curveReceiving) { updateCurve(g_curveReceiving, msg->data.data(), msg->data.size(), msg->nChannels, msg->sampleSize); } } } void frameFreqSqrdMagnReceivedCallback(const rtabmap_audio::AudioFrameFreqSqrdMagnPtr & msg) { if(msg->data.size() && msg->nChannels && g_spectrogram && g_spectrogram->isVisible()) { QMetaObject::invokeMethod(g_spectrogram, "setSamplingRate", Q_ARG(int, msg->fs)); std::vector data(msg->data.size()/msg->nChannels); memcpy(data.data(), msg->data.data(), data.size()*sizeof(float)); // Just copy the first channel TODO support more channels... QMetaObject::invokeMethod(g_spectrogram, "push", Q_ARG(std::vector, data)); } } void my_handler(int s){ QApplication::closeAllWindows(); } int main(int argc, char** argv) { ULogger::setType(ULogger::kTypeConsole); ULogger::setLevel(ULogger::kDebug); ros::init(argc, argv, "audioPlayer"); ros::NodeHandle nh("~"); //Parameter bool guiUsed = DEFAULT_GUI_USED; nh.param("gui_used", guiUsed, guiUsed); ROS_INFO("gui_used=%s", guiUsed?"true":"false"); nh = ros::NodeHandle(""); ros::Subscriber audioSubs = nh.subscribe("audioFrame", 1, frameReceivedCallback); ros::Subscriber audioFreqSqrdMagnSubs; if(guiUsed) { QApplication app(argc, argv); qRegisterMetaType >("QVector"); qRegisterMetaType >("std::vector"); g_plot = new UPlot(); g_plot->keepAllData(false); g_plot->setMaxVisibleItems(1024); g_plot->setXLabel("Time (s)"); g_curveReceiving = g_plot->addCurve("Receiving"); // debugging purpose, to see what is received g_curvePlaying = g_plot->addCurve("Playing"); g_plot->setMinimumSize(600, 400); g_spectrogram = new USpectrogram(); g_spectrogram->setMinimumSize(640, 480); g_spectrogram->show(); g_plot->show(); audioFreqSqrdMagnSubs = nh.subscribe("audioFrameFreqSqrdMagn", 1, frameFreqSqrdMagnReceivedCallback); // Catch ctrl-c to close the gui // (Place this after QApplication's constructor) struct sigaction sigIntHandler; sigIntHandler.sa_handler = my_handler; sigemptyset(&sigIntHandler.sa_mask); sigIntHandler.sa_flags = 0; sigaction(SIGINT, &sigIntHandler, NULL); ros::AsyncSpinner spinner(4); // Use 4 threads spinner.start(); app.exec(); spinner.stop(); } else { ros::spin(); } uSleep(100); // make sure all subscribers have terminated /* Shut down */ FMOD_RESULT result; if(g_sound) { result = g_sound->release(); ERRCHECK(result); } if(g_system) { result = g_system->close(); ERRCHECK(result); result = g_system->release(); ERRCHECK(result); } if(g_plot) { delete g_plot; } if(g_spectrogram) { delete g_spectrogram; } return 0; }