mirror of
https://github.com/ggerganov/wave-share.git
synced 2026-03-31 23:16:48 +08:00
Initial commit
This commit is contained in:
821
main.cpp
Normal file
821
main.cpp
Normal file
@@ -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 <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_audio.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <algorithm>
|
||||
|
||||
#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<float, kMaxSamplesPerFrame>;
|
||||
using AmplitudeData16 = std::array<int16_t, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
using SpectrumData = std::array<float, kMaxSamplesPerFrame>;
|
||||
using RecordedData = std::array<float, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
|
||||
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 <class T>
|
||||
float getTime_ms(const T & tStart, const T & tEnd) {
|
||||
return ((float)(std::chrono::duration_cast<std::chrono::microseconds>(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<const uint8_t *>(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<char, ::kMaxDataSize> 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<char, ::kMaxDataSize> rxData;
|
||||
std::array<std::uint8_t, ::kMaxDataSize> 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<bool, ::kMaxDataBits> dataBits;
|
||||
std::array<double, ::kMaxDataBits> phaseOffsets;
|
||||
std::array<double, ::kMaxDataBits> 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;
|
||||
}
|
||||
788
main.js
Normal file
788
main.js
Normal file
@@ -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
|
||||
//
|
||||
//<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
|
||||
//<script>...getIPs called in here...
|
||||
//
|
||||
var win = iframe.contentWindow;
|
||||
RTCPeerConnection = win.RTCPeerConnection
|
||||
|| win.mozRTCPeerConnection
|
||||
|| win.webkitRTCPeerConnection;
|
||||
useWebKit = !!win.webkitRTCPeerConnection;
|
||||
}
|
||||
|
||||
//minimal requirements for data connection
|
||||
var mediaConstraints = {
|
||||
optional: [{RtpDataChannels: true}]
|
||||
};
|
||||
|
||||
var servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};
|
||||
|
||||
//construct a new RTCPeerConnection
|
||||
var pc = new RTCPeerConnection(servers, mediaConstraints);
|
||||
|
||||
function handleCandidate(candidate){
|
||||
//match just the IP address
|
||||
//console.log(candidate);
|
||||
var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
|
||||
var regex_res = ip_regex.exec(candidate);
|
||||
if (regex_res == null) return;
|
||||
var ip_addr = ip_regex.exec(candidate)[1];
|
||||
|
||||
//remove duplicates
|
||||
if(ip_dups[ip_addr] === undefined)
|
||||
callback(ip_addr);
|
||||
|
||||
ip_dups[ip_addr] = true;
|
||||
}
|
||||
|
||||
//listen for candidate events
|
||||
pc.onicecandidate = function(ice){
|
||||
|
||||
//skip non-candidate events
|
||||
if(ice.candidate)
|
||||
handleCandidate(ice.candidate.candidate);
|
||||
};
|
||||
|
||||
//create a bogus data channel
|
||||
pc.createDataChannel("");
|
||||
|
||||
//create an offer sdp
|
||||
pc.createOffer(function(result){
|
||||
|
||||
//trigger the stun server request
|
||||
pc.setLocalDescription(result, function(){}, function(){});
|
||||
|
||||
}, function(){});
|
||||
|
||||
//wait for a while to let everything done
|
||||
setTimeout(function(){
|
||||
//read candidate info from local description
|
||||
var lines = pc.localDescription.sdp.split('\n');
|
||||
|
||||
lines.forEach(function(line){
|
||||
if(line.indexOf('a=candidate:') === 0)
|
||||
handleCandidate(line);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function transmitRelevantDataSender(sdp) {
|
||||
var res = parseSDP(sdp);
|
||||
var hashparts = null;
|
||||
if (typeof res.fingerprint === 'undefined') {
|
||||
hashparts = res.media[0].fingerprint.hash.split(":");
|
||||
} else {
|
||||
hashparts = res.fingerprint.hash.split(":");
|
||||
}
|
||||
|
||||
var r = new Uint8Array(256);
|
||||
r[0] = "O".charCodeAt(0);
|
||||
|
||||
var ip = document.getElementById('available-networks').value;
|
||||
r[2] = parseInt(ip.split(".")[0]);
|
||||
r[3] = parseInt(ip.split(".")[1]);
|
||||
r[4] = parseInt(ip.split(".")[2]);
|
||||
r[5] = parseInt(ip.split(".")[3]);
|
||||
|
||||
for (var m in res.media[0].candidates) {
|
||||
if (res.media[0].candidates[m].ip != ip) continue;
|
||||
//if (res.media[0].candidate[m].transport != "UDP") continue;
|
||||
var port = res.media[0].candidates[m].port;
|
||||
r[6] = parseInt(port & 0xFF00) >> 8
|
||||
r[7] = parseInt(port & 0x00FF);
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 32; ++i) {
|
||||
r[8+i] = parseInt(hashparts[i], 16);
|
||||
}
|
||||
|
||||
var credentials = "";
|
||||
credentials += res.media[0].iceUfrag;
|
||||
credentials += " ";
|
||||
credentials += res.media[0].icePwd;
|
||||
for (var i = 0; i < credentials.length; ++i) {
|
||||
r[40 + i] = credentials.charCodeAt(i);
|
||||
}
|
||||
|
||||
r[1] = 40 + credentials.length;
|
||||
|
||||
var buffer = Module._malloc(256);
|
||||
Module.writeArrayToMemory(r, buffer, 256);
|
||||
Module.cwrap('setText', 'number', ['number', 'buffer'])(r[1], buffer);
|
||||
Module._free(buffer);
|
||||
}
|
||||
|
||||
function transmitRelevantDataReceiver(sdp) {
|
||||
var res = parseSDP(sdp);
|
||||
var hashparts = null;
|
||||
if (typeof res.fingerprint === 'undefined') {
|
||||
hashparts = res.media[0].fingerprint.hash.split(":");
|
||||
} else {
|
||||
hashparts = res.fingerprint.hash.split(":");
|
||||
}
|
||||
|
||||
var r = new Uint8Array(256);
|
||||
r[0] = "A".charCodeAt(0);
|
||||
|
||||
var ip = document.getElementById('available-networks').value;
|
||||
r[2] = parseInt(ip.split(".")[0]);
|
||||
r[3] = parseInt(ip.split(".")[1]);
|
||||
r[4] = parseInt(ip.split(".")[2]);
|
||||
r[5] = parseInt(ip.split(".")[3]);
|
||||
|
||||
for (var m in res.media[0].candidates) {
|
||||
if (res.media[0].candidates[m].ip != ip) continue;
|
||||
//if (res.media[0].candidate[m].transport != "UDP") continue;
|
||||
var port = res.media[0].candidates[m].port;
|
||||
r[6] = parseInt(port & 0xFF00) >> 8
|
||||
r[7] = parseInt(port & 0x00FF);
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 32; ++i) {
|
||||
r[8+i] = parseInt(hashparts[i], 16);
|
||||
}
|
||||
|
||||
var credentials = "";
|
||||
credentials += res.media[0].iceUfrag;
|
||||
credentials += " ";
|
||||
credentials += res.media[0].icePwd;
|
||||
for (var i = 0; i < credentials.length; ++i) {
|
||||
r[40 + i] = credentials.charCodeAt(i);
|
||||
}
|
||||
|
||||
r[1] = 40 + credentials.length;
|
||||
|
||||
var buffer = Module._malloc(256);
|
||||
Module.writeArrayToMemory(r, buffer, 256);
|
||||
Module.cwrap('setText', 'number', ['number', 'buffer'])(r[1], buffer);
|
||||
Module._free(buffer);
|
||||
}
|
||||
|
||||
//
|
||||
// Web RTC, Common
|
||||
//
|
||||
|
||||
var sdpConstraints = { optional: [{RtpDataChannels: true}] };
|
||||
|
||||
var oldRxData = null;
|
||||
var lastSenderRequest = null
|
||||
var lastSenderRequestSDP = null
|
||||
var lastSenderRequestTimestamp = null
|
||||
var lastReceiverAnswer = null
|
||||
var lastReceiverAnswerSDP = null
|
||||
var lastReceiverAnswerTimestamp = null
|
||||
|
||||
var bitrateDiv = document.querySelector('div#bitrate');
|
||||
var fileInput = document.querySelector('input#fileInput');
|
||||
var downloadAnchor = document.querySelector('a#download');
|
||||
var sendProgress = document.querySelector('progress#sendProgress');
|
||||
var receiveProgress = document.querySelector('progress#receiveProgress');
|
||||
var statusMessage = document.querySelector('span#status');
|
||||
var peerInfo = document.querySelector('a#peer-info');
|
||||
var peerReceive = document.querySelector('a#peer-receive');
|
||||
|
||||
var receiveBuffer = [];
|
||||
var receivedSize = 0;
|
||||
|
||||
var bytesPrev = 0;
|
||||
var timestampPrev = 0;
|
||||
var timestampStart;
|
||||
var statsInterval = null;
|
||||
var bitrateMax = 0;
|
||||
|
||||
//
|
||||
// Web RTC, Sender
|
||||
//
|
||||
|
||||
var senderPC;
|
||||
var senderDC;
|
||||
|
||||
function onAddIceCandidateSuccess() {
|
||||
console.log('AddIceCandidate success.');
|
||||
}
|
||||
|
||||
function onAddIceCandidateError(error) {
|
||||
console.log('Failed to add Ice Candidate: ' + error.toString());
|
||||
}
|
||||
|
||||
function createOfferSDP() {
|
||||
lastReceiverAnswerSDP = null;
|
||||
|
||||
senderDC = senderPC.createDataChannel("fileTransfer");
|
||||
senderDC.binaryType = 'arraybuffer';
|
||||
senderDC.onopen = onSendChannelStateChange;
|
||||
senderDC.onclose = onSendChannelStateChange;
|
||||
senderPC.createOffer().then(function(e) {
|
||||
var res = parseSDP(e.sdp);
|
||||
|
||||
res.name = kOfferName;
|
||||
res.origin.username = kOfferUsername;
|
||||
res.origin.sessionId = kOfferSessionId;
|
||||
res.origin.sessionVersion = kOfferSessionVersion;
|
||||
res.groups[0].mids = kOfferBundle;
|
||||
res.media[0].mid = kOfferBundle;
|
||||
res.media[0].connection.ip = document.getElementById('available-networks').value;
|
||||
|
||||
e.sdp = writeSDP(res);
|
||||
senderPC.setLocalDescription(e);
|
||||
});
|
||||
};
|
||||
|
||||
function senderInit() {
|
||||
senderPC = new RTCPeerConnection(null);
|
||||
|
||||
senderPC.oniceconnectionstatechange = function(e) {
|
||||
var state = senderPC.iceConnectionState;
|
||||
};
|
||||
|
||||
senderPC.onicecandidate = function(e) {
|
||||
if (e.candidate) return;
|
||||
transmitRelevantDataSender(senderPC.localDescription.sdp);
|
||||
}
|
||||
|
||||
createOfferSDP();
|
||||
}
|
||||
|
||||
function senderSend() {
|
||||
if (lastReceiverAnswerSDP == null) return;
|
||||
var answerDesc = new RTCSessionDescription(JSON.parse(lastReceiverAnswerSDP));
|
||||
if (senderPC) {
|
||||
senderPC.setRemoteDescription(answerDesc);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Web RTC, Receiver
|
||||
//
|
||||
|
||||
var receiverPC;
|
||||
var receiverDC;
|
||||
var firstTimeFail = false;
|
||||
|
||||
function updatePeerInfo() {
|
||||
if (typeof Module === 'undefined') return;
|
||||
var framesLeftToRecord = Module.cwrap('getFramesLeftToRecord', 'number', [])();
|
||||
var framesToRecord = Module.cwrap('getFramesToRecord', 'number', [])();
|
||||
var framesLeftToAnalyze = Module.cwrap('getFramesLeftToAnalyze', 'number', [])();
|
||||
var framesToAnalyze = Module.cwrap('getFramesToAnalyze', 'number', [])();
|
||||
|
||||
if (framesToAnalyze > 0) {
|
||||
peerInfo.innerHTML=
|
||||
"Analyzing Rx data: <progress value=" + (framesToAnalyze - framesLeftToAnalyze) +
|
||||
" max=" + (framesToRecord) + "></progress>";
|
||||
peerReceive.innerHTML= "";
|
||||
} else if (framesLeftToRecord > Math.max(0, 0.05*framesToRecord)) {
|
||||
firstTimeFail = true;
|
||||
peerInfo.innerHTML=
|
||||
"Sound handshake in progress: <progress value=" + (framesToRecord - framesLeftToRecord) +
|
||||
" max=" + (framesToRecord) + "></progress>";
|
||||
peerReceive.innerHTML= "";
|
||||
} else if (framesToRecord > 0) {
|
||||
peerInfo.innerHTML= "Analyzing Rx data ...";
|
||||
} else if (framesToRecord == -1) {
|
||||
if (firstTimeFail) {
|
||||
playSound("/media/case-closed");
|
||||
firstTimeFail = false;
|
||||
}
|
||||
peerInfo.innerHTML= "<p style=\"color:red\">Failed to decode Rx data</p>";
|
||||
}
|
||||
}
|
||||
|
||||
function checkRxForPeerData() {
|
||||
if (typeof Module === 'undefined') return;
|
||||
Module.cwrap('getText', 'number', ['buffer'])(bufferRx);
|
||||
var result = "";
|
||||
for(var i = 0; i < 82; ++i){
|
||||
result += (String.fromCharCode((Module.HEAPU8)[bufferRx + i]));
|
||||
brx[i] = (Module.HEAPU8)[bufferRx + i];
|
||||
}
|
||||
|
||||
if (String.fromCharCode(brx[0]) == "O") {
|
||||
var lastSenderRequestTmp = brx;
|
||||
if (lastSenderRequestTmp == lastSenderRequest) return;
|
||||
|
||||
console.log("Received Offer");
|
||||
lastSenderRequest = lastSenderRequestTmp;
|
||||
|
||||
var vals = Array();
|
||||
vals[0] = "";
|
||||
vals[0] += String(brx[2]) + ".";
|
||||
vals[0] += String(brx[3]) + ".";
|
||||
vals[0] += String(brx[4]) + ".";
|
||||
vals[0] += String(brx[5]);
|
||||
|
||||
vals[1] = String(brx[6]*256 + brx[7]);
|
||||
|
||||
vals[2] = "";
|
||||
for (var i = 0; i < 32; ++i) {
|
||||
if (brx[8+i] == 0) {
|
||||
vals[2] += '00';
|
||||
} else if (brx[8+i] < 16) {
|
||||
vals[2] += '0'+brx[8+i].toString(16).toUpperCase();
|
||||
} else {
|
||||
vals[2] += brx[8+i].toString(16).toUpperCase();
|
||||
}
|
||||
if (i < 31) vals[2] += ':';
|
||||
}
|
||||
|
||||
var credentials = "";
|
||||
for (var i = 40; i < brx[1]; ++i) {
|
||||
credentials += String.fromCharCode(brx[i]);
|
||||
}
|
||||
vals[3] = credentials.split(" ")[0];
|
||||
vals[4] = credentials.split(" ")[1];
|
||||
|
||||
var res = parseSDP(getOfferTemplate());
|
||||
|
||||
res.origin.username = kOfferUsername;
|
||||
res.origin.sessionId = kOfferSessionId;
|
||||
res.media[0].iceUfrag = vals[3];
|
||||
res.media[0].icePwd = vals[4];
|
||||
res.media[0].connection.ip = vals[0];
|
||||
res.media[0].port = vals[1];
|
||||
if (typeof res.fingerprint === 'undefined') {
|
||||
res.media[0].fingerprint.hash = vals[2];
|
||||
} else {
|
||||
res.fingerprint.hash = vals[2];
|
||||
}
|
||||
if (typeof res.candidates === 'undefined') {
|
||||
for (var i = 0; i < kOfferNumCandidates; ++i) {
|
||||
res.media[0].candidates[i].ip = vals[0];
|
||||
res.media[0].candidates[i].port = vals[1];
|
||||
res.media[0].candidates[i].priority = kOfferCandidatePriority;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < kOfferNumCandidates; ++i) {
|
||||
res.candidates[i].ip = vals[0];
|
||||
res.candidates[i].port = vals[1];
|
||||
res.candidates[i].priority = kOfferCandidatePriority;
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(writeSDP(res));
|
||||
lastSenderRequestSDP = '{"type":"offer","sdp":'+JSON.stringify(writeSDP(res))+'}';
|
||||
peerInfo.innerHTML= "Receive file from " + vals[0] + " ?";
|
||||
peerReceive.innerHTML= "<button onClick=\"lockoutSubmit(this); receiverInit();\">Receive</button>";
|
||||
playSound("/media/open-ended");
|
||||
|
||||
return;
|
||||
} else {
|
||||
lastSenderRequest = null;
|
||||
}
|
||||
|
||||
if (String.fromCharCode(brx[0]) == "A") {
|
||||
var lastReceiverAnswerTmp = brx;
|
||||
if (lastReceiverAnswerTmp == lastReceiverAnswer) return;
|
||||
|
||||
console.log("Received Answer");
|
||||
lastReceiverAnswer = lastReceiverAnswerTmp;
|
||||
|
||||
var vals = Array();
|
||||
vals[0] = "";
|
||||
vals[0] += String(brx[2]) + ".";
|
||||
vals[0] += String(brx[3]) + ".";
|
||||
vals[0] += String(brx[4]) + ".";
|
||||
vals[0] += String(brx[5]);
|
||||
|
||||
vals[1] = String(brx[6]*256 + brx[7]);
|
||||
|
||||
vals[2] = "";
|
||||
for (var i = 0; i < 32; ++i) {
|
||||
if (brx[8+i] == 0) {
|
||||
vals[2] += '00';
|
||||
} else if (brx[8+i] < 16) {
|
||||
vals[2] += '0'+brx[8+i].toString(16).toUpperCase();
|
||||
} else {
|
||||
vals[2] += brx[8+i].toString(16).toUpperCase();
|
||||
}
|
||||
if (i < 31) vals[2] += ':';
|
||||
}
|
||||
|
||||
var credentials = "";
|
||||
for (var i = 40; i < brx[1]; ++i) {
|
||||
credentials += String.fromCharCode(brx[i]);
|
||||
}
|
||||
vals[3] = credentials.split(" ")[0];
|
||||
vals[4] = credentials.split(" ")[1];
|
||||
|
||||
var res = parseSDP(getAnswerTemplate());
|
||||
|
||||
res.origin.username = kAnswerUsername;
|
||||
res.origin.sessionId = kAnswerSessionId;
|
||||
res.media[0].iceUfrag = vals[3];
|
||||
res.media[0].icePwd = vals[4];
|
||||
res.media[0].connection.ip = vals[0];
|
||||
res.media[0].port = vals[1];
|
||||
if (typeof res.fingerprint === 'undefined') {
|
||||
res.media[0].fingerprint.hash = vals[2];
|
||||
} else {
|
||||
res.fingerprint.hash = vals[2];
|
||||
}
|
||||
if (typeof res.candidates === 'undefined') {
|
||||
for (var i = 0; i < kAnswerNumCandidates; ++i) {
|
||||
res.media[0].candidates[i].ip = vals[0];
|
||||
res.media[0].candidates[i].port = vals[1];
|
||||
res.media[0].candidates[i].priority = kAnswerCandidatePriority;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < kAnswerNumCandidates; ++i) {
|
||||
res.candidates[i].ip = vals[0];
|
||||
res.candidates[i].port = vals[1];
|
||||
res.candidates[i].priority = kAnswerCandidatePriority;
|
||||
}
|
||||
}
|
||||
|
||||
lastReceiverAnswerSDP = '{"type":"answer","sdp":'+JSON.stringify(writeSDP(res))+'}';
|
||||
playSound("/media/open-ended");
|
||||
senderSend();
|
||||
|
||||
return;
|
||||
} else {
|
||||
lastReceiverAnswer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function createAnswerSDP() {
|
||||
if (lastSenderRequestSDP == null) return;
|
||||
var offerDesc = new RTCSessionDescription(JSON.parse(lastSenderRequestSDP));
|
||||
receiverPC.setRemoteDescription(offerDesc,
|
||||
function() {
|
||||
receiverPC.createAnswer(
|
||||
function (e) {
|
||||
var res = parseSDP(e.sdp);
|
||||
|
||||
res.name = kAnswerName;
|
||||
res.origin.username = kAnswerUsername;
|
||||
res.origin.sessionId = kAnswerSessionId;
|
||||
res.origin.sessionVersion = kAnswerSessionVersion;
|
||||
res.media[0].mid = kAnswerBundle;
|
||||
res.media[0].connection.ip = document.getElementById('available-networks').value;
|
||||
|
||||
e.sdp = writeSDP(res);
|
||||
receiverPC.setLocalDescription(e);
|
||||
},
|
||||
function () { console.warn("Couldn't create offer") },
|
||||
sdpConstraints
|
||||
);
|
||||
}, function(e) {
|
||||
console.log("Could not set remote description. Reason: " + e);
|
||||
});
|
||||
};
|
||||
|
||||
function receiverInit() {
|
||||
receiverPC = new RTCPeerConnection(null);
|
||||
|
||||
receiverPC.ondatachannel = receiveChannelCallback;
|
||||
receiverPC.onicecandidate = function(e) {
|
||||
if (e.candidate) return;
|
||||
transmitRelevantDataReceiver(receiverPC.localDescription.sdp);
|
||||
};
|
||||
receiverPC.oniceconnectionstatechange = function(e) {};
|
||||
|
||||
createAnswerSDP();
|
||||
}
|
||||
|
||||
//
|
||||
// File sutff
|
||||
//
|
||||
|
||||
function ab2str(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
||||
}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
|
||||
var bufView = new Uint16Array(buf);
|
||||
for (var i=0, strLen=str.length; i<strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
fileInput.addEventListener('change', handleFileInputChange, false);
|
||||
|
||||
function handleFileInputChange() {
|
||||
var file = fileInput.files[0];
|
||||
if (!file) {
|
||||
console.log('No file chosen');
|
||||
}
|
||||
}
|
||||
|
||||
function sendData() {
|
||||
var file = fileInput.files[0];
|
||||
if (file == null) {
|
||||
peerInfo.innerHTML = "Connection established, but no file selected";
|
||||
return;
|
||||
}
|
||||
peerInfo.innerHTML = "Sending selected file to peer ...";
|
||||
console.log('File is ' + [file.name, file.size, file.type, file.lastModifiedDate ].join(' '));
|
||||
|
||||
// Handle 0 size files.
|
||||
statusMessage.textContent = '';
|
||||
downloadAnchor.textContent = '';
|
||||
if (file.size === 0) {
|
||||
bitrateDiv.innerHTML = '';
|
||||
statusMessage.textContent = 'File is empty, please select a non-empty file';
|
||||
closeDataChannels();
|
||||
return;
|
||||
}
|
||||
senderDC.send(str2ab(file.name));
|
||||
senderDC.send(str2ab(String(file.size)));
|
||||
sendProgress.max = file.size;
|
||||
receiveProgress.max = file.size;
|
||||
var chunkSize = 16384;
|
||||
var sliceFile = function(offset) {
|
||||
var reader = new window.FileReader();
|
||||
reader.onload = (function() {
|
||||
return function(e) {
|
||||
if (senderDC.bufferedAmount > 4*1024*1024) {
|
||||
window.setTimeout(sliceFile, 100, offset);
|
||||
} else {
|
||||
senderDC.send(e.target.result);
|
||||
if (file.size > offset + e.target.result.byteLength) {
|
||||
window.setTimeout(sliceFile, 0, offset + chunkSize);
|
||||
}
|
||||
sendProgress.value = offset + e.target.result.byteLength;
|
||||
}
|
||||
};
|
||||
})(file);
|
||||
var slice = file.slice(offset, offset + chunkSize);
|
||||
reader.readAsArrayBuffer(slice);
|
||||
};
|
||||
sliceFile(0);
|
||||
}
|
||||
|
||||
function closeDataChannels() {
|
||||
console.log('Closing data channels');
|
||||
if (senderDC) {
|
||||
senderDC.close();
|
||||
console.log('Closed data channel with label: ' + senderDC.label);
|
||||
}
|
||||
if (receiverDC) {
|
||||
receiverDC.close();
|
||||
console.log('Closed data channel with label: ' + receiverDC.label);
|
||||
}
|
||||
|
||||
if (senderPC) senderPC.close();
|
||||
if (receiverPC) receiverPC.close();
|
||||
|
||||
senderPC = null;
|
||||
receiverPC = null;
|
||||
console.log('Closed peer connections');
|
||||
|
||||
// re-enable the file select
|
||||
fileInput.disabled = false;
|
||||
}
|
||||
|
||||
function onSendChannelStateChange() {
|
||||
var readyState = senderDC.readyState;
|
||||
console.log('Send channel state is: ' + readyState);
|
||||
if (readyState === 'open') {
|
||||
sendData();
|
||||
}
|
||||
}
|
||||
|
||||
function receiveChannelCallback(event) {
|
||||
console.log('Receive Channel Callback');
|
||||
receiverDC = event.channel;
|
||||
receiverDC.binaryType = 'arraybuffer';
|
||||
receiverDC.onmessage = onReceiveMessageCallback;
|
||||
receiverDC.onopen = onReceiveChannelStateChange;
|
||||
receiverDC.onclose = onReceiveChannelStateChange;
|
||||
|
||||
receivedSize = 0;
|
||||
recvFileName = '';
|
||||
recvFileSize = 0;
|
||||
bitrateMax = 0;
|
||||
downloadAnchor.textContent = '';
|
||||
downloadAnchor.removeAttribute('download');
|
||||
if (downloadAnchor.href) {
|
||||
URL.revokeObjectURL(downloadAnchor.href);
|
||||
downloadAnchor.removeAttribute('href');
|
||||
}
|
||||
}
|
||||
|
||||
var recvFileName = '';
|
||||
var recvFileSize = 0;
|
||||
|
||||
function onReceiveMessageCallback(event) {
|
||||
//console.log('Received Message ' + event.data.byteLength);
|
||||
if (recvFileName == '') {
|
||||
recvFileName = ab2str(event.data);
|
||||
return;
|
||||
}
|
||||
if (recvFileSize == 0) {
|
||||
recvFileSize = parseInt(ab2str(event.data));
|
||||
peerInfo.innerHTML = "Receiving file '" + recvFileName + "' (" + recvFileSize + " bytes) ...";
|
||||
receiveProgress.max = recvFileSize;
|
||||
return;
|
||||
}
|
||||
|
||||
receiveBuffer.push(event.data);
|
||||
receivedSize += event.data.byteLength;
|
||||
|
||||
receiveProgress.value = receivedSize;
|
||||
|
||||
// we are assuming that our signaling protocol told
|
||||
// about the expected file size (and name, hash, etc).
|
||||
//var file = fileInput.files[0];
|
||||
if (receivedSize === recvFileSize) {
|
||||
var received = new window.Blob(receiveBuffer);
|
||||
receiveBuffer = [];
|
||||
|
||||
downloadAnchor.href = URL.createObjectURL(received);
|
||||
downloadAnchor.download = recvFileName;
|
||||
downloadAnchor.textContent = 'Click to download \'' + recvFileName + '\' (' + recvFileSize + ' bytes)';
|
||||
downloadAnchor.style.display = 'block';
|
||||
|
||||
var bitrate = Math.round(8*receivedSize/((new Date()).getTime() - timestampStart));
|
||||
bitrateDiv.innerHTML = 'Average Bitrate: ' + bitrate + ' kbits/sec (max: ' + bitrateMax + ' kbits/sec)';
|
||||
|
||||
if (statsInterval) {
|
||||
window.clearInterval(statsInterval);
|
||||
statsInterval = null;
|
||||
}
|
||||
|
||||
closeDataChannels();
|
||||
}
|
||||
}
|
||||
|
||||
function onReceiveChannelStateChange() {
|
||||
var readyState = receiverDC.readyState;
|
||||
console.log('Receive channel state is: ' + readyState);
|
||||
if (readyState === 'open') {
|
||||
timestampStart = (new Date()).getTime();
|
||||
timestampPrev = timestampStart;
|
||||
statsInterval = window.setInterval(displayStats, 500);
|
||||
window.setTimeout(displayStats, 100);
|
||||
window.setTimeout(displayStats, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// display bitrate statistics.
|
||||
function displayStats() {
|
||||
var display = function(bitrate) {
|
||||
bitrateDiv.innerHTML = '<strong>Current Bitrate:</strong> ' +
|
||||
bitrate + ' kbits/sec';
|
||||
};
|
||||
|
||||
if (receiverPC && receiverPC.iceConnectionState === 'connected') {
|
||||
// Firefox currently does not have data channel stats. See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1136832
|
||||
// Instead, the bitrate is calculated based on the number of
|
||||
// bytes received.
|
||||
var bytesNow = receivedSize;
|
||||
var now = (new Date()).getTime();
|
||||
var bitrate = Math.round(8*(bytesNow - bytesPrev)/(now - timestampPrev));
|
||||
display(bitrate);
|
||||
timestampPrev = now;
|
||||
bytesPrev = bytesNow;
|
||||
if (bitrate > bitrateMax) {
|
||||
bitrateMax = bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
reed-solomon/LICENSE
Normal file
21
reed-solomon/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
Copyright © 2015 Mike Lubinets, github.com/mersinvald
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the “Software”), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
239
reed-solomon/gf.hpp
Normal file
239
reed-solomon/gf.hpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/* Author: Mike Lubinets (aka mersinvald)
|
||||
* Date: 29.12.15
|
||||
*
|
||||
* See LICENSE */
|
||||
|
||||
#ifndef GF_H
|
||||
#define GF_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "poly.hpp"
|
||||
|
||||
#if !defined DEBUG && !defined __CC_ARM
|
||||
#include <assert.h>
|
||||
#else
|
||||
#define assert(dummy)
|
||||
#endif
|
||||
|
||||
|
||||
namespace RS {
|
||||
|
||||
namespace gf {
|
||||
|
||||
|
||||
/* GF tables pre-calculated for 0x11d primitive polynomial */
|
||||
|
||||
const uint8_t exp[512] = {
|
||||
0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c,
|
||||
0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d,
|
||||
0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46,
|
||||
0x8c, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f,
|
||||
0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd,
|
||||
0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9,
|
||||
0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81,
|
||||
0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85,
|
||||
0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8,
|
||||
0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6,
|
||||
0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3,
|
||||
0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82,
|
||||
0x19, 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51,
|
||||
0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12,
|
||||
0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c,
|
||||
0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2,
|
||||
0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98,
|
||||
0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27,
|
||||
0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c,
|
||||
0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe,
|
||||
0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7,
|
||||
0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf,
|
||||
0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f,
|
||||
0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17,
|
||||
0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d,
|
||||
0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1,
|
||||
0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb,
|
||||
0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19,
|
||||
0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2,
|
||||
0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 0x24,
|
||||
0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 0x58,
|
||||
0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2
|
||||
};
|
||||
|
||||
const uint8_t log[256] = {
|
||||
0x0, 0x0, 0x1, 0x19, 0x2, 0x32, 0x1a, 0xc6, 0x3, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x4,
|
||||
0x64, 0xe0, 0xe, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x8, 0x4c, 0x71, 0x5,
|
||||
0x8a, 0x65, 0x2f, 0xe1, 0x24, 0xf, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d,
|
||||
0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x9, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x6,
|
||||
0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36,
|
||||
0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e,
|
||||
0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca,
|
||||
0x5e, 0x9b, 0x9f, 0xa, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x7,
|
||||
0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0xd, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3,
|
||||
0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37,
|
||||
0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2,
|
||||
0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f,
|
||||
0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0xc, 0x6f, 0xf6, 0x6c,
|
||||
0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb,
|
||||
0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0xb, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f,
|
||||
0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* ################################
|
||||
* # OPERATIONS OVER GALUA FIELDS #
|
||||
* ################################ */
|
||||
|
||||
/* @brief Addition in Galua Fields
|
||||
* @param x - left operand
|
||||
* @param y - right operand
|
||||
* @return x + y */
|
||||
inline uint8_t add(uint8_t x, uint8_t y) {
|
||||
return x^y;
|
||||
}
|
||||
|
||||
/* ##### GF substraction ###### */
|
||||
/* @brief Substraction in Galua Fields
|
||||
* @param x - left operand
|
||||
* @param y - right operand
|
||||
* @return x - y */
|
||||
inline uint8_t sub(uint8_t x, uint8_t y) {
|
||||
return x^y;
|
||||
}
|
||||
|
||||
/* @brief Multiplication in Galua Fields
|
||||
* @param x - left operand
|
||||
* @param y - rifht operand
|
||||
* @return x * y */
|
||||
inline uint8_t mul(uint16_t x, uint16_t y){
|
||||
if (x == 0 || y == 0)
|
||||
return 0;
|
||||
return exp[log[x] + log[y]];
|
||||
}
|
||||
|
||||
/* @brief Division in Galua Fields
|
||||
* @param x - dividend
|
||||
* @param y - divisor
|
||||
* @return x / y */
|
||||
inline uint8_t div(uint8_t x, uint8_t y){
|
||||
assert(y != 0);
|
||||
if(x == 0) return 0;
|
||||
return exp[(log[x] + 255 - log[y]) % 255];
|
||||
}
|
||||
|
||||
/* @brief X in power Y w
|
||||
* @param x - operand
|
||||
* @param power - power
|
||||
* @return x^power */
|
||||
inline uint8_t pow(uint8_t x, intmax_t power){
|
||||
intmax_t i = log[x];
|
||||
i *= power;
|
||||
i %= 255;
|
||||
if(i < 0) i = i + 255;
|
||||
return exp[i];
|
||||
}
|
||||
|
||||
/* @brief Inversion in Galua Fields
|
||||
* @param x - number
|
||||
* @return inversion of x */
|
||||
inline uint8_t inverse(uint8_t x){
|
||||
return exp[255 - log[x]]; /* == div(1, x); */
|
||||
}
|
||||
|
||||
/* ##########################
|
||||
* # POLYNOMIALS OPERATIONS #
|
||||
* ########################## */
|
||||
|
||||
/* @brief Multiplication polynomial by scalar
|
||||
* @param &p - source polynomial
|
||||
* @param &newp - destination polynomial
|
||||
* @param x - scalar */
|
||||
inline void
|
||||
poly_scale(const Poly *p, Poly *newp, uint16_t x) {
|
||||
newp->length = p->length;
|
||||
for(uint16_t i = 0; i < p->length; i++){
|
||||
newp->at(i) = mul(p->at(i), x);
|
||||
}
|
||||
}
|
||||
|
||||
/* @brief Addition of two polynomials
|
||||
* @param &p - right operand polynomial
|
||||
* @param &q - left operand polynomial
|
||||
* @param &newp - destination polynomial */
|
||||
inline void
|
||||
poly_add(const Poly *p, const Poly *q, Poly *newp) {
|
||||
newp->length = poly_max(p->length, q->length);
|
||||
memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));
|
||||
|
||||
for(uint8_t i = 0; i < p->length; i++){
|
||||
newp->at(i + newp->length - p->length) = p->at(i);
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < q->length; i++){
|
||||
newp->at(i + newp->length - q->length) ^= q->at(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* @brief Multiplication of two polynomials
|
||||
* @param &p - right operand polynomial
|
||||
* @param &q - left operand polynomial
|
||||
* @param &newp - destination polynomial */
|
||||
inline void
|
||||
poly_mul(const Poly *p, const Poly *q, Poly *newp) {
|
||||
newp->length = p->length + q->length - 1;
|
||||
memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));
|
||||
/* Compute the polynomial multiplication (just like the outer product of two vectors,
|
||||
* we multiply each coefficients of p with all coefficients of q) */
|
||||
for(uint8_t j = 0; j < q->length; j++){
|
||||
for(uint8_t i = 0; i < p->length; i++){
|
||||
newp->at(i+j) ^= mul(p->at(i), q->at(j)); /* == r[i + j] = gf_add(r[i+j], gf_mul(p[i], q[j])) */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @brief Division of two polynomials
|
||||
* @param &p - right operand polynomial
|
||||
* @param &q - left operand polynomial
|
||||
* @param &newp - destination polynomial */
|
||||
inline void
|
||||
poly_div(const Poly *p, const Poly *q, Poly *newp) {
|
||||
if(p->ptr() != newp->ptr()) {
|
||||
memcpy(newp->ptr(), p->ptr(), p->length*sizeof(uint8_t));
|
||||
}
|
||||
|
||||
newp->length = p->length;
|
||||
|
||||
uint8_t coef;
|
||||
|
||||
for(int i = 0; i < (p->length-(q->length-1)); i++){
|
||||
coef = newp->at(i);
|
||||
if(coef != 0){
|
||||
for(uint8_t j = 1; j < q->length; j++){
|
||||
if(q->at(j) != 0)
|
||||
newp->at(i+j) ^= mul(q->at(j), coef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t sep = p->length-(q->length-1);
|
||||
memmove(newp->ptr(), newp->ptr()+sep, (newp->length-sep) * sizeof(uint8_t));
|
||||
newp->length = newp->length-sep;
|
||||
}
|
||||
|
||||
/* @brief Evaluation of polynomial in x
|
||||
* @param &p - polynomial to evaluate
|
||||
* @param x - evaluation point */
|
||||
inline int8_t
|
||||
poly_eval(const Poly *p, uint16_t x) {
|
||||
uint8_t y = p->at(0);
|
||||
for(uint8_t i = 1; i < p->length; i++){
|
||||
y = mul(y, x) ^ p->at(i);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
} /* end of gf namespace */
|
||||
|
||||
}
|
||||
#endif // GF_H
|
||||
|
||||
98
reed-solomon/poly.hpp
Normal file
98
reed-solomon/poly.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* Author: Mike Lubinets (aka mersinvald)
|
||||
* Date: 29.12.15
|
||||
*
|
||||
* See LICENSE */
|
||||
|
||||
#ifndef POLY_H
|
||||
#define POLY_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#if !defined DEBUG && !defined __CC_ARM
|
||||
#include <assert.h>
|
||||
#else
|
||||
#define assert(dummy)
|
||||
#endif
|
||||
|
||||
namespace RS {
|
||||
|
||||
struct Poly {
|
||||
Poly()
|
||||
: length(0), _memory(NULL) {}
|
||||
|
||||
Poly(uint8_t id, uint16_t offset, uint8_t size) \
|
||||
: length(0), _id(id), _size(size), _offset(offset), _memory(NULL) {}
|
||||
|
||||
/* @brief Append number at the end of polynomial
|
||||
* @param num - number to append
|
||||
* @return false if polynomial can't be stretched */
|
||||
inline bool Append(uint8_t num) {
|
||||
assert(length < _size);
|
||||
ptr()[length++] = num;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* @brief Polynomial initialization */
|
||||
inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) {
|
||||
this->_id = id;
|
||||
this->_offset = offset;
|
||||
this->_size = size;
|
||||
this->length = 0;
|
||||
this->_memory = memory_ptr;
|
||||
}
|
||||
|
||||
/* @brief Polynomial memory zeroing */
|
||||
inline void Reset() {
|
||||
memset((void*)ptr(), 0, this->_size);
|
||||
}
|
||||
|
||||
/* @brief Copy polynomial to memory
|
||||
* @param src - source byte-sequence
|
||||
* @param size - size of polynomial
|
||||
* @param offset - write offset */
|
||||
inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) {
|
||||
assert(src && len <= this->_size-offset);
|
||||
memcpy(ptr()+offset, src, len * sizeof(uint8_t));
|
||||
length = len + offset;
|
||||
}
|
||||
|
||||
#define poly_max(a, b) ((a > b) ? (a) : (b))
|
||||
|
||||
inline void Copy(const Poly* src) {
|
||||
length = poly_max(length, src->length);
|
||||
Set(src->ptr(), length);
|
||||
}
|
||||
|
||||
inline uint8_t& at(uint8_t i) const {
|
||||
assert(i < _size);
|
||||
return ptr()[i];
|
||||
}
|
||||
|
||||
inline uint8_t id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
inline uint8_t size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
// Returns pointer to memory of this polynomial
|
||||
inline uint8_t* ptr() const {
|
||||
assert(_memory && *_memory);
|
||||
return (*_memory) + _offset;
|
||||
}
|
||||
|
||||
uint8_t length;
|
||||
|
||||
protected:
|
||||
|
||||
uint8_t _id;
|
||||
uint8_t _size; // Size of reserved memory for this polynomial
|
||||
uint16_t _offset; // Offset in memory
|
||||
uint8_t** _memory; // Pointer to pointer to memory
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // POLY_H
|
||||
532
reed-solomon/rs.hpp
Normal file
532
reed-solomon/rs.hpp
Normal file
@@ -0,0 +1,532 @@
|
||||
/* Author: Mike Lubinets (aka mersinvald)
|
||||
* Date: 29.12.15
|
||||
*
|
||||
* See LICENSE */
|
||||
|
||||
#ifndef RS_HPP
|
||||
#define RS_HPP
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "poly.hpp"
|
||||
#include "gf.hpp"
|
||||
|
||||
#if !defined DEBUG && !defined __CC_ARM
|
||||
#include <assert.h>
|
||||
#else
|
||||
#define assert(dummy)
|
||||
#endif
|
||||
|
||||
namespace RS {
|
||||
|
||||
#define MSG_CNT 3 // message-length polynomials count
|
||||
#define POLY_CNT 14 // (ecc_length*2)-length polynomialc count
|
||||
|
||||
class ReedSolomon {
|
||||
public:
|
||||
const uint8_t msg_length;
|
||||
const uint8_t ecc_length;
|
||||
|
||||
uint8_t * generator_cache = nullptr;
|
||||
bool generator_cached = false;
|
||||
|
||||
ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p) :
|
||||
msg_length(msg_length_p), ecc_length(ecc_length_p) {
|
||||
generator_cache = new uint8_t[ecc_length + 1];
|
||||
|
||||
const uint8_t enc_len = msg_length + ecc_length;
|
||||
const uint8_t poly_len = ecc_length * 2;
|
||||
uint8_t** memptr = &memory;
|
||||
uint16_t offset = 0;
|
||||
|
||||
/* Initialize first six polys manually cause their amount depends on template parameters */
|
||||
|
||||
polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr);
|
||||
offset += enc_len;
|
||||
|
||||
polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr);
|
||||
offset += enc_len;
|
||||
|
||||
for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) {
|
||||
polynoms[i].Init(i, offset, poly_len, memptr);
|
||||
offset += poly_len;
|
||||
}
|
||||
|
||||
polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr);
|
||||
offset += enc_len;
|
||||
|
||||
for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) {
|
||||
polynoms[i].Init(i, offset, poly_len, memptr);
|
||||
offset += poly_len;
|
||||
}
|
||||
}
|
||||
|
||||
~ReedSolomon() {
|
||||
delete [] generator_cache;
|
||||
// Dummy destructor, gcc-generated one crashes programm
|
||||
memory = NULL;
|
||||
}
|
||||
|
||||
/* @brief Message block encoding
|
||||
* @param *src - input message buffer (msg_lenth size)
|
||||
* @param *dst - output buffer for ecc (ecc_length size at least) */
|
||||
void EncodeBlock(const void* src, void* dst) {
|
||||
assert(msg_length + ecc_length < 256);
|
||||
|
||||
/* Allocating memory on stack for polynomials storage */
|
||||
uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
|
||||
this->memory = stack_memory;
|
||||
|
||||
const uint8_t* src_ptr = (const uint8_t*) src;
|
||||
uint8_t* dst_ptr = (uint8_t*) dst;
|
||||
|
||||
Poly *msg_in = &polynoms[ID_MSG_IN];
|
||||
Poly *msg_out = &polynoms[ID_MSG_OUT];
|
||||
Poly *gen = &polynoms[ID_GENERATOR];
|
||||
|
||||
// Weird shit, but without reseting msg_in it simply doesn't work
|
||||
msg_in->Reset();
|
||||
msg_out->Reset();
|
||||
|
||||
// Using cached generator or generating new one
|
||||
if(generator_cached) {
|
||||
gen->Set(generator_cache, ecc_length + 1);
|
||||
} else {
|
||||
GeneratorPoly();
|
||||
memcpy(generator_cache, gen->ptr(), gen->length);
|
||||
generator_cached = true;
|
||||
}
|
||||
|
||||
// Copying input message to internal polynomial
|
||||
msg_in->Set(src_ptr, msg_length);
|
||||
msg_out->Set(src_ptr, msg_length);
|
||||
msg_out->length = msg_in->length + ecc_length;
|
||||
|
||||
// Here all the magic happens
|
||||
uint8_t coef = 0; // cache
|
||||
for(uint8_t i = 0; i < msg_length; i++){
|
||||
coef = msg_out->at(i);
|
||||
if(coef != 0){
|
||||
for(uint32_t j = 1; j < gen->length; j++){
|
||||
msg_out->at(i+j) ^= gf::mul(gen->at(j), coef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copying ECC to the output buffer
|
||||
memcpy(dst_ptr, msg_out->ptr()+msg_length, ecc_length * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
/* @brief Message encoding
|
||||
* @param *src - input message buffer (msg_lenth size)
|
||||
* @param *dst - output buffer (msg_length + ecc_length size at least) */
|
||||
void Encode(const void* src, void* dst) {
|
||||
uint8_t* dst_ptr = (uint8_t*) dst;
|
||||
|
||||
// Copying message to the output buffer
|
||||
memcpy(dst_ptr, src, msg_length * sizeof(uint8_t));
|
||||
|
||||
// Calling EncodeBlock to write ecc to out[ut buffer
|
||||
EncodeBlock(src, dst_ptr+msg_length);
|
||||
}
|
||||
|
||||
/* @brief Message block decoding
|
||||
* @param *src - encoded message buffer (msg_length size)
|
||||
* @param *ecc - ecc buffer (ecc_length size)
|
||||
* @param *msg_out - output buffer (msg_length size at least)
|
||||
* @param *erase_pos - known errors positions
|
||||
* @param erase_count - count of known errors
|
||||
* @return RESULT_SUCCESS if successfull, error code otherwise */
|
||||
int DecodeBlock(const void* src, const void* ecc, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {
|
||||
assert(msg_length + ecc_length < 256);
|
||||
|
||||
const uint8_t *src_ptr = (const uint8_t*) src;
|
||||
const uint8_t *ecc_ptr = (const uint8_t*) ecc;
|
||||
uint8_t *dst_ptr = (uint8_t*) dst;
|
||||
|
||||
const uint8_t src_len = msg_length + ecc_length;
|
||||
const uint8_t dst_len = msg_length;
|
||||
|
||||
bool ok;
|
||||
|
||||
/* Allocation memory on stack */
|
||||
uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
|
||||
this->memory = stack_memory;
|
||||
|
||||
Poly *msg_in = &polynoms[ID_MSG_IN];
|
||||
Poly *msg_out = &polynoms[ID_MSG_OUT];
|
||||
Poly *epos = &polynoms[ID_ERASURES];
|
||||
|
||||
// Copying message to polynomials memory
|
||||
msg_in->Set(src_ptr, msg_length);
|
||||
msg_in->Set(ecc_ptr, ecc_length, msg_length);
|
||||
msg_out->Copy(msg_in);
|
||||
|
||||
// Copying known errors to polynomial
|
||||
if(erase_pos == NULL) {
|
||||
epos->length = 0;
|
||||
} else {
|
||||
epos->Set(erase_pos, erase_count);
|
||||
for(uint8_t i = 0; i < epos->length; i++){
|
||||
msg_in->at(epos->at(i)) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Too many errors
|
||||
if(epos->length > ecc_length) return 1;
|
||||
|
||||
Poly *synd = &polynoms[ID_SYNDROMES];
|
||||
Poly *eloc = &polynoms[ID_ERRORS_LOC];
|
||||
Poly *reloc = &polynoms[ID_TPOLY1];
|
||||
Poly *err = &polynoms[ID_ERRORS];
|
||||
Poly *forney = &polynoms[ID_FORNEY];
|
||||
|
||||
// Calculating syndrome
|
||||
CalcSyndromes(msg_in);
|
||||
|
||||
// Checking for errors
|
||||
bool has_errors = false;
|
||||
for(uint8_t i = 0; i < synd->length; i++) {
|
||||
if(synd->at(i) != 0) {
|
||||
has_errors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Going to exit if no errors
|
||||
if(!has_errors) goto return_corrected_msg;
|
||||
|
||||
CalcForneySyndromes(synd, epos, src_len);
|
||||
FindErrorLocator(forney, NULL, epos->length);
|
||||
|
||||
// Reversing syndrome
|
||||
// TODO optimize through special Poly flag
|
||||
reloc->length = eloc->length;
|
||||
for(int8_t i = eloc->length-1, j = 0; i >= 0; i--, j++){
|
||||
reloc->at(j) = eloc->at(i);
|
||||
}
|
||||
|
||||
// Fing errors
|
||||
ok = FindErrors(reloc, src_len);
|
||||
if(!ok) return 1;
|
||||
|
||||
// Error happened while finding errors (so helpfull :D)
|
||||
if(err->length == 0) return 1;
|
||||
|
||||
/* Adding found errors with known */
|
||||
for(uint8_t i = 0; i < err->length; i++) {
|
||||
epos->Append(err->at(i));
|
||||
}
|
||||
|
||||
// Correcting errors
|
||||
CorrectErrata(synd, epos, msg_in);
|
||||
|
||||
return_corrected_msg:
|
||||
// Wrighting corrected message to output buffer
|
||||
msg_out->length = dst_len;
|
||||
memcpy(dst_ptr, msg_out->ptr(), msg_out->length * sizeof(uint8_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @brief Message block decoding
|
||||
* @param *src - encoded message buffer (msg_length + ecc_length size)
|
||||
* @param *msg_out - output buffer (msg_length size at least)
|
||||
* @param *erase_pos - known errors positions
|
||||
* @param erase_count - count of known errors
|
||||
* @return RESULT_SUCCESS if successfull, error code otherwise */
|
||||
int Decode(const void* src, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {
|
||||
const uint8_t *src_ptr = (const uint8_t*) src;
|
||||
const uint8_t *ecc_ptr = src_ptr + msg_length;
|
||||
|
||||
return DecodeBlock(src, ecc_ptr, dst, erase_pos, erase_count);
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
private:
|
||||
#endif
|
||||
|
||||
enum POLY_ID {
|
||||
ID_MSG_IN = 0,
|
||||
ID_MSG_OUT,
|
||||
ID_GENERATOR, // 3
|
||||
ID_TPOLY1, // T for Temporary
|
||||
ID_TPOLY2,
|
||||
|
||||
ID_MSG_E, // 5
|
||||
|
||||
ID_TPOLY3, // 6
|
||||
ID_TPOLY4,
|
||||
|
||||
ID_SYNDROMES,
|
||||
ID_FORNEY,
|
||||
|
||||
ID_ERASURES_LOC,
|
||||
ID_ERRORS_LOC,
|
||||
|
||||
ID_ERASURES,
|
||||
ID_ERRORS,
|
||||
|
||||
ID_COEF_POS,
|
||||
ID_ERR_EVAL
|
||||
};
|
||||
|
||||
// Pointer for polynomials memory on stack
|
||||
uint8_t* memory;
|
||||
Poly polynoms[MSG_CNT + POLY_CNT];
|
||||
|
||||
void GeneratorPoly() {
|
||||
Poly *gen = polynoms + ID_GENERATOR;
|
||||
gen->at(0) = 1;
|
||||
gen->length = 1;
|
||||
|
||||
Poly *mulp = polynoms + ID_TPOLY1;
|
||||
Poly *temp = polynoms + ID_TPOLY2;
|
||||
mulp->length = 2;
|
||||
|
||||
for(int8_t i = 0; i < ecc_length; i++){
|
||||
mulp->at(0) = 1;
|
||||
mulp->at(1) = gf::pow(2, i);
|
||||
|
||||
gf::poly_mul(gen, mulp, temp);
|
||||
|
||||
gen->Copy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void CalcSyndromes(const Poly *msg) {
|
||||
Poly *synd = &polynoms[ID_SYNDROMES];
|
||||
synd->length = ecc_length+1;
|
||||
synd->at(0) = 0;
|
||||
for(uint8_t i = 1; i < ecc_length+1; i++){
|
||||
synd->at(i) = gf::poly_eval(msg, gf::pow(2, i-1));
|
||||
}
|
||||
}
|
||||
|
||||
void FindErrataLocator(const Poly *epos) {
|
||||
Poly *errata_loc = &polynoms[ID_ERASURES_LOC];
|
||||
Poly *mulp = &polynoms[ID_TPOLY1];
|
||||
Poly *addp = &polynoms[ID_TPOLY2];
|
||||
Poly *apol = &polynoms[ID_TPOLY3];
|
||||
Poly *temp = &polynoms[ID_TPOLY4];
|
||||
|
||||
errata_loc->length = 1;
|
||||
errata_loc->at(0) = 1;
|
||||
|
||||
mulp->length = 1;
|
||||
addp->length = 2;
|
||||
|
||||
for(uint8_t i = 0; i < epos->length; i++){
|
||||
mulp->at(0) = 1;
|
||||
addp->at(0) = gf::pow(2, epos->at(i));
|
||||
addp->at(1) = 0;
|
||||
|
||||
gf::poly_add(mulp, addp, apol);
|
||||
gf::poly_mul(errata_loc, apol, temp);
|
||||
|
||||
errata_loc->Copy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void FindErrorEvaluator(const Poly *synd, const Poly *errata_loc, Poly *dst, uint8_t ecclen) {
|
||||
Poly *mulp = &polynoms[ID_TPOLY1];
|
||||
gf::poly_mul(synd, errata_loc, mulp);
|
||||
|
||||
Poly *divisor = &polynoms[ID_TPOLY2];
|
||||
divisor->length = ecclen+2;
|
||||
|
||||
divisor->Reset();
|
||||
divisor->at(0) = 1;
|
||||
|
||||
gf::poly_div(mulp, divisor, dst);
|
||||
}
|
||||
|
||||
void CorrectErrata(const Poly *synd, const Poly *err_pos, const Poly *msg_in) {
|
||||
Poly *c_pos = &polynoms[ID_COEF_POS];
|
||||
Poly *corrected = &polynoms[ID_MSG_OUT];
|
||||
c_pos->length = err_pos->length;
|
||||
|
||||
for(uint8_t i = 0; i < err_pos->length; i++)
|
||||
c_pos->at(i) = msg_in->length - 1 - err_pos->at(i);
|
||||
|
||||
/* uses t_poly 1, 2, 3, 4 */
|
||||
FindErrataLocator(c_pos);
|
||||
Poly *errata_loc = &polynoms[ID_ERASURES_LOC];
|
||||
|
||||
/* reversing syndromes */
|
||||
Poly *rsynd = &polynoms[ID_TPOLY3];
|
||||
rsynd->length = synd->length;
|
||||
|
||||
for(int8_t i = synd->length-1, j = 0; i >= 0; i--, j++) {
|
||||
rsynd->at(j) = synd->at(i);
|
||||
}
|
||||
|
||||
/* getting reversed error evaluator polynomial */
|
||||
Poly *re_eval = &polynoms[ID_TPOLY4];
|
||||
|
||||
/* uses T_POLY 1, 2 */
|
||||
FindErrorEvaluator(rsynd, errata_loc, re_eval, errata_loc->length-1);
|
||||
|
||||
/* reversing it back */
|
||||
Poly *e_eval = &polynoms[ID_ERR_EVAL];
|
||||
e_eval->length = re_eval->length;
|
||||
for(int8_t i = re_eval->length-1, j = 0; i >= 0; i--, j++) {
|
||||
e_eval->at(j) = re_eval->at(i);
|
||||
}
|
||||
|
||||
Poly *X = &polynoms[ID_TPOLY1]; /* this will store errors positions */
|
||||
X->length = 0;
|
||||
|
||||
int16_t l;
|
||||
for(uint8_t i = 0; i < c_pos->length; i++){
|
||||
l = 255 - c_pos->at(i);
|
||||
X->Append(gf::pow(2, -l));
|
||||
}
|
||||
|
||||
/* Magnitude polynomial
|
||||
Shit just got real */
|
||||
Poly *E = &polynoms[ID_MSG_E];
|
||||
E->Reset();
|
||||
E->length = msg_in->length;
|
||||
|
||||
uint8_t Xi_inv;
|
||||
|
||||
Poly *err_loc_prime_temp = &polynoms[ID_TPOLY2];
|
||||
|
||||
uint8_t err_loc_prime;
|
||||
uint8_t y;
|
||||
|
||||
for(uint8_t i = 0; i < X->length; i++){
|
||||
Xi_inv = gf::inverse(X->at(i));
|
||||
|
||||
err_loc_prime_temp->length = 0;
|
||||
for(uint8_t j = 0; j < X->length; j++){
|
||||
if(j != i){
|
||||
err_loc_prime_temp->Append(gf::sub(1, gf::mul(Xi_inv, X->at(j))));
|
||||
}
|
||||
}
|
||||
|
||||
err_loc_prime = 1;
|
||||
for(uint8_t j = 0; j < err_loc_prime_temp->length; j++){
|
||||
err_loc_prime = gf::mul(err_loc_prime, err_loc_prime_temp->at(j));
|
||||
}
|
||||
|
||||
y = gf::poly_eval(re_eval, Xi_inv);
|
||||
y = gf::mul(gf::pow(X->at(i), 1), y);
|
||||
|
||||
E->at(err_pos->at(i)) = gf::div(y, err_loc_prime);
|
||||
}
|
||||
|
||||
gf::poly_add(msg_in, E, corrected);
|
||||
}
|
||||
|
||||
bool FindErrorLocator(const Poly *synd, Poly *erase_loc = NULL, size_t erase_count = 0) {
|
||||
Poly *error_loc = &polynoms[ID_ERRORS_LOC];
|
||||
Poly *err_loc = &polynoms[ID_TPOLY1];
|
||||
Poly *old_loc = &polynoms[ID_TPOLY2];
|
||||
Poly *temp = &polynoms[ID_TPOLY3];
|
||||
Poly *temp2 = &polynoms[ID_TPOLY4];
|
||||
|
||||
if(erase_loc != NULL) {
|
||||
err_loc->Copy(erase_loc);
|
||||
old_loc->Copy(erase_loc);
|
||||
} else {
|
||||
err_loc->length = 1;
|
||||
old_loc->length = 1;
|
||||
err_loc->at(0) = 1;
|
||||
old_loc->at(0) = 1;
|
||||
}
|
||||
|
||||
uint8_t synd_shift = 0;
|
||||
if(synd->length > ecc_length) {
|
||||
synd_shift = synd->length - ecc_length;
|
||||
}
|
||||
|
||||
uint8_t K = 0;
|
||||
uint8_t delta = 0;
|
||||
uint8_t index;
|
||||
|
||||
for(uint8_t i = 0; i < ecc_length - erase_count; i++){
|
||||
if(erase_loc != NULL)
|
||||
K = erase_count + i + synd_shift;
|
||||
else
|
||||
K = i + synd_shift;
|
||||
|
||||
delta = synd->at(K);
|
||||
for(uint8_t j = 1; j < err_loc->length; j++) {
|
||||
index = err_loc->length - j - 1;
|
||||
delta ^= gf::mul(err_loc->at(index), synd->at(K-j));
|
||||
}
|
||||
|
||||
old_loc->Append(0);
|
||||
|
||||
if(delta != 0) {
|
||||
if(old_loc->length > err_loc->length) {
|
||||
gf::poly_scale(old_loc, temp, delta);
|
||||
gf::poly_scale(err_loc, old_loc, gf::inverse(delta));
|
||||
err_loc->Copy(temp);
|
||||
}
|
||||
gf::poly_scale(old_loc, temp, delta);
|
||||
gf::poly_add(err_loc, temp, temp2);
|
||||
err_loc->Copy(temp2);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t shift = 0;
|
||||
while(err_loc->length && err_loc->at(shift) == 0) shift++;
|
||||
|
||||
uint32_t errs = err_loc->length - shift - 1;
|
||||
if(((errs - erase_count) * 2 + erase_count) > ecc_length){
|
||||
return false; /* Error count is greater then we can fix! */
|
||||
}
|
||||
|
||||
memcpy(error_loc->ptr(), err_loc->ptr() + shift, (err_loc->length - shift) * sizeof(uint8_t));
|
||||
error_loc->length = (err_loc->length - shift);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FindErrors(const Poly *error_loc, size_t msg_in_size) {
|
||||
Poly *err = &polynoms[ID_ERRORS];
|
||||
|
||||
uint8_t errs = error_loc->length - 1;
|
||||
err->length = 0;
|
||||
|
||||
for(uint8_t i = 0; i < msg_in_size; i++) {
|
||||
if(gf::poly_eval(error_loc, gf::pow(2, i)) == 0) {
|
||||
err->Append(msg_in_size - 1 - i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sanity check:
|
||||
* the number of err/errata positions found
|
||||
* should be exactly the same as the length of the errata locator polynomial */
|
||||
if(err->length != errs)
|
||||
/* couldn't find error locations */
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalcForneySyndromes(const Poly *synd, const Poly *erasures_pos, size_t msg_in_size) {
|
||||
Poly *erase_pos_reversed = &polynoms[ID_TPOLY1];
|
||||
Poly *forney_synd = &polynoms[ID_FORNEY];
|
||||
erase_pos_reversed->length = 0;
|
||||
|
||||
for(uint8_t i = 0; i < erasures_pos->length; i++){
|
||||
erase_pos_reversed->Append(msg_in_size - 1 - erasures_pos->at(i));
|
||||
}
|
||||
|
||||
forney_synd->Reset();
|
||||
forney_synd->Set(synd->ptr()+1, synd->length-1);
|
||||
|
||||
uint8_t x;
|
||||
for(uint8_t i = 0; i < erasures_pos->length; i++) {
|
||||
x = gf::pow(2, erase_pos_reversed->at(i));
|
||||
for(int8_t j = 0; j < forney_synd->length - 1; j++){
|
||||
forney_synd->at(j) = gf::mul(forney_synd->at(j), x) ^ forney_synd->at(j+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RS_HPP
|
||||
|
||||
22
sdp-transform/LICENSE
Normal file
22
sdp-transform/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2013 Eirik Albrigtsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
347
sdp-transform/grammar.js
Normal file
347
sdp-transform/grammar.js
Normal file
@@ -0,0 +1,347 @@
|
||||
var grammar = {
|
||||
v: [{
|
||||
name: 'version',
|
||||
reg: /^(\d*)$/
|
||||
}],
|
||||
o: [{ //o=- 20518 0 IN IP4 203.0.113.1
|
||||
// NB: sessionId will be a String in most cases because it is huge
|
||||
name: 'origin',
|
||||
reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
|
||||
names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
|
||||
format: '%s %s %d %s IP%d %s'
|
||||
}],
|
||||
// default parsing of these only (though some of these feel outdated)
|
||||
s: [{ name: 'name' }],
|
||||
i: [{ name: 'description' }],
|
||||
u: [{ name: 'uri' }],
|
||||
e: [{ name: 'email' }],
|
||||
p: [{ name: 'phone' }],
|
||||
z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
|
||||
r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
|
||||
//k: [{}], // outdated thing ignored
|
||||
t: [{ //t=0 0
|
||||
name: 'timing',
|
||||
reg: /^(\d*) (\d*)/,
|
||||
names: ['start', 'stop'],
|
||||
format: '%d %d'
|
||||
}],
|
||||
c: [{ //c=IN IP4 10.47.197.26
|
||||
name: 'connection',
|
||||
reg: /^IN IP(\d) (\S*)/,
|
||||
names: ['version', 'ip'],
|
||||
format: 'IN IP%d %s'
|
||||
}],
|
||||
b: [{ //b=AS:4000
|
||||
push: 'bandwidth',
|
||||
reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
|
||||
names: ['type', 'limit'],
|
||||
format: '%s:%s'
|
||||
}],
|
||||
m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
|
||||
// NB: special - pushes to session
|
||||
// TODO: rtp/fmtp should be filtered by the payloads found here?
|
||||
reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
|
||||
names: ['type', 'port', 'protocol', 'payloads'],
|
||||
format: '%s %d %s %s'
|
||||
}],
|
||||
a: [
|
||||
{ //a=rtpmap:110 opus/48000/2
|
||||
push: 'rtp',
|
||||
reg: /^rtpmap:(\d*) ([\w\-\.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
|
||||
names: ['payload', 'codec', 'rate', 'encoding'],
|
||||
format: function (o) {
|
||||
return (o.encoding) ?
|
||||
'rtpmap:%d %s/%s/%s':
|
||||
o.rate ?
|
||||
'rtpmap:%d %s/%s':
|
||||
'rtpmap:%d %s';
|
||||
}
|
||||
},
|
||||
{ //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
|
||||
//a=fmtp:111 minptime=10; useinbandfec=1
|
||||
push: 'fmtp',
|
||||
reg: /^fmtp:(\d*) ([\S| ]*)/,
|
||||
names: ['payload', 'config'],
|
||||
format: 'fmtp:%d %s'
|
||||
},
|
||||
{ //a=control:streamid=0
|
||||
name: 'control',
|
||||
reg: /^control:(.*)/,
|
||||
format: 'control:%s'
|
||||
},
|
||||
{ //a=rtcp:65179 IN IP4 193.84.77.194
|
||||
name: 'rtcp',
|
||||
reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
|
||||
names: ['port', 'netType', 'ipVer', 'address'],
|
||||
format: function (o) {
|
||||
return (o.address != null) ?
|
||||
'rtcp:%d %s IP%d %s':
|
||||
'rtcp:%d';
|
||||
}
|
||||
},
|
||||
{ //a=rtcp-fb:98 trr-int 100
|
||||
push: 'rtcpFbTrrInt',
|
||||
reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
|
||||
names: ['payload', 'value'],
|
||||
format: 'rtcp-fb:%d trr-int %d'
|
||||
},
|
||||
{ //a=rtcp-fb:98 nack rpsi
|
||||
push: 'rtcpFb',
|
||||
reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
|
||||
names: ['payload', 'type', 'subtype'],
|
||||
format: function (o) {
|
||||
return (o.subtype != null) ?
|
||||
'rtcp-fb:%s %s %s':
|
||||
'rtcp-fb:%s %s';
|
||||
}
|
||||
},
|
||||
{ //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
|
||||
//a=extmap:1/recvonly URI-gps-string
|
||||
push: 'ext',
|
||||
reg: /^extmap:(\d+)(?:\/(\w+))? (\S*)(?: (\S*))?/,
|
||||
names: ['value', 'direction', 'uri', 'config'],
|
||||
format: function (o) {
|
||||
return 'extmap:%d' + (o.direction ? '/%s' : '%v') + ' %s' + (o.config ? ' %s' : '');
|
||||
}
|
||||
},
|
||||
{ //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
|
||||
push: 'crypto',
|
||||
reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
|
||||
names: ['id', 'suite', 'config', 'sessionConfig'],
|
||||
format: function (o) {
|
||||
return (o.sessionConfig != null) ?
|
||||
'crypto:%d %s %s %s':
|
||||
'crypto:%d %s %s';
|
||||
}
|
||||
},
|
||||
{ //a=setup:actpass
|
||||
name: 'setup',
|
||||
reg: /^setup:(\w*)/,
|
||||
format: 'setup:%s'
|
||||
},
|
||||
{ //a=mid:1
|
||||
name: 'mid',
|
||||
reg: /^mid:([^\s]*)/,
|
||||
format: 'mid:%s'
|
||||
},
|
||||
{ //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
|
||||
name: 'msid',
|
||||
reg: /^msid:(.*)/,
|
||||
format: 'msid:%s'
|
||||
},
|
||||
{ //a=ptime:20
|
||||
name: 'ptime',
|
||||
reg: /^ptime:(\d*)/,
|
||||
format: 'ptime:%d'
|
||||
},
|
||||
{ //a=maxptime:60
|
||||
name: 'maxptime',
|
||||
reg: /^maxptime:(\d*)/,
|
||||
format: 'maxptime:%d'
|
||||
},
|
||||
{ //a=sendrecv
|
||||
name: 'direction',
|
||||
reg: /^(sendrecv|recvonly|sendonly|inactive)/
|
||||
},
|
||||
{ //a=ice-lite
|
||||
name: 'icelite',
|
||||
reg: /^(ice-lite)/
|
||||
},
|
||||
{ //a=ice-ufrag:F7gI
|
||||
name: 'iceUfrag',
|
||||
reg: /^ice-ufrag:(\S*)/,
|
||||
format: 'ice-ufrag:%s'
|
||||
},
|
||||
{ //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
|
||||
name: 'icePwd',
|
||||
reg: /^ice-pwd:(\S*)/,
|
||||
format: 'ice-pwd:%s'
|
||||
},
|
||||
{ //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
|
||||
name: 'fingerprint',
|
||||
reg: /^fingerprint:(\S*) (\S*)/,
|
||||
names: ['type', 'hash'],
|
||||
format: 'fingerprint:%s %s'
|
||||
},
|
||||
{ //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
|
||||
//a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10
|
||||
//a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10
|
||||
//a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10
|
||||
//a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10
|
||||
push:'candidates',
|
||||
reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?(?: network-id (\d*))?(?: network-cost (\d*))?/,
|
||||
names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'],
|
||||
format: function (o) {
|
||||
var str = 'candidate:%s %d %s %d %s %d typ %s';
|
||||
|
||||
str += (o.raddr != null) ? ' raddr %s rport %d' : '%v%v';
|
||||
|
||||
// NB: candidate has three optional chunks, so %void middles one if it's missing
|
||||
str += (o.tcptype != null) ? ' tcptype %s' : '%v';
|
||||
|
||||
if (o.generation != null) {
|
||||
str += ' generation %d';
|
||||
}
|
||||
|
||||
str += (o['network-id'] != null) ? ' network-id %d' : '%v';
|
||||
str += (o['network-cost'] != null) ? ' network-cost %d' : '%v';
|
||||
return str;
|
||||
}
|
||||
},
|
||||
{ //a=end-of-candidates (keep after the candidates line for readability)
|
||||
name: 'endOfCandidates',
|
||||
reg: /^(end-of-candidates)/
|
||||
},
|
||||
{ //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
|
||||
name: 'remoteCandidates',
|
||||
reg: /^remote-candidates:(.*)/,
|
||||
format: 'remote-candidates:%s'
|
||||
},
|
||||
{ //a=ice-options:google-ice
|
||||
name: 'iceOptions',
|
||||
reg: /^ice-options:(\S*)/,
|
||||
format: 'ice-options:%s'
|
||||
},
|
||||
{ //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
|
||||
push: 'ssrcs',
|
||||
reg: /^ssrc:(\d*) ([\w_-]*)(?::(.*))?/,
|
||||
names: ['id', 'attribute', 'value'],
|
||||
format: function (o) {
|
||||
var str = 'ssrc:%d';
|
||||
if (o.attribute != null) {
|
||||
str += ' %s';
|
||||
if (o.value != null) {
|
||||
str += ':%s';
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
},
|
||||
{ //a=ssrc-group:FEC 1 2
|
||||
//a=ssrc-group:FEC-FR 3004364195 1080772241
|
||||
push: 'ssrcGroups',
|
||||
// token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E
|
||||
reg: /^ssrc-group:([\x21\x23\x24\x25\x26\x27\x2A\x2B\x2D\x2E\w]*) (.*)/,
|
||||
names: ['semantics', 'ssrcs'],
|
||||
format: 'ssrc-group:%s %s'
|
||||
},
|
||||
{ //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
|
||||
name: 'msidSemantic',
|
||||
reg: /^msid-semantic:\s?(\w*) (\S*)/,
|
||||
names: ['semantic', 'token'],
|
||||
format: 'msid-semantic: %s %s' // space after ':' is not accidental
|
||||
},
|
||||
{ //a=group:BUNDLE audio video
|
||||
push: 'groups',
|
||||
reg: /^group:(\w*) (.*)/,
|
||||
names: ['type', 'mids'],
|
||||
format: 'group:%s %s'
|
||||
},
|
||||
{ //a=rtcp-mux
|
||||
name: 'rtcpMux',
|
||||
reg: /^(rtcp-mux)/
|
||||
},
|
||||
{ //a=rtcp-rsize
|
||||
name: 'rtcpRsize',
|
||||
reg: /^(rtcp-rsize)/
|
||||
},
|
||||
{ //a=sctpmap:5000 webrtc-datachannel 1024
|
||||
name: 'sctpmap',
|
||||
reg: /^sctpmap:([\w_\/]*) (\S*)(?: (\S*))?/,
|
||||
names: ['sctpmapNumber', 'app', 'maxMessageSize'],
|
||||
format: function (o) {
|
||||
return (o.maxMessageSize != null) ?
|
||||
'sctpmap:%s %s %s' :
|
||||
'sctpmap:%s %s';
|
||||
}
|
||||
},
|
||||
{ //a=x-google-flag:conference
|
||||
name: 'xGoogleFlag',
|
||||
reg: /^x-google-flag:([^\s]*)/,
|
||||
format: 'x-google-flag:%s'
|
||||
},
|
||||
{ //a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0
|
||||
push: 'rids',
|
||||
reg: /^rid:([\d\w]+) (\w+)(?: ([\S| ]*))?/,
|
||||
names: ['id', 'direction', 'params'],
|
||||
format: function (o) {
|
||||
return (o.params) ? 'rid:%s %s %s' : 'rid:%s %s';
|
||||
}
|
||||
},
|
||||
{ //a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]
|
||||
//a=imageattr:* send [x=800,y=640] recv *
|
||||
//a=imageattr:100 recv [x=320,y=240]
|
||||
push: 'imageattrs',
|
||||
reg: new RegExp(
|
||||
//a=imageattr:97
|
||||
'^imageattr:(\\d+|\\*)' +
|
||||
//send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320]
|
||||
'[\\s\\t]+(send|recv)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*)' +
|
||||
//recv [x=330,y=250]
|
||||
'(?:[\\s\\t]+(recv|send)[\\s\\t]+(\\*|\\[\\S+\\](?:[\\s\\t]+\\[\\S+\\])*))?'
|
||||
),
|
||||
names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],
|
||||
format: function (o) {
|
||||
return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : '');
|
||||
}
|
||||
},
|
||||
{ //a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8
|
||||
//a=simulcast:recv 1;4,5 send 6;7
|
||||
name: 'simulcast',
|
||||
reg: new RegExp(
|
||||
//a=simulcast:
|
||||
'^simulcast:' +
|
||||
//send 1,2,3;~4,~5
|
||||
'(send|recv) ([a-zA-Z0-9\\-_~;,]+)' +
|
||||
//space + recv 6;~7,~8
|
||||
'(?:\\s?(send|recv) ([a-zA-Z0-9\\-_~;,]+))?' +
|
||||
//end
|
||||
'$'
|
||||
),
|
||||
names: ['dir1', 'list1', 'dir2', 'list2'],
|
||||
format: function (o) {
|
||||
return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : '');
|
||||
}
|
||||
},
|
||||
{ //Old simulcast draft 03 (implemented by Firefox)
|
||||
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03
|
||||
//a=simulcast: recv pt=97;98 send pt=97
|
||||
//a=simulcast: send rid=5;6;7 paused=6,7
|
||||
name: 'simulcast_03',
|
||||
reg: /^simulcast:[\s\t]+([\S+\s\t]+)$/,
|
||||
names: ['value'],
|
||||
format: 'simulcast: %s'
|
||||
},
|
||||
{
|
||||
//a=framerate:25
|
||||
//a=framerate:29.97
|
||||
name: 'framerate',
|
||||
reg: /^framerate:(\d+(?:$|\.\d+))/,
|
||||
format: 'framerate:%s'
|
||||
},
|
||||
{ // RFC4570
|
||||
//a=source-filter: incl IN IP4 239.5.2.31 10.1.15.5
|
||||
name: 'sourceFilter',
|
||||
reg: /^source-filter: *(excl|incl) (\S*) (IP4|IP6|\*) (\S*) (.*)/,
|
||||
names: ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'],
|
||||
format: 'source-filter: %s %s %s %s %s'
|
||||
},
|
||||
{ // any a= that we don't understand is kepts verbatim on media.invalid
|
||||
push: 'invalid',
|
||||
names: ['value']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// set sensible defaults to avoid polluting the grammar with boring details
|
||||
Object.keys(grammar).forEach(function (key) {
|
||||
var objs = grammar[key];
|
||||
objs.forEach(function (obj) {
|
||||
if (!obj.reg) {
|
||||
obj.reg = /(.*)/;
|
||||
}
|
||||
if (!obj.format) {
|
||||
obj.format = '%s';
|
||||
}
|
||||
});
|
||||
});
|
||||
11
sdp-transform/index.js
Normal file
11
sdp-transform/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var parser = require('./parser');
|
||||
var writer = require('./writer');
|
||||
|
||||
exports.write = writer;
|
||||
exports.parse = parser.parse;
|
||||
exports.parseFmtpConfig = parser.parseFmtpConfig;
|
||||
exports.parseParams = parser.parseParams;
|
||||
exports.parsePayloads = parser.parsePayloads;
|
||||
exports.parseRemoteCandidates = parser.parseRemoteCandidates;
|
||||
exports.parseImageAttributes = parser.parseImageAttributes;
|
||||
exports.parseSimulcastStreamList = parser.parseSimulcastStreamList;
|
||||
118
sdp-transform/parser.js
Normal file
118
sdp-transform/parser.js
Normal file
@@ -0,0 +1,118 @@
|
||||
var toIntIfInt = function (v) {
|
||||
return String(Number(v)) === v ? Number(v) : v;
|
||||
};
|
||||
|
||||
var attachProperties = function (match, location, names, rawName) {
|
||||
if (rawName && !names) {
|
||||
location[rawName] = toIntIfInt(match[1]);
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < names.length; i += 1) {
|
||||
if (match[i+1] != null) {
|
||||
location[names[i]] = toIntIfInt(match[i+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var parseReg = function (obj, location, content) {
|
||||
var needsBlank = obj.name && obj.names;
|
||||
if (obj.push && !location[obj.push]) {
|
||||
location[obj.push] = [];
|
||||
}
|
||||
else if (needsBlank && !location[obj.name]) {
|
||||
location[obj.name] = {};
|
||||
}
|
||||
var keyLocation = obj.push ?
|
||||
{} : // blank object that will be pushed
|
||||
needsBlank ? location[obj.name] : location; // otherwise, named location or root
|
||||
|
||||
attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
|
||||
|
||||
if (obj.push) {
|
||||
location[obj.push].push(keyLocation);
|
||||
}
|
||||
};
|
||||
|
||||
var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
|
||||
|
||||
function parseSDP(sdp) {
|
||||
var session = {}
|
||||
, media = []
|
||||
, location = session; // points at where properties go under (one of the above)
|
||||
|
||||
// parse lines we understand
|
||||
sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
|
||||
var type = l[0];
|
||||
var content = l.slice(2);
|
||||
if (type === 'm') {
|
||||
media.push({rtp: [], fmtp: []});
|
||||
location = media[media.length-1]; // point at latest media line
|
||||
}
|
||||
|
||||
for (var j = 0; j < (grammar[type] || []).length; j += 1) {
|
||||
var obj = grammar[type][j];
|
||||
if (obj.reg.test(content)) {
|
||||
return parseReg(obj, location, content);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
session.media = media; // link it up
|
||||
return session;
|
||||
};
|
||||
|
||||
var paramReducer = function (acc, expr) {
|
||||
var s = expr.split(/=(.+)/, 2);
|
||||
if (s.length === 2) {
|
||||
acc[s[0]] = toIntIfInt(s[1]);
|
||||
}
|
||||
return acc;
|
||||
};
|
||||
|
||||
function parseParamsSDP(str) {
|
||||
return str.split(/\;\s?/).reduce(paramReducer, {});
|
||||
};
|
||||
|
||||
function parsePayloadsSDP(str) {
|
||||
return str.split(' ').map(Number);
|
||||
};
|
||||
|
||||
function parseRemoteCandidatesSDP(str) {
|
||||
var candidates = [];
|
||||
var parts = str.split(' ').map(toIntIfInt);
|
||||
for (var i = 0; i < parts.length; i += 3) {
|
||||
candidates.push({
|
||||
component: parts[i],
|
||||
ip: parts[i + 1],
|
||||
port: parts[i + 2]
|
||||
});
|
||||
}
|
||||
return candidates;
|
||||
};
|
||||
|
||||
function parseImageAttributesSDP(str) {
|
||||
return str.split(' ').map(function (item) {
|
||||
return item.substring(1, item.length-1).split(',').reduce(paramReducer, {});
|
||||
});
|
||||
};
|
||||
|
||||
function parseSimulcastStreamListSDP(str) {
|
||||
return str.split(';').map(function (stream) {
|
||||
return stream.split(',').map(function (format) {
|
||||
var scid, paused = false;
|
||||
|
||||
if (format[0] !== '~') {
|
||||
scid = toIntIfInt(format);
|
||||
} else {
|
||||
scid = toIntIfInt(format.substring(1, format.length));
|
||||
paused = true;
|
||||
}
|
||||
|
||||
return {
|
||||
scid: scid,
|
||||
paused: paused
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
112
sdp-transform/writer.js
Normal file
112
sdp-transform/writer.js
Normal file
@@ -0,0 +1,112 @@
|
||||
// customized util.format - discards excess arguments and can void middle ones
|
||||
var formatRegExp = /%[sdv%]/g;
|
||||
var format = function (formatStr) {
|
||||
var i = 1;
|
||||
var args = arguments;
|
||||
var len = args.length;
|
||||
return formatStr.replace(formatRegExp, function (x) {
|
||||
if (i >= len) {
|
||||
return x; // missing argument
|
||||
}
|
||||
var arg = args[i];
|
||||
i += 1;
|
||||
switch (x) {
|
||||
case '%%':
|
||||
return '%';
|
||||
case '%s':
|
||||
return String(arg);
|
||||
case '%d':
|
||||
return Number(arg);
|
||||
case '%v':
|
||||
return '';
|
||||
}
|
||||
});
|
||||
// NB: we discard excess arguments - they are typically undefined from makeLine
|
||||
};
|
||||
|
||||
var makeLine = function (type, obj, location) {
|
||||
var str = obj.format instanceof Function ?
|
||||
(obj.format(obj.push ? location : location[obj.name])) :
|
||||
obj.format;
|
||||
|
||||
var args = [type + '=' + str];
|
||||
if (obj.names) {
|
||||
for (var i = 0; i < obj.names.length; i += 1) {
|
||||
var n = obj.names[i];
|
||||
if (obj.name) {
|
||||
args.push(location[obj.name][n]);
|
||||
}
|
||||
else { // for mLine and push attributes
|
||||
args.push(location[obj.names[i]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
args.push(location[obj.name]);
|
||||
}
|
||||
return format.apply(null, args);
|
||||
};
|
||||
|
||||
// RFC specified order
|
||||
// TODO: extend this with all the rest
|
||||
var defaultOuterOrder = [
|
||||
'v', 'o', 's', 'i',
|
||||
'u', 'e', 'p', 'c',
|
||||
'b', 't', 'r', 'z', 'a'
|
||||
];
|
||||
var defaultInnerOrder = ['i', 'c', 'b', 'a'];
|
||||
|
||||
|
||||
function writeSDP(session, opts) {
|
||||
opts = opts || {};
|
||||
// ensure certain properties exist
|
||||
if (session.version == null) {
|
||||
session.version = 0; // 'v=0' must be there (only defined version atm)
|
||||
}
|
||||
if (session.name == null) {
|
||||
session.name = ' '; // 's= ' must be there if no meaningful name set
|
||||
}
|
||||
session.media.forEach(function (mLine) {
|
||||
if (mLine.payloads == null) {
|
||||
mLine.payloads = '';
|
||||
}
|
||||
});
|
||||
|
||||
var outerOrder = opts.outerOrder || defaultOuterOrder;
|
||||
var innerOrder = opts.innerOrder || defaultInnerOrder;
|
||||
var sdp = [];
|
||||
|
||||
// loop through outerOrder for matching properties on session
|
||||
outerOrder.forEach(function (type) {
|
||||
grammar[type].forEach(function (obj) {
|
||||
if (obj.name in session && session[obj.name] != null) {
|
||||
sdp.push(makeLine(type, obj, session));
|
||||
}
|
||||
else if (obj.push in session && session[obj.push] != null) {
|
||||
session[obj.push].forEach(function (el) {
|
||||
sdp.push(makeLine(type, obj, el));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// then for each media line, follow the innerOrder
|
||||
session.media.forEach(function (mLine) {
|
||||
sdp.push(makeLine('m', grammar.m[0], mLine));
|
||||
|
||||
innerOrder.forEach(function (type) {
|
||||
grammar[type].forEach(function (obj) {
|
||||
if (obj.name in mLine && mLine[obj.name] != null) {
|
||||
sdp.push(makeLine(type, obj, mLine));
|
||||
}
|
||||
else if (obj.push in mLine && mLine[obj.push] != null) {
|
||||
mLine[obj.push].forEach(function (el) {
|
||||
sdp.push(makeLine(type, obj, el));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return sdp.join('\r\n') + '\r\n';
|
||||
};
|
||||
Reference in New Issue
Block a user