Initial commit

This commit is contained in:
Georgi Gerganov
2018-04-29 13:29:22 +03:00
parent b5bc223e73
commit 23fc4ca051
11 changed files with 3109 additions and 0 deletions

821
main.cpp Normal file
View 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(&timestamp)));
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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';
};