From 23fc4ca05149c6531e508b8923567701d042ec1c Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Sun, 29 Apr 2018 13:29:22 +0300 Subject: [PATCH] Initial commit --- main.cpp | 821 +++++++++++++++++++++++++++++++++++++++ main.js | 788 +++++++++++++++++++++++++++++++++++++ reed-solomon/LICENSE | 21 + reed-solomon/gf.hpp | 239 ++++++++++++ reed-solomon/poly.hpp | 98 +++++ reed-solomon/rs.hpp | 532 +++++++++++++++++++++++++ sdp-transform/LICENSE | 22 ++ sdp-transform/grammar.js | 347 +++++++++++++++++ sdp-transform/index.js | 11 + sdp-transform/parser.js | 118 ++++++ sdp-transform/writer.js | 112 ++++++ 11 files changed, 3109 insertions(+) create mode 100644 main.cpp create mode 100644 main.js create mode 100644 reed-solomon/LICENSE create mode 100644 reed-solomon/gf.hpp create mode 100644 reed-solomon/poly.hpp create mode 100644 reed-solomon/rs.hpp create mode 100644 sdp-transform/LICENSE create mode 100644 sdp-transform/grammar.js create mode 100644 sdp-transform/index.js create mode 100644 sdp-transform/parser.js create mode 100644 sdp-transform/writer.js diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..72ba4a9 --- /dev/null +++ b/main.cpp @@ -0,0 +1,821 @@ +/*! \file main.cpp + * \brief Send/Receive data through sound + * \author Georgi Gerganov + */ + +#include "build_timestamp.h" + +#include "fftw3.h" +#include "reed-solomon/rs.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846f +#endif + +#ifdef __EMSCRIPTEN__ +#include "emscripten/emscripten.h" +#endif + +#ifdef main +#undef main +#endif + +static SDL_AudioDeviceID devid_in = 0; +static SDL_AudioDeviceID devid_out = 0; + +struct DataRxTx; +static DataRxTx *g_data = nullptr; + +namespace { + //constexpr float IRAND_MAX = 1.0f/RAND_MAX; + //inline float frand() { return ((float)(rand()%RAND_MAX)*IRAND_MAX); } + + constexpr double kBaseSampleRate = 48000.0; + constexpr auto kMaxSamplesPerFrame = 1024; + constexpr auto kMaxDataBits = 256; + constexpr auto kMaxDataSize = 256; + constexpr auto kMaxSpectrumHistory = 4; + constexpr auto kMaxRecordedFrames = 64*10; + + using AmplitudeData = std::array; + using AmplitudeData16 = std::array; + using SpectrumData = std::array; + using RecordedData = std::array; + + inline void addAmplitudeSmooth(const AmplitudeData & src, AmplitudeData & dst, float scalar, int startId, int finalId, int cycleMod, int nPerCycle) { + int nTotal = nPerCycle*finalId; + float frac = 0.15f; + float ds = frac*nTotal; + float ids = 1.0f/ds; + int nBegin = frac*nTotal; + int nEnd = (1.0f - frac)*nTotal; + for (int i = startId; i < finalId; i++) { + float k = cycleMod*finalId + i; + if (k < nBegin) { + dst[i] += scalar*src[i]*(k*ids); + } else if (k > nEnd) { + dst[i] += scalar*src[i]*(((float)(nTotal) - k)*ids); + } else { + dst[i] += scalar*src[i]; + } + } + } + + template + float getTime_ms(const T & tStart, const T & tEnd) { + return ((float)(std::chrono::duration_cast(tEnd - tStart).count()))/1000.0; + } +} + +struct DataRxTx { + DataRxTx(int aSampleRateOut, int aSampleRate, int aSamplesPerFrame, int aSampleSizeB, const char * text) { + sampleSizeBytes = aSampleSizeB; + sampleRate = aSampleRate; + sampleRateOut = aSampleRateOut; + samplesPerFrame = aSamplesPerFrame; + + init(strlen(text), text); + } + + void init(int textLength, const char * stext) { + const uint8_t * text = reinterpret_cast(stext); + frameId = 0; + nIterations = 0; + hasData = false; + + isamplesPerFrame = 1.0f/samplesPerFrame; + sendVolume = ((double)(paramVolume))/100.0f; + hzPerFrame = sampleRate/samplesPerFrame; + ihzPerFrame = 1.0/hzPerFrame; + framesPerTx = paramFramesPerTx; + + nDataBitsPerTx = paramBytesPerTx*8; + nECCBytesPerTx = paramECCBytesPerTx; + + framesToAnalyze = 0; + framesLeftToAnalyze = 0; + framesToRecord = 0; + framesLeftToRecord = 0; + nBitsInMarker = 16; + nMarkerFrames = 64; + nPostMarkerFrames = 0; + sendDataLength = 82; + recvDuration_frames = nMarkerFrames + nPostMarkerFrames + framesPerTx*((sendDataLength + nECCBytesPerTx)/paramBytesPerTx + 1); + + d0 = paramFreqDelta/2; + freqDelta_hz = hzPerFrame*paramFreqDelta; + freqStart_hz = hzPerFrame*paramFreqStart; + if (paramFreqDelta == 1) { + d0 = 1; + freqDelta_hz *= 2; + } + + outputBlock.fill(0); + encodedData.fill(0); + + for (int k = 0; k < (int) phaseOffsets.size(); ++k) { + phaseOffsets[k] = (M_PI*k)/(nDataBitsPerTx); + } +#ifdef __EMSCRIPTEN__ + std::random_shuffle(phaseOffsets.begin(), phaseOffsets.end()); +#endif + + for (int k = 0; k < (int) dataBits.size(); ++k) { + double freq = freqStart_hz + freqDelta_hz*k; + dataFreqs_hz[k] = freq; + + double phaseOffset = phaseOffsets[k]; + double curHzPerFrame = sampleRateOut/samplesPerFrame; + double curIHzPerFrame = 1.0/curHzPerFrame; + for (int i = 0; i < samplesPerFrame; i++) { + bit1Amplitude[k][i] = std::sin((2.0*M_PI*i)*freq*isamplesPerFrame*curIHzPerFrame + phaseOffset); + } + for (int i = 0; i < samplesPerFrame; i++) { + bit0Amplitude[k][i] = std::sin((2.0*M_PI*i)*(freq + hzPerFrame*d0)*isamplesPerFrame*curIHzPerFrame + phaseOffset); + } + } + + if (rs) delete rs; + rs = new RS::ReedSolomon(sendDataLength, nECCBytesPerTx); + if (textLength > 0) { + static std::array theData; + theData.fill(0); + for (int i = 0; i < textLength; ++i) theData[i] = text[i]; + rs->Encode(theData.data(), encodedData.data()); + hasData = true; + } + + // Rx + receivingData = false; + analyzingData = false; + + sampleAmplitude.fill(0); + + sampleSpectrum.fill(0); + sampleSpectrumTmp.fill(0); + for (auto & s : sampleAmplitudeHistory) { + s.fill(0); + } + + rxData.fill(0); + + if (fftPlan) fftwf_destroy_plan(fftPlan); + if (fftIn) fftwf_free(fftIn); + if (fftOut) fftwf_free(fftOut); + + fftIn = (float*) fftwf_malloc(sizeof(float)*samplesPerFrame); + fftOut = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex)*samplesPerFrame); + fftPlan = fftwf_plan_dft_r2c_1d(1*samplesPerFrame, fftIn, fftOut, FFTW_ESTIMATE); + + for (int i = 0; i < samplesPerFrame; ++i) { + fftOut[i][0] = 0.0f; + fftOut[i][1] = 0.0f; + } + } + + void send() { + int samplesPerFrameOut = (sampleRateOut/sampleRate)*samplesPerFrame; + if (sampleRateOut != sampleRate) { + printf("Resampling from %d Hz to %d Hz\n", (int) sampleRate, (int) sampleRateOut); + } + + while(hasData) { + int nBytesPerTx = nDataBitsPerTx/8; + std::fill(outputBlock.begin(), outputBlock.end(), 0.0f); + std::uint16_t nFreq = 0; + + if (sampleRateOut != sampleRate) { + for (int k = 0; k < nDataBitsPerTx; ++k) { + double freq = freqStart_hz + freqDelta_hz*k; + + double phaseOffset = phaseOffsets[k]; + double curHzPerFrame = sampleRateOut/samplesPerFrame; + double curIHzPerFrame = 1.0/curHzPerFrame; + for (int i = 0; i < samplesPerFrameOut; i++) { + bit1Amplitude[k][i] = std::sin((2.0*M_PI*(i + frameId*samplesPerFrameOut))*freq*isamplesPerFrame*curIHzPerFrame + phaseOffset); + } + for (int i = 0; i < samplesPerFrameOut; i++) { + bit0Amplitude[k][i] = std::sin((2.0*M_PI*(i + frameId*samplesPerFrameOut))*(freq + hzPerFrame*d0)*isamplesPerFrame*curIHzPerFrame + phaseOffset); + } + } + } + + if (frameId < nMarkerFrames) { + nFreq = nBitsInMarker; + + for (int i = 0; i < nBitsInMarker; ++i) { + if (i%2 == 0) { + ::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId, nMarkerFrames); + } else { + ::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId, nMarkerFrames); + } + } + } else if (frameId < nMarkerFrames + nPostMarkerFrames) { + nFreq = nBitsInMarker; + + for (int i = 0; i < nBitsInMarker; ++i) { + if (i%2 == 0) { + ::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId - nMarkerFrames, nPostMarkerFrames); + } else { + ::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId - nMarkerFrames, nPostMarkerFrames); + } + } + } else if (frameId < + (nMarkerFrames + nPostMarkerFrames) + + ((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx) { + int dataOffset = frameId - nMarkerFrames - nPostMarkerFrames; + int cycleModMain = dataOffset%framesPerTx; + dataOffset /= framesPerTx; + dataOffset *= nBytesPerTx; + + dataBits.fill(0); + + if (paramFreqDelta > 1) { + for (int j = 0; j < nBytesPerTx; ++j) { + for (int i = 0; i < 8; ++i) { + dataBits[j*8 + i] = encodedData[dataOffset + j] & (1 << i); + } + } + + for (int k = 0; k < nDataBitsPerTx; ++k) { + ++nFreq; + if (dataBits[k] == false) { + ::addAmplitudeSmooth(bit0Amplitude[k], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); + continue; + } + ::addAmplitudeSmooth(bit1Amplitude[k], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); + } + } else { + for (int j = 0; j < nBytesPerTx; ++j) { + { + uint8_t d = encodedData[dataOffset + j] & 15; + dataBits[(2*j + 0)*16 + d] = 1; + } + { + uint8_t d = encodedData[dataOffset + j] & 240; + dataBits[(2*j + 1)*16 + (d >> 4)] = 1; + } + } + + for (int k = 0; k < 2*nBytesPerTx*16; ++k) { + if (dataBits[k] == 0) continue; + + ++nFreq; + if (k%2) { + ::addAmplitudeSmooth(bit0Amplitude[k/2], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); + } else { + ::addAmplitudeSmooth(bit1Amplitude[k/2], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx); + } + } + } + } else { + textToSend = ""; + hasData = false; + } + + if (nFreq == 0) nFreq = 1; + float scale = 1.0f/nFreq; + for (int i = 0; i < samplesPerFrameOut; ++i) { + outputBlock[i] *= scale; + } + + for (int i = 0; i < samplesPerFrameOut; ++i) { + outputBlock16[frameId*samplesPerFrameOut + i] = std::round(32000.0*outputBlock[i]); + } + ++frameId; + } + SDL_QueueAudio(devid_out, outputBlock16.data(), 2*frameId*samplesPerFrameOut); + } + + void receive() { + static int nCalls = 0; + static float tSum_ms = 0.0f; + auto tCallStart = std::chrono::high_resolution_clock::now(); + + if (needUpdate) { + init(0, ""); + needUpdate = false; + } + + while (hasData == false) { + // read capture data + int nBytesRecorded = SDL_DequeueAudio(devid_in, sampleAmplitude.data(), samplesPerFrame*sampleSizeBytes); + if (nBytesRecorded != 0) { + { + sampleAmplitudeHistory[historyId] = sampleAmplitude; + + if (++historyId >= ::kMaxSpectrumHistory) { + historyId = 0; + } + + if (historyId == 0 && receivingData == false) { + std::fill(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.end(), 0.0f); + for (auto & s : sampleAmplitudeHistory) { + for (int i = 0; i < samplesPerFrame; ++i) { + sampleAmplitudeAverage[i] += s[i]; + } + } + float norm = 1.0f/::kMaxSpectrumHistory; + for (int i = 0; i < samplesPerFrame; ++i) { + sampleAmplitudeAverage[i] *= norm; + } + + // calculate spectrum + std::copy(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.begin() + samplesPerFrame, fftIn); + + fftwf_execute(fftPlan); + + for (int i = 0; i < samplesPerFrame; ++i) { + sampleSpectrumTmp[i] = (fftOut[i][0]*fftOut[i][0] + fftOut[i][1]*fftOut[i][1]); + } + for (int i = 1; i < samplesPerFrame/2; ++i) { + sampleSpectrumTmp[i] += sampleSpectrumTmp[samplesPerFrame - i]; + sampleSpectrumTmp[samplesPerFrame - i] = 0.0f; + } + + sampleSpectrum = sampleSpectrumTmp; + } + + if (framesLeftToRecord > 0) { + std::copy(sampleAmplitude.begin(), + sampleAmplitude.begin() + samplesPerFrame, + recordedAmplitude.data() + (framesToRecord - framesLeftToRecord)*samplesPerFrame); + + if (--framesLeftToRecord <= 0) { + std::fill(sampleSpectrum.begin(), sampleSpectrum.end(), 0.0f); + analyzingData = true; + } + } + } + + if (analyzingData) { + int nBytesPerTx = nDataBitsPerTx/8; + int stepsPerFrame = 16; + int step = samplesPerFrame/stepsPerFrame; + + std::fill(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.end(), 0.0f); + + int offsetStart = 0; + + framesToAnalyze = nMarkerFrames*stepsPerFrame; + framesLeftToAnalyze = framesToAnalyze; + + bool isValid = false; + //for (int ii = nMarkerFrames*stepsPerFrame/2; ii < (nMarkerFrames + nPostMarkerFrames)*stepsPerFrame; ++ii) { + for (int ii = nMarkerFrames*stepsPerFrame - 1; ii >= nMarkerFrames*stepsPerFrame/2; --ii) { + offsetStart = ii; + + for (int itx = 0; itx < 1024; ++itx) { + int offsetTx = offsetStart + itx*framesPerTx*stepsPerFrame; + if (offsetTx >= recvDuration_frames*stepsPerFrame) { + break; + } + + std::copy( + recordedAmplitude.begin() + offsetTx*step, + recordedAmplitude.begin() + offsetTx*step + samplesPerFrame, fftIn); + + for (int k = 1; k < framesPerTx-1; ++k) { + for (int i = 0; i < samplesPerFrame; ++i) { + fftIn[i] += recordedAmplitude[(offsetTx + k*stepsPerFrame)*step + i]; + } + } + + fftwf_execute(fftPlan); + + for (int i = 0; i < samplesPerFrame; ++i) { + sampleSpectrumTmp[i] = (fftOut[i][0]*fftOut[i][0] + fftOut[i][1]*fftOut[i][1]); + } + for (int i = 1; i < samplesPerFrame/2; ++i) { + sampleSpectrumTmp[i] += sampleSpectrumTmp[samplesPerFrame - i]; + sampleSpectrumTmp[samplesPerFrame - i] = 0.0f; + } + + uint8_t curByte = 0; + if (paramFreqDelta > 1) { + for (int i = 0; i < nDataBitsPerTx; ++i) { + int k = i%8; + int bin = std::round(dataFreqs_hz[i]*ihzPerFrame); + if (sampleSpectrumTmp[bin] > 1*sampleSpectrumTmp[bin + d0]) { + curByte += 1 << k; + } else if (sampleSpectrumTmp[bin + d0] > 1*sampleSpectrumTmp[bin]) { + } else { + } + if (k == 7) { + encodedData[itx*nBytesPerTx + i/8] = curByte; + curByte = 0; + } + } + } else { + for (int i = 0; i < 2*nBytesPerTx; ++i) { + int bin = std::round(dataFreqs_hz[0]*ihzPerFrame) + i*16; + + int kmax = 0; + double amax = 0.0; + for (int k = 0; k < 16; ++k) { + if (sampleSpectrumTmp[bin + k] > amax) { + kmax = k; + amax = sampleSpectrumTmp[bin + k]; + } + } + + if (i%2) { + curByte += (kmax << 4); + encodedData[itx*nBytesPerTx + i/2] = curByte; + curByte = 0; + } else { + curByte = kmax; + } + } + } + } + + if ((rs->Decode(encodedData.data(), rxData.data()) == 0) && ((rxData[0] == 'O') || rxData[0] == 'A')) { + if (rxData[0] == 'A') { + printf("[ANSWER] Received SDP sound data successfully!\n"); + } else if (rxData[0] == 'O') { + printf("[OFFER] Received SDP sound data successfully!\n"); + } else { + printf("Received SDP sound data succssfully\n"); + } + framesToRecord = 0; + isValid = true; + } + + if (isValid) { + break; + } + --framesLeftToAnalyze; + } + + if (isValid == false) { + printf("Failed to capture SDP sound data. Please try again\n"); + framesToRecord = -1; + } + + receivingData = false; + analyzingData = false; + + framesToAnalyze = 0; + framesLeftToAnalyze = 0; + } + + // check if receiving data + if (receivingData == false) { + bool isReceiving = true; + + for (int i = 0; i < nBitsInMarker; ++i) { + int bin = std::round(dataFreqs_hz[i]*ihzPerFrame); + + if (i%2 == 0) { + if (sampleSpectrum[bin] <= 3.0f*sampleSpectrum[bin + d0]) isReceiving = false; + } else { + if (sampleSpectrum[bin] >= 3.0f*sampleSpectrum[bin + d0]) isReceiving = false; + } + } + + if (isReceiving) { + std::time_t timestamp = std::time(nullptr); + printf("%sReceiving WebRTC SDP sound data from another peer ...\n", std::asctime(std::localtime(×tamp))); + rxData.fill(0); + receivingData = true; + framesToRecord = recvDuration_frames; + framesLeftToRecord = recvDuration_frames; + } + } + } else { + break; + } + + ++nIterations; + } + + auto tCallEnd = std::chrono::high_resolution_clock::now(); + tSum_ms += getTime_ms(tCallStart, tCallEnd); + if (++nCalls == 10) { + averageRxTime_ms = tSum_ms/nCalls; + tSum_ms = 0.0f; + nCalls = 0; + } + + if ((int) SDL_GetQueuedAudioSize(devid_in) > 32*sampleSizeBytes*samplesPerFrame) { + printf("nIter = %d, Queue size: %d\n", nIterations, SDL_GetQueuedAudioSize(devid_in)); + SDL_ClearQueuedAudio(devid_in); + } + } + + int nIterations; + bool needUpdate = false; + + int paramFreqDelta = 6; + int paramFreqStart = 40; + int paramFramesPerTx = 6; + int paramBytesPerTx = 2; + int paramECCBytesPerTx = 32; + int paramVolume = 10; + + // Rx + bool receivingData; + bool analyzingData; + + fftwf_plan fftPlan = 0; + float *fftIn; + fftwf_complex *fftOut = 0; + + ::AmplitudeData sampleAmplitude; + + ::SpectrumData sampleSpectrum; + ::SpectrumData sampleSpectrumTmp; + + std::array rxData; + std::array encodedData; + + int historyId = 0; + ::AmplitudeData sampleAmplitudeAverage; + std::array<::AmplitudeData, ::kMaxSpectrumHistory> sampleAmplitudeHistory; + + ::RecordedData recordedAmplitude; + + // Tx + bool hasData; + int sampleSizeBytes; + float sampleRate; + float sampleRateOut; + int samplesPerFrame; + float isamplesPerFrame; + + ::AmplitudeData outputBlock; + ::AmplitudeData16 outputBlock16; + + std::array<::AmplitudeData, ::kMaxDataBits> bit1Amplitude; + std::array<::AmplitudeData, ::kMaxDataBits> bit0Amplitude; + + float sendVolume; + float hzPerFrame; + float ihzPerFrame; + + int d0 = 1; + float freqStart_hz; + float freqDelta_hz; + + int frameId; + int nRampFrames; + int nRampFramesBegin; + int nRampFramesEnd; + int nRampFramesBlend; + int dataId; + int framesPerTx; + int framesToAnalyze; + int framesLeftToAnalyze; + int framesToRecord; + int framesLeftToRecord; + int nBitsInMarker; + int nMarkerFrames; + int nPostMarkerFrames; + int recvDuration_frames; + + std::array dataBits; + std::array phaseOffsets; + std::array dataFreqs_hz; + + int nDataBitsPerTx; + int nECCBytesPerTx; + int sendDataLength; + + RS::ReedSolomon *rs = nullptr; + + float averageRxTime_ms = 0.0; + + std::string textToSend; +}; + +// JS interface +extern "C" { + int setText(int textLength, const char * text) { + g_data->init(textLength, text); + return 0; + } + + int getText(char * text) { + std::copy(g_data->rxData.begin(), g_data->rxData.end(), text); + return 0; + } + + int getSampleRate() { + return g_data->sampleRate; + } + + float getAverageRxTime_ms() { + return g_data->averageRxTime_ms; + } + + int getFramesToRecord() { + return g_data->framesToRecord; + } + + int getFramesLeftToRecord() { + return g_data->framesLeftToRecord; + } + + int getFramesToAnalyze() { + return g_data->framesToAnalyze; + } + + int getFramesLeftToAnalyze() { + return g_data->framesLeftToAnalyze; + } + + void setParameters( + int paramFreqDelta, + int paramFreqStart, + int paramFramesPerTx, + int paramBytesPerTx, + int /*paramECCBytesPerTx*/, + int paramVolume) { + if (g_data == nullptr) return; + + g_data->paramFreqDelta = paramFreqDelta; + g_data->paramFreqStart = paramFreqStart; + g_data->paramFramesPerTx = paramFramesPerTx; + g_data->paramBytesPerTx = paramBytesPerTx; + g_data->paramVolume = paramVolume; + + g_data->needUpdate = true; + } +} + +// main loop +void update() { + SDL_Event e; + SDL_bool shouldTerminate = SDL_FALSE; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + shouldTerminate = SDL_TRUE; + } + } + + if (g_data->hasData == false) { + SDL_PauseAudioDevice(devid_out, SDL_FALSE); + + static auto tLastNoData = std::chrono::high_resolution_clock::now(); + auto tNow = std::chrono::high_resolution_clock::now(); + + if (SDL_GetQueuedAudioSize(devid_out) == 0) { + SDL_PauseAudioDevice(devid_in, SDL_FALSE); + if (::getTime_ms(tLastNoData, tNow) > 500.0f) { + g_data->receive(); + } else { + SDL_ClearQueuedAudio(devid_in); + } + } else { + tLastNoData = tNow; + //SDL_ClearQueuedAudio(devid_in); + //SDL_Delay(10); + } + } else { + SDL_PauseAudioDevice(devid_out, SDL_TRUE); + SDL_PauseAudioDevice(devid_in, SDL_TRUE); + + g_data->send(); + } + + if (shouldTerminate) { + SDL_Log("Shutting down.\n"); + SDL_PauseAudioDevice(devid_in, 1); + SDL_CloseAudioDevice(devid_in); + SDL_PauseAudioDevice(devid_out, 1); + SDL_CloseAudioDevice(devid_out); + SDL_CloseAudio(); + SDL_Quit(); + #ifdef __EMSCRIPTEN__ + emscripten_cancel_main_loop(); + #endif + } +} + +int main(int /*argc*/, char** argv) { + printf("Build time: %s\n", BUILD_TIMESTAMP); + + const char *captureDeviceName = argv[1]; + + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + if (SDL_Init(SDL_INIT_AUDIO) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); + return (1); + } + + SDL_SetHintWithPriority( SDL_HINT_AUDIO_RESAMPLING_MODE, "medium", SDL_HINT_OVERRIDE ); + + { + int devcount = SDL_GetNumAudioDevices(SDL_FALSE); + for (int i = 0; i < devcount; i++) { + printf("Output device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_FALSE)); + } + } + { + int devcount = SDL_GetNumAudioDevices(SDL_TRUE); + for (int i = 0; i < devcount; i++) { + printf("Capture device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_TRUE)); + } + } + + SDL_AudioSpec desiredSpec; + SDL_zero(desiredSpec); + + desiredSpec.freq = ::kBaseSampleRate; + desiredSpec.format = AUDIO_S16SYS; + desiredSpec.channels = 1; + desiredSpec.samples = 16*1024; + desiredSpec.callback = NULL; + + SDL_AudioSpec obtainedSpec; + SDL_zero(obtainedSpec); + + //devid_out = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desiredSpec, &obtainedSpec, SDL_AUDIO_ALLOW_ANY_CHANGE); + //devid_out = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desiredSpec, &obtainedSpec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + devid_out = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desiredSpec, &obtainedSpec, 0); + if (!devid_out) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for playback: %s!\n", SDL_GetError()); + SDL_Quit(); + exit(1); + } + + printf("Obtained spec for output device (SDL Id = %d):\n", devid_out); + printf(" - Sample rate: %d (required: %d)\n", obtainedSpec.freq, desiredSpec.freq); + printf(" - Format: %d (required: %d)\n", obtainedSpec.format, desiredSpec.format); + printf(" - Channels: %d (required: %d)\n", obtainedSpec.channels, desiredSpec.channels); + printf(" - Samples per frame: %d (required: %d)\n", obtainedSpec.samples, desiredSpec.samples); + + if (obtainedSpec.format != desiredSpec.format || + obtainedSpec.channels != desiredSpec.channels || + obtainedSpec.samples != desiredSpec.samples) { + SDL_CloseAudio(); + throw std::runtime_error("Failed to initialize desired SDL_OpenAudio!"); + } + + SDL_AudioSpec captureSpec; + captureSpec = obtainedSpec; + captureSpec.freq = ::kBaseSampleRate; + captureSpec.format = AUDIO_F32SYS; + captureSpec.samples = 1024; + + SDL_Log("Opening capture device %s%s%s...\n", + captureDeviceName ? "'" : "", + captureDeviceName ? captureDeviceName : "[[default]]", + captureDeviceName ? "'" : ""); + + devid_in = SDL_OpenAudioDevice(argv[1], SDL_TRUE, &captureSpec, &captureSpec, 0); + if (!devid_in) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for capture: %s!\n", SDL_GetError()); + SDL_Quit(); + exit(1); + } + + printf("Obtained spec for input device (SDL Id = %d):\n", devid_out); + printf(" - Sample rate: %d\n", captureSpec.freq); + printf(" - Format: %d (required: %d)\n", captureSpec.format, desiredSpec.format); + printf(" - Channels: %d (required: %d)\n", captureSpec.channels, desiredSpec.channels); + printf(" - Samples per frame: %d\n", captureSpec.samples); + + int sampleSizeBytes = 4; + //switch (obtainedSpec.format) { + // case AUDIO_U8: + // case AUDIO_S8: + // sampleSizeBytes = 1; + // break; + // case AUDIO_U16SYS: + // case AUDIO_S16SYS: + // sampleSizeBytes = 2; + // break; + // case AUDIO_S32SYS: + // case AUDIO_F32SYS: + // sampleSizeBytes = 4; + // break; + //} + + g_data = new DataRxTx(obtainedSpec.freq, ::kBaseSampleRate, captureSpec.samples, sampleSizeBytes, ""); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(update, 60, 1); +#else + while(true) { + SDL_Delay(20); + update(); + } +#endif + + delete g_data; + return 0; +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..0b61154 --- /dev/null +++ b/main.js @@ -0,0 +1,788 @@ +/*! \file main.js + * \brief File transfer with WebRTC. Signaling is done through sound + * \author Georgi Gerganov + */ + +var kOfferNumCandidates = 5; +var kOfferName = '-'; +var kOfferUsername = '-'; +var kOfferSessionId = '1337'; +var kOfferSessionVersion = '0'; +var kOfferLocalhost = '0.0.0.0'; +var kOfferBundle = 'sdparta_0'; +var kOfferPort = '5400'; +var kOfferCandidateFoundation = '0'; +var kOfferCandidateComponent = '1'; +var kOfferCandidatePriority = '2122252543'; +var kOfferUfrag = 'bc105aa9'; +var kOfferPwd = '52f0a329e7fd93662f50828f617b408d'; +var kOfferFingerprint = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'; + +var kAnswerNumCandidates = 5; +var kAnswerName = '-'; +var kAnswerUsername = '-'; +var kAnswerSessionId = '1338'; +var kAnswerSessionVersion = '0'; +var kAnswerLocalhost = '0.0.0.0'; +var kAnswerBundle = 'sdparta_0'; +var kAnswerPort = '5400'; +var kAnswerCandidateFoundation = '0'; +var kAnswerCandidateComponent = '1'; +var kAnswerCandidatePriority = '2122252543'; +var kAnswerUfrag = 'c417de3e'; +var kAnswerPwd = '1aa0e1241c16687064c4fd31b8fc367a'; +var kAnswerFingerprint = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'; + +function getOfferTemplate() { + return "v=0\r\n" + + "o="+kOfferUsername+" "+kOfferSessionId+" "+kOfferSessionVersion+" IN IP4 "+kOfferLocalhost+"\r\n" + + "s="+kOfferName+"\r\n" + + "t=0 0\r\n" + + "a=sendrecv\r\n" + + "a=fingerprint:sha-256 "+kOfferFingerprint+"\r\n" + + "a=group:BUNDLE "+kOfferBundle+"\r\n" + + "a=ice-options:trickle\r\n" + + "a=msid-semantic:WMS *\r\n" + + "m=application "+kOfferPort+" DTLS/SCTP 5000\r\n" + + "c=IN IP4 "+kOfferLocalhost+"\r\n" + + "a=candidate:0 "+kOfferCandidateComponent+" UDP "+kOfferCandidatePriority+" "+kOfferLocalhost+" "+kOfferPort+" typ host\r\n" + + "a=candidate:1 "+kOfferCandidateComponent+" UDP "+kOfferCandidatePriority+" "+kOfferLocalhost+" "+kOfferPort+" typ host\r\n" + + "a=candidate:2 "+kOfferCandidateComponent+" UDP "+kOfferCandidatePriority+" "+kOfferLocalhost+" "+kOfferPort+" typ host\r\n" + + "a=candidate:3 "+kOfferCandidateComponent+" UDP "+kOfferCandidatePriority+" "+kOfferLocalhost+" "+kOfferPort+" typ host\r\n" + + "a=candidate:4 "+kOfferCandidateComponent+" UDP "+kOfferCandidatePriority+" "+kOfferLocalhost+" "+kOfferPort+" typ host\r\n" + + "a=sendrecv\r\n" + + "a=end-of-candidates\r\n" + + "a=ice-pwd:"+kOfferPwd+"\r\n" + + "a=ice-ufrag:"+kOfferUfrag+"\r\n" + + "a=mid:"+kOfferBundle+"\r\n" + + "a=sctpmap:5000 webrtc-datachannel 256\r\n" + + "a=setup:actpass\r\n" + + "a=max-message-size:1073741823\r\n"; +} + +function getAnswerTemplate() { + return "v=0\r\n" + + "o="+kAnswerUsername+" "+kAnswerSessionId+" "+kAnswerSessionVersion+" IN IP4 "+kAnswerLocalhost+"\r\n" + + "s="+kAnswerName+"\r\n" + + "t=0 0\r\n" + + "a=sendrecv\r\n" + + "a=fingerprint:sha-256 "+kAnswerFingerprint+"\r\n" + + "a=group:BUNDLE "+kAnswerBundle+"\r\n" + + "a=ice-options:trickle\r\n" + + "a=msid-semantic:WMS *\r\n" + + "m=application "+kAnswerPort+" DTLS/SCTP 5000\r\n" + + "c=IN IP4 "+kAnswerLocalhost+"\r\n" + + "a=candidate:0 "+kAnswerCandidateComponent+" UDP "+kAnswerCandidatePriority+" "+kAnswerLocalhost+" "+kAnswerPort+" typ host\r\n" + + "a=candidate:1 "+kAnswerCandidateComponent+" UDP "+kAnswerCandidatePriority+" "+kAnswerLocalhost+" "+kAnswerPort+" typ host\r\n" + + "a=candidate:2 "+kAnswerCandidateComponent+" UDP "+kAnswerCandidatePriority+" "+kAnswerLocalhost+" "+kAnswerPort+" typ host\r\n" + + "a=candidate:3 "+kAnswerCandidateComponent+" UDP "+kAnswerCandidatePriority+" "+kAnswerLocalhost+" "+kAnswerPort+" typ host\r\n" + + "a=candidate:4 "+kAnswerCandidateComponent+" UDP "+kAnswerCandidatePriority+" "+kAnswerLocalhost+" "+kAnswerPort+" typ host\r\n" + + "a=sendrecv\r\n" + + "a=end-of-candidates\r\n" + + "a=ice-pwd:"+kAnswerPwd+"\r\n" + + "a=ice-ufrag:"+kAnswerUfrag+"\r\n" + + "a=mid:"+kAnswerBundle+"\r\n" + + "a=sctpmap:5000 webrtc-datachannel 256\r\n" + + "a=setup:active\r\n" + + "a=max-message-size:1073741823\r\n"; +} + +// Taken from: https://github.com/diafygi/webrtc-ips +// get the IP addresses associated with an account +function getIPs(callback){ + var ip_dups = {}; + + //compatibility for firefox and chrome + var RTCPeerConnection = window.RTCPeerConnection + || window.mozRTCPeerConnection + || window.webkitRTCPeerConnection; + var useWebKit = !!window.webkitRTCPeerConnection; + + //bypass naive webrtc blocking using an iframe + if(!RTCPeerConnection){ + //NOTE: you need to have an iframe in the page right above the script tag + // + // + //