diff --git a/.gitignore b/.gitignore index 82c570fa..5ecbb89c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ build cmake-build-* DartConfiguration.tcl .DS_Store +.run/ diff --git a/docs/modules/dev/pages/code-structure.adoc b/docs/modules/dev/pages/code-structure.adoc index 82132e3c..958c2b17 100644 --- a/docs/modules/dev/pages/code-structure.adoc +++ b/docs/modules/dev/pages/code-structure.adoc @@ -29,7 +29,8 @@ Here we define core Moonlight functions in order to create the foundation for a It currently hosts the followings: * HTTP/S protocol interface: https://github.com/games-on-whales/wolf/blob/HEAD/src/moonlight/moonlight/protocol.hpp[protocol.hpp] -* Reed Solomon FEC implementation https://github.com/games-on-whales/wolf/blob/HEAD/src/moonlight/reedsolomon/rs.h[rs.h] +* Reed Solomon FEC header https://github.com/games-on-whales/wolf/blob/HEAD/src/moonlight/moonlight/fec.hpp[fec.hpp] +** Based on top of https://github.com/sleepybishop/nanors[sleepybishop/nanors] * RTSP message parser https://github.com/games-on-whales/wolf/blob/HEAD/src/moonlight/rtsp/parser.hpp[parser.hpp] ** Built using https://github.com/yhirose/cpp-peglib[yhirose/cpp-peglib] by writing a formal PEG (Parsing Expression Grammars) definition for all messages diff --git a/src/moonlight/CMakeLists.txt b/src/moonlight/CMakeLists.txt index 6fe622bc..695f26da 100644 --- a/src/moonlight/CMakeLists.txt +++ b/src/moonlight/CMakeLists.txt @@ -5,7 +5,6 @@ project(moonlight) # Optionally glob, but only for CMake 3.12 or later: file(GLOB HEADER_LIST CONFIGURE_DEPENDS moonlight/*.hpp - reedsolomon/rs.h rtsp/parser.hpp) @@ -16,7 +15,6 @@ add_library(wolf::moonlight ALIAS moonlight) target_sources(moonlight PRIVATE moonlight.cpp - reedsolomon/rs.c PUBLIC ${HEADER_LIST}) @@ -36,6 +34,29 @@ FetchContent_MakeAvailable(immer) target_link_libraries(moonlight PUBLIC immer) unset(FPHSA_NAME_MISMATCHED) +# FEC implementation +FetchContent_Declare( + nanors + GIT_REPOSITORY https://github.com/sleepybishop/nanors.git + GIT_TAG 395e5ada44dd8d5974eaf6bb6b17f23406e3ca72) +FetchContent_GetProperties(nanors) +if (NOT nanors_POPULATED) + FetchContent_Populate(nanors) + + add_library(nanors STATIC ${nanors_SOURCE_DIR}/rs.c) + add_library(nanors::nanors ALIAS nanors) + target_include_directories(nanors PUBLIC ${nanors_SOURCE_DIR} ${nanors_SOURCE_DIR}/deps/obl/) + + target_sources(nanors + PRIVATE ${nanors_SOURCE_DIR}/rs.c + PUBLIC ${nanors_SOURCE_DIR}/rs.h) + + set_source_files_properties(${nanors_SOURCE_DIR}/rs.c + PROPERTIES COMPILE_FLAGS "-include deps/obl/autoshim.h -ftree-vectorize") + + target_link_libraries(moonlight PUBLIC nanors::nanors) +endif () + # Additional algorithms for dealing with containers FetchContent_Declare( range @@ -65,9 +86,7 @@ set_target_properties(moonlight PROPERTIES PUBLIC_HEADER .) set_target_properties(moonlight PROPERTIES OUTPUT_NAME "moonlight") # moonlight-common-c dependencies -target_include_directories(moonlight PUBLIC - ./reedsolomon - ./rtsp) +target_include_directories(moonlight PUBLIC ./rtsp) find_package(Boost REQUIRED) diff --git a/src/moonlight/moonlight/fec.hpp b/src/moonlight/moonlight/fec.hpp new file mode 100644 index 00000000..ec15a67c --- /dev/null +++ b/src/moonlight/moonlight/fec.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include + +extern "C" { +#include +} + +/** + * FEC (Forward Error Correction) + * + * Moonlight uses Reed Solomon ( https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction ) + * to encode the payload so that it can be checked on the receiving end for transmission errors + * (and possibly fix them). + * + * This is just a small wrapper on top of the excellent https://github.com/sleepybishop/nanors implementation + */ +namespace moonlight::fec { + +/** + * Maximum number of data shards that can be encoded in one go + */ +#define DATA_SHARDS_MAX 255 + +/** + * One time initialization required by the library + */ +inline void init() { + reed_solomon_init(); +} + +/** + * A smart pointer to the reed_solomon data structure, it will release the memory when going out of scope + */ +using rs_ptr = std::unique_ptr; + +/** + * Creates and allocates the required Reed Solomon data structure. + * + * @param data_shards Number of data shards to be encoded + * @param parity_shards Number of parity shards to be created + * + * @return A smart pointer, it will release the memory when going out of scope + */ +inline rs_ptr create(int data_shards, int parity_shards) { + auto rs = reed_solomon_new(data_shards, parity_shards); + return {rs, ::reed_solomon_release}; +} + +/** + * Encodes the input data shards using Reed Solomon. + * It will read \p nr_shards * \p block_size and then append all the newly created parity shards + * to \p shards. + * + * @warning \p shards MUST be of size: shards[data_shards + parity_shards][block_size] + * @warning The content of \p shards after \p nr_shards will be overwritten + * + * @param rs the reed solomon data structure created with `create()` + * @param shards[in, out] the memory location where data and parity blocks will live + * @param nr_shards the total number of shards ( data_shards + parity_shards ) + * @param block_size the size of each block that needs to be encoded + * + * @return zero on success or an error code if failing. + */ +inline int encode(reed_solomon *rs, uint8_t **shards, int nr_shards, int block_size) { + return reed_solomon_encode(rs, shards, nr_shards, block_size); +} + +/** + * Decodes back the input data shards using Reed Solomon. + * It will recreate missing blocks based on the \p marks property + * + * @warning \p shards MUST be of size: shards[data_shards + parity_shards][block_size] + * @warning The content of \p shards where blocks are missing will be overwritten + * + * @param rs the reed solomon data structure created with `create()` + * @param shards[in, out] the memory location where data and parity blocks will live + * @param marks an array of size \p nr_shards, if `marks[i] == 1` that blocks will be reconstructed + * @param nr_shards the total number of shards ( data_shards + parity_shards ) + * @param block_size the size of each block that needs to be encoded + * + * @return zero on success or an error code if failing + */ +inline int decode(reed_solomon *rs, uint8_t **shards, uint8_t *marks, int nr_shards, int block_size) { + return reed_solomon_decode(rs, shards, marks, nr_shards, block_size); +} + +} // namespace moonlight::fec \ No newline at end of file diff --git a/src/moonlight/reedsolomon/rs.c b/src/moonlight/reedsolomon/rs.c deleted file mode 100644 index f4df424d..00000000 --- a/src/moonlight/reedsolomon/rs.c +++ /dev/null @@ -1,639 +0,0 @@ -/* -* fec.c -- forward error correction based on Vandermonde matrices -* -* (C) 1997-98 Luigi Rizzo (luigi@iet.unipi.it) -* (C) 2001 Alain Knaff (alain@knaff.lu) -* (C) 2017 Iwan Timmer (irtimmer@gmail.com) -* -* Portions derived from code by Phil Karn (karn@ka9q.ampr.org), -* Robert Morelos-Zaragoza (robert@spectra.eng.hawaii.edu) and Hari -* Thirumoorthy (harit@spectra.eng.hawaii.edu), Aug 1995 -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* -* 1. Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* 2. Redistributions in binary form must reproduce the above -* copyright notice, this list of conditions and the following -* disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND -* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS -* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -* OF SUCH DAMAGE. -*/ - -#include -#include -#include - -#include -#include "rs.h" - -#ifdef _MSC_VER -#define NEED_ALLOCA -#define alloca(x) _alloca(x) -#endif - -typedef unsigned char gf; - -#define GF_BITS 8 -#define GF_PP "101110001" -#define GF_SIZE ((1 << GF_BITS) - 1) - -#define SWAP(a,b,t) {t tmp; tmp=a; a=b; b=tmp;} - -/* -* USE_GF_MULC, GF_MULC0(c) and GF_ADDMULC(x) can be used when multiplying -* many numbers by the same constant. In this case the first -* call sets the constant, and others perform the multiplications. -* A value related to the multiplication is held in a local variable -* declared with USE_GF_MULC . See usage in addmul1(). -*/ -#define USE_GF_MULC register gf * __gf_mulc_ -#define GF_MULC0(c) __gf_mulc_ = &gf_mul_table[(c)<<8] -#define GF_ADDMULC(dst, x) dst ^= __gf_mulc_[x] -#define GF_MULC(dst, x) dst = __gf_mulc_[x] - -#define gf_mul(x,y) gf_mul_table[(x<<8)+y] - -/* -* To speed up computations, we have tables for logarithm, exponent -* multiplication and inverse of a number. -*/ -static gf gf_exp[2*GF_SIZE]; -static int gf_log[GF_SIZE + 1]; -static gf inverse[GF_SIZE+1]; -#ifdef _MSC_VER -static gf __declspec(align (256)) gf_mul_table[(GF_SIZE + 1)*(GF_SIZE + 1)]; -#else -static gf gf_mul_table[(GF_SIZE + 1)*(GF_SIZE + 1)] __attribute__((aligned (256))); -#endif - -/* -* modnn(x) computes x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, -* without a slow divide. -*/ -static inline gf modnn(int x) { - while (x >= GF_SIZE) { - x -= GF_SIZE; - x = (x >> GF_BITS) + (x & GF_SIZE); - } - return x; -} - -static void addmul(gf *dst1, gf *src1, gf c, int sz) { - USE_GF_MULC; - if (c != 0) { - register gf *dst = dst1, *src = src1; - gf *lim = &dst[sz]; - - GF_MULC0(c); - for (; dst < lim; dst++, src++) - GF_ADDMULC(*dst, *src); - } -} - -static void mul(gf *dst1, gf *src1, gf c, int sz) { - USE_GF_MULC; - if (c != 0) { - register gf *dst = dst1, *src = src1; - gf *lim = &dst[sz]; - GF_MULC0(c); - for (; dst < lim; dst++, src++) - GF_MULC(*dst , *src); - } else - memset(dst1, 0, c); -} - -/* y = a.dot(b) */ -static gf* multiply1(gf *a, int ar, int ac, gf *b, int br, int bc) { - gf *new_m, tg; - int r, c, i, ptr = 0; - - assert(ac == br); - new_m = (gf*) calloc(1, ar*bc); - if (NULL != new_m) { - - /* this multiply is slow */ - for (r = 0; r < ar; r++) { - for (c = 0; c < bc; c++) { - tg = 0; - for (i = 0; i < ac; i++) - tg ^= gf_mul(a[r*ac+i], b[i*bc+c]); - - new_m[ptr++] = tg; - } - } - } - - return new_m; -} - -static void init_mul_table(void) { - int i, j; - for (i=0; i< GF_SIZE+1; i++) - for (j=0; j< GF_SIZE+1; j++) - gf_mul_table[(i<<8)+j] = gf_exp[modnn(gf_log[i] + gf_log[j]) ] ; - - for (j=0; j< GF_SIZE+1; j++) - gf_mul_table[j] = gf_mul_table[j<<8] = 0; -} - -/* -* initialize the data structures used for computations in GF. -*/ -static void generate_gf(void) { - int i; - gf mask; - - mask = 1; - gf_exp[GF_BITS] = 0; - /* - * first, generate the (polynomial representation of) powers of \alpha, - * which are stored in gf_exp[i] = \alpha ** i . - * At the same time build gf_log[gf_exp[i]] = i . - * The first GF_BITS powers are simply bits shifted to the left. - */ - for (i = 0; i < GF_BITS; i++, mask <<= 1) { - gf_exp[i] = mask; - gf_log[gf_exp[i]] = i; - /* - * If GF_PP[i] == 1 then \alpha ** i occurs in poly-repr - * gf_exp[GF_BITS] = \alpha ** GF_BITS - */ - if (GF_PP[i] == '1') - gf_exp[GF_BITS] ^= mask; - } - /* - * now gf_exp[GF_BITS] = \alpha ** GF_BITS is complete, so can als - * compute its inverse. - */ - gf_log[gf_exp[GF_BITS]] = GF_BITS; - /* - * Poly-repr of \alpha ** (i+1) is given by poly-repr of - * \alpha ** i shifted left one-bit and accounting for any - * \alpha ** GF_BITS term that may occur when poly-repr of - * \alpha ** i is shifted. - */ - mask = 1 << (GF_BITS - 1) ; - for (i = GF_BITS + 1; i < GF_SIZE; i++) { - if (gf_exp[i - 1] >= mask) - gf_exp[i] = gf_exp[GF_BITS] ^ ((gf_exp[i - 1] ^ mask) << 1); - else - gf_exp[i] = gf_exp[i - 1] << 1; - - gf_log[gf_exp[i]] = i; - } - /* - * log(0) is not defined, so use a special value - */ - gf_log[0] = GF_SIZE; - /* set the extended gf_exp values for fast multiply */ - for (i = 0; i < GF_SIZE; i++) - gf_exp[i + GF_SIZE] = gf_exp[i]; - - /* - * again special cases. 0 has no inverse. This used to - * be initialized to GF_SIZE, but it should make no difference - * since noone is supposed to read from here. - */ - inverse[0] = 0; - inverse[1] = 1; - for (i=2; i<=GF_SIZE; i++) - inverse[i] = gf_exp[GF_SIZE-gf_log[i]]; -} - -/* -* invert_mat() takes a matrix and produces its inverse -* k is the size of the matrix. -* (Gauss-Jordan, adapted from Numerical Recipes in C) -* Return non-zero if singular. -*/ -static int invert_mat(gf *src, int k) { - gf c, *p; - int irow, icol, row, col, i, ix; - - int error = 1; -#ifdef NEED_ALLOCA - int *indxc = alloca(k*sizeof(int)); - int *indxr = alloca(k*sizeof(int)); - int *ipiv = alloca(k*sizeof(int)); - gf *id_row = alloca(k*sizeof(gf)); -#else - int indxc[k]; - int indxr[k]; - int ipiv[k]; - gf id_row[k]; -#endif - - memset(id_row, 0, k*sizeof(gf)); - /* - * ipiv marks elements already used as pivots. - */ - for (i = 0; i < k; i++) - ipiv[i] = 0; - - for (col = 0; col < k; col++) { - gf *pivot_row; - /* - * Zeroing column 'col', look for a non-zero element. - * First try on the diagonal, if it fails, look elsewhere. - */ - irow = icol = -1; - if (ipiv[col] != 1 && src[col*k + col] != 0) { - irow = col; - icol = col; - goto found_piv; - } - for (row = 0; row < k; row++) { - if (ipiv[row] != 1) { - for (ix = 0; ix < k; ix++) { - if (ipiv[ix] == 0) { - if (src[row*k + ix] != 0) { - irow = row; - icol = ix; - goto found_piv; - } - } else if (ipiv[ix] > 1) { - fprintf(stderr, "singular matrix\n"); - goto fail; - } - } - } - } - if (icol == -1) { - fprintf(stderr, "XXX pivot not found!\n"); - goto fail ; - } - - found_piv: - ++(ipiv[icol]); - /* - * swap rows irow and icol, so afterwards the diagonal - * element will be correct. Rarely done, not worth - * optimizing. - */ - if (irow != icol) { - for (ix = 0; ix < k; ix++) { - SWAP(src[irow*k + ix], src[icol*k + ix], gf); - } - } - indxr[col] = irow; - indxc[col] = icol; - pivot_row = &src[icol*k]; - c = pivot_row[icol]; - if (c == 0) { - fprintf(stderr, "singular matrix 2\n"); - goto fail; - } else if (c != 1 ) { - /* - * this is done often , but optimizing is not so - * fruitful, at least in the obvious ways (unrolling) - */ - c = inverse[ c ]; - pivot_row[icol] = 1; - for (ix = 0; ix < k; ix++) - pivot_row[ix] = gf_mul(c, pivot_row[ix]); - } - /* - * from all rows, remove multiples of the selected row - * to zero the relevant entry (in fact, the entry is not zero - * because we know it must be zero). - * (Here, if we know that the pivot_row is the identity, - * we can optimize the addmul). - */ - id_row[icol] = 1; - if (memcmp(pivot_row, id_row, k*sizeof(gf)) != 0) { - for (p = src, ix = 0 ; ix < k ; ix++, p += k) { - if (ix != icol) { - c = p[icol]; - p[icol] = 0; - addmul(p, pivot_row, c, k); - } - } - } - id_row[icol] = 0; - } - for (col = k-1 ; col >= 0 ; col-- ) { - if (indxr[col] <0 || indxr[col] >= k) - fprintf(stderr, "AARGH, indxr[col] %d\n", indxr[col]); - else if (indxc[col] <0 || indxc[col] >= k) - fprintf(stderr, "AARGH, indxc[col] %d\n", indxc[col]); - else - if (indxr[col] != indxc[col] ) { - for (row = 0 ; row < k ; row++ ) - SWAP( src[row*k + indxr[col]], src[row*k + indxc[col]], gf); - } - } - error = 0; - -fail: - return error ; -} - -/* -* Not check for input params -* */ -static gf* sub_matrix(gf* matrix, int rmin, int cmin, int rmax, int cmax, int nrows, int ncols) { - int i, j, ptr = 0; - gf* new_m = (gf*) malloc((rmax-rmin) * (cmax-cmin)); - if (NULL != new_m) { - for (i = rmin; i < rmax; i++) { - for (j = cmin; j < cmax; j++) { - new_m[ptr++] = matrix[i*ncols + j]; - } - } - } - - return new_m; -} - -/* copy from golang rs version */ -static inline int code_some_shards(gf* matrixRows, gf** inputs, gf** outputs, int dataShards, int outputCount, int byteCount) { - gf* in; - int iRow, c; - for (c = 0; c < dataShards; c++) { - in = inputs[c]; - for (iRow = 0; iRow < outputCount; iRow++) { - if (0 == c) - mul(outputs[iRow], in, matrixRows[iRow*dataShards+c], byteCount); - else - addmul(outputs[iRow], in, matrixRows[iRow*dataShards+c], byteCount); - } - } - - return 0; -} - -void reed_solomon_init(void) { - generate_gf(); - init_mul_table(); -} - -reed_solomon* reed_solomon_new(int data_shards, int parity_shards) { - gf* vm = NULL; - gf* top = NULL; - int err = 0; - reed_solomon* rs = NULL; - - do { - rs = malloc(sizeof(reed_solomon)); - if (NULL == rs) - return NULL; - - rs->data_shards = data_shards; - rs->parity_shards = parity_shards; - rs->shards = (data_shards + parity_shards); - rs->m = NULL; - rs->parity = NULL; - - if (rs->shards > DATA_SHARDS_MAX || data_shards <= 0 || parity_shards <= 0) { - err = 1; - break; - } - - vm = (gf*)malloc(data_shards * rs->shards); - - if (NULL == vm) { - err = 2; - break; - } - - int ptr = 0; - for (int row = 0; row < rs->shards; row++) { - for (int col = 0; col < data_shards; col++) - vm[ptr++] = row == col ? 1 : 0; - } - - top = sub_matrix(vm, 0, 0, data_shards, data_shards, rs->shards, data_shards); - if (NULL == top) { - err = 3; - break; - } - - err = invert_mat(top, data_shards); - assert(0 == err); - - rs->m = multiply1(vm, rs->shards, data_shards, top, data_shards, data_shards); - if (NULL == rs->m) { - err = 4; - break; - } - - for (int j = 0; j < parity_shards; j++) { - for (int i = 0; i < data_shards; i++) - rs->m[(data_shards + j)*data_shards + i] = inverse[(parity_shards + i) ^ j]; - } - - rs->parity = sub_matrix(rs->m, data_shards, 0, rs->shards, data_shards, rs->shards, data_shards); - if (NULL == rs->parity) { - err = 5; - break; - } - - free(vm); - free(top); - vm = NULL; - top = NULL; - return rs; - - } while(0); - - fprintf(stderr, "err=%d\n", err); - if (NULL != vm) - free(vm); - - if (NULL != top) - free(top); - - if (NULL != rs) { - if (NULL != rs->m) - free(rs->m); - - if (NULL != rs->parity) - free(rs->parity); - - free(rs); - } - - return NULL; -} - -void reed_solomon_release(reed_solomon* rs) { - if (NULL != rs) { - if (NULL != rs->m) - free(rs->m); - - if (NULL != rs->parity) - free(rs->parity); - - free(rs); - } -} - -/** -* decode one shard -* input: -* rs -* original data_blocks[rs->data_shards][block_size] -* dec_fec_blocks[nr_fec_blocks][block_size] -* fec_block_nos: fec pos number in original fec_blocks -* erased_blocks: erased blocks in original data_blocks -* nr_fec_blocks: the number of erased blocks -* */ -static int reed_solomon_decode(reed_solomon* rs, unsigned char **data_blocks, int block_size, unsigned char **dec_fec_blocks, unsigned int *fec_block_nos, unsigned int *erased_blocks, int nr_fec_blocks) { - /* use stack instead of malloc, define a small number of DATA_SHARDS_MAX to save memory */ - gf dataDecodeMatrix[DATA_SHARDS_MAX*DATA_SHARDS_MAX]; - unsigned char* subShards[DATA_SHARDS_MAX]; - unsigned char* outputs[DATA_SHARDS_MAX]; - gf* m = rs->m; - int i, j, c, swap, subMatrixRow, dataShards; - - /* the erased_blocks should always sorted - * if sorted, nr_fec_blocks times to check it - * if not, sort it here - * */ - for (i = 0; i < nr_fec_blocks; i++) { - swap = 0; - for (j = i+1; j < nr_fec_blocks; j++) { - if (erased_blocks[i] > erased_blocks[j]) { - /* the prefix is bigger than the following, swap */ - c = erased_blocks[i]; - erased_blocks[i] = erased_blocks[j]; - erased_blocks[j] = c; - - swap = 1; - } - } - if (!swap) - break; - } - - j = 0; - subMatrixRow = 0; - dataShards = rs->data_shards; - for (i = 0; i < dataShards; i++) { - if (j < nr_fec_blocks && i == (int)erased_blocks[j]) - j++; - else { - /* this row is ok */ - for (c = 0; c < dataShards; c++) - dataDecodeMatrix[subMatrixRow*dataShards + c] = m[i*dataShards + c]; - - subShards[subMatrixRow] = data_blocks[i]; - subMatrixRow++; - } - } - - for (i = 0; i < nr_fec_blocks && subMatrixRow < dataShards; i++) { - subShards[subMatrixRow] = dec_fec_blocks[i]; - j = dataShards + fec_block_nos[i]; - for (c = 0; c < dataShards; c++) - dataDecodeMatrix[subMatrixRow*dataShards + c] = m[j*dataShards + c]; - - subMatrixRow++; - } - - if (subMatrixRow < dataShards) - return -1; - - invert_mat(dataDecodeMatrix, dataShards); - - for (i = 0; i < nr_fec_blocks; i++) { - j = erased_blocks[i]; - outputs[i] = data_blocks[j]; - memmove(dataDecodeMatrix+i*dataShards, dataDecodeMatrix+j*dataShards, dataShards); - } - - return code_some_shards(dataDecodeMatrix, subShards, outputs, dataShards, nr_fec_blocks, block_size); -} - -/** -* encode a big size of buffer -* input: -* rs -* nr_shards: assert(0 == nr_shards % rs->shards) -* shards[nr_shards][block_size] -* */ -int reed_solomon_encode(reed_solomon* rs, unsigned char** shards, int nr_shards, int block_size) { - unsigned char** data_blocks; - unsigned char** fec_blocks; - int i, ds = rs->data_shards, ps = rs->parity_shards, ss = rs->shards; - i = nr_shards / ss; - data_blocks = shards; - fec_blocks = &shards[(i*ds)]; - - for (i = 0; i < nr_shards; i += ss) { - code_some_shards(rs->parity, data_blocks, fec_blocks, rs->data_shards, rs->parity_shards, block_size); - data_blocks += ds; - fec_blocks += ps; - } - return 0; -} - -/** -* reconstruct a big size of buffer -* input: -* rs -* nr_shards: assert(0 == nr_shards % rs->data_shards) -* shards[nr_shards][block_size] -* marks[nr_shards] marks as errors -* */ -int reed_solomon_reconstruct(reed_solomon* rs, unsigned char** shards, unsigned char* marks, int nr_shards, int block_size) { - unsigned char *dec_fec_blocks[DATA_SHARDS_MAX]; - unsigned int fec_block_nos[DATA_SHARDS_MAX]; - unsigned int erased_blocks[DATA_SHARDS_MAX]; - unsigned char* fec_marks; - unsigned char **data_blocks, **fec_blocks; - int i, j, dn, pn, n; - int ds = rs->data_shards; - int ps = rs->parity_shards; - int err = 0; - - data_blocks = shards; - n = nr_shards / rs->shards; - fec_marks = marks + n*ds; //after all data, is't fec marks - fec_blocks = shards + n*ds; - - for (j = 0; j < n; j++) { - dn = 0; - for (i = 0; i < ds; i++) { - if (marks[i]) - erased_blocks[dn++] = i; - } - if (dn > 0) { - pn = 0; - for (i = 0; i < ps && pn < dn; i++) { - if (!fec_marks[i]) { - //got valid fec row - fec_block_nos[pn] = i; - dec_fec_blocks[pn] = fec_blocks[i]; - pn++; - } - } - - if (dn == pn) { - reed_solomon_decode(rs, data_blocks, block_size, dec_fec_blocks, fec_block_nos, erased_blocks, dn); - } else - err = -1; - } - data_blocks += ds; - marks += ds; - fec_blocks += ps; - fec_marks += ps; - } - - return err; -} \ No newline at end of file diff --git a/src/moonlight/reedsolomon/rs.h b/src/moonlight/reedsolomon/rs.h deleted file mode 100644 index 15fae290..00000000 --- a/src/moonlight/reedsolomon/rs.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef __RS_H_ -#define __RS_H_ - -/* use small value to save memory */ -#define DATA_SHARDS_MAX 255 - -typedef struct _reed_solomon { - int data_shards; - int parity_shards; - int shards; - unsigned char* m; - unsigned char* parity; -} reed_solomon; - -/** - * MUST initial one time - * */ -void reed_solomon_init(void); - -reed_solomon* reed_solomon_new(int data_shards, int parity_shards); -void reed_solomon_release(reed_solomon* rs); - -/** - * encode a big size of buffer - * input: - * rs - * nr_shards: assert(0 == nr_shards % rs->data_shards) - * shards[nr_shards][block_size] - * */ -int reed_solomon_encode(reed_solomon* rs, unsigned char** shards, int nr_shards, int block_size); - -/** - * reconstruct a big size of buffer - * input: - * rs - * nr_shards: assert(0 == nr_shards % rs->data_shards) - * shards[nr_shards][block_size] - * marks[nr_shards] marks as errors - * */ -int reed_solomon_reconstruct(reed_solomon* rs, unsigned char** shards, unsigned char* marks, int nr_shards, int block_size); -#endif - diff --git a/src/streaming/streaming/gst-plugin/audio.hpp b/src/streaming/streaming/gst-plugin/audio.hpp index 0ac06931..c471c23c 100644 --- a/src/streaming/streaming/gst-plugin/audio.hpp +++ b/src/streaming/streaming/gst-plugin/audio.hpp @@ -1,16 +1,19 @@ #pragma once +#include #include #include namespace audio { +constexpr auto RTP_HEADER_SIZE = sizeof(state::AudioRTPHeaders); +constexpr auto FEC_HEADER_SIZE = sizeof(state::AudioFECPacket); + /** * Creates an RTP header and returns a GstBuffer to it */ static GstBuffer *create_rtp_header(const gst_rtp_moonlight_pay_audio &rtpmoonlightpay) { - constexpr auto rtp_header_size = sizeof(state::AudioRTPHeaders); - GstBuffer *buf = gst_buffer_new_and_fill(rtp_header_size, 0x00); + GstBuffer *buf = gst_buffer_new_and_fill(RTP_HEADER_SIZE, 0x00); /* get WRITE access to the memory */ GstMapInfo info; @@ -33,8 +36,7 @@ static GstBuffer *create_rtp_header(const gst_rtp_moonlight_pay_audio &rtpmoonli } static GstBuffer *create_rtp_fec_header(const gst_rtp_moonlight_pay_audio &rtpmoonlightpay, int fec_packet_idx) { - constexpr auto rtp_header_size = sizeof(state::AudioFECPacket); - GstBuffer *buf = gst_buffer_new_and_fill(rtp_header_size, 0x00); + GstBuffer *buf = gst_buffer_new_and_fill(FEC_HEADER_SIZE, 0x00); /* get WRITE access to the memory */ GstMapInfo info; @@ -100,25 +102,27 @@ static GstBufferList *split_into_rtp(gst_rtp_moonlight_pay_audio *rtpmoonlightpa // Time to generate FEC based on the previous payloads if (time_to_fec) { - auto encoded_block_size = (int)gst_buffer_get_size(rtp_audio_buf); - reed_solomon_encode(rtpmoonlightpay->rs, - rtpmoonlightpay->packets_buffer.data(), - AUDIO_TOTAL_SHARDS, - encoded_block_size); + /* Here the assumption is that all audio blocks will have the exact same size */ + auto rtp_block_size = (int)gst_buffer_get_size(rtp_audio_buf); + auto payload_size = rtp_block_size - RTP_HEADER_SIZE; + if (moonlight::fec::encode(rtpmoonlightpay->rs.get(), + rtpmoonlightpay->packets_buffer, + AUDIO_TOTAL_SHARDS, + rtp_block_size) != 0) { + logs::log(logs::warning, "Error during audio FEC encoding"); + } for (auto fec_packet_idx = 0; fec_packet_idx < AUDIO_FEC_SHARDS; fec_packet_idx++) { - auto fec_packet_header = create_rtp_fec_header(*rtpmoonlightpay, fec_packet_idx); + auto fec_packet = create_rtp_fec_header(*rtpmoonlightpay, fec_packet_idx); - GstBuffer *fec_payload_buf = gst_buffer_new_allocate(nullptr, encoded_block_size, nullptr); + GstBuffer *fec_payload_buf = gst_buffer_new_allocate(nullptr, payload_size, nullptr); gst_buffer_fill(fec_payload_buf, - sizeof(state::AudioRTPHeaders), - rtpmoonlightpay->packets_buffer[AUDIO_DATA_SHARDS + fec_packet_idx], - encoded_block_size - sizeof(state::AudioRTPHeaders)); - - auto fec_buf = gst_buffer_append(fec_packet_header, fec_payload_buf); - gst_copy_timestamps(inbuf, fec_buf); + 0, + rtpmoonlightpay->packets_buffer[AUDIO_DATA_SHARDS + fec_packet_idx] + RTP_HEADER_SIZE, + payload_size); - gst_buffer_list_add(rtp_packets, fec_buf); + fec_packet = gst_buffer_append(fec_packet, fec_payload_buf); + gst_buffer_list_add(rtp_packets, fec_packet); } } rtpmoonlightpay->cur_seq_number++; diff --git a/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.cpp b/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.cpp index aa075e16..1cdf5377 100644 --- a/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.cpp +++ b/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.cpp @@ -16,10 +16,6 @@ #include "config.h" #endif -extern "C" { -#include -} - #include #include #include @@ -146,16 +142,14 @@ static void gst_rtp_moonlight_pay_audio_init(gst_rtp_moonlight_pay_audio *rtpmoo rtpmoonlightpay_audio->encrypt = true; rtpmoonlightpay_audio->packet_duration = 5; - rtpmoonlightpay_audio->packets_buffer = {}; + rtpmoonlightpay_audio->packets_buffer = new unsigned char *[AUDIO_TOTAL_SHARDS]; for (int i = 0; i < AUDIO_TOTAL_SHARDS; i++) { - auto new_array = std::array{0}; - rtpmoonlightpay_audio->packets_buffer[i] = new_array.data(); + rtpmoonlightpay_audio->packets_buffer[i] = new unsigned char[AUDIO_MAX_BLOCK_SIZE]; } - auto rs = reed_solomon_new(AUDIO_DATA_SHARDS, AUDIO_FEC_SHARDS); - memcpy(&rs->m[16], AUDIO_FEC_PARITY, sizeof(AUDIO_FEC_PARITY)); - memcpy(rs->parity, AUDIO_FEC_PARITY, sizeof(AUDIO_FEC_PARITY)); - rtpmoonlightpay_audio->rs = rs; + auto rs = moonlight::fec::create(AUDIO_DATA_SHARDS, AUDIO_FEC_SHARDS); + memcpy(rs->p, AUDIO_FEC_PARITY, sizeof(AUDIO_FEC_PARITY)); + rtpmoonlightpay_audio->rs = std::move(rs); } void gst_rtp_moonlight_pay_audio_set_property(GObject *object, @@ -214,8 +208,6 @@ void gst_rtp_moonlight_pay_audio_dispose(GObject *object) { GST_DEBUG_OBJECT(rtpmoonlightpay_audio, "dispose"); - reed_solomon_release(rtpmoonlightpay_audio->rs); - G_OBJECT_CLASS(gst_rtp_moonlight_pay_audio_parent_class)->dispose(object); } diff --git a/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.hpp b/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.hpp index 1d4283a9..2707a983 100644 --- a/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.hpp +++ b/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_audio.hpp @@ -2,6 +2,7 @@ #include #include +#include #include constexpr int AUDIO_DATA_SHARDS = 4; @@ -40,8 +41,8 @@ struct _gst_rtp_moonlight_pay_audio { int packet_duration; - std::array packets_buffer; - _reed_solomon *rs; + unsigned char **packets_buffer; + moonlight::fec::rs_ptr rs; }; struct _gst_rtp_moonlight_pay_audioClass { diff --git a/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_video.cpp b/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_video.cpp index d0937402..3031ac38 100644 --- a/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_video.cpp +++ b/src/streaming/streaming/gst-plugin/gstrtpmoonlightpay_video.cpp @@ -16,10 +16,6 @@ #include "config.h" #endif -extern "C" { -#include -} - #include #include #include diff --git a/src/streaming/streaming/gst-plugin/utils.hpp b/src/streaming/streaming/gst-plugin/utils.hpp index bbc7d9ee..1bd694c9 100644 --- a/src/streaming/streaming/gst-plugin/utils.hpp +++ b/src/streaming/streaming/gst-plugin/utils.hpp @@ -1,9 +1,5 @@ #pragma once -extern "C" { -#include -} - #include #include #include @@ -11,6 +7,7 @@ extern "C" { #include #include #include +#include static void gst_buffer_copy_into(GstBuffer *buf, unsigned char *destination) { auto size = gst_buffer_get_size(buf); diff --git a/src/streaming/streaming/gst-plugin/video.hpp b/src/streaming/streaming/gst-plugin/video.hpp index b47ae677..bb3f02f1 100644 --- a/src/streaming/streaming/gst-plugin/video.hpp +++ b/src/streaming/streaming/gst-plugin/video.hpp @@ -170,13 +170,14 @@ static void generate_fec_packets(const gst_rtp_moonlight_pay_video &rtpmoonlight gst_buffer_map(rtp_payload, &info, GST_MAP_WRITE); // Reed Solomon encode the full stream of bytes - auto rs = reed_solomon_new(blocks.data_shards, blocks.parity_shards); + auto rs = moonlight::fec::create(blocks.data_shards, blocks.parity_shards); unsigned char *ptr[nr_shards]; for (int shard_idx = 0; shard_idx < nr_shards; shard_idx++) { ptr[shard_idx] = info.data + (shard_idx * blocks.block_size); } - reed_solomon_encode(rs, ptr, nr_shards, blocks.block_size); - reed_solomon_release(rs); + if(moonlight::fec::encode(rs.get(), ptr, nr_shards, blocks.block_size) != 0){ + logs::log(logs::warning, "Error during video FEC encoding"); + } // update FEC info of the already created RTP packets for (int shard_idx = 0; shard_idx < blocks.data_shards; shard_idx++) { diff --git a/src/streaming/streaming/streaming.cpp b/src/streaming/streaming/streaming.cpp index 957cd322..7ad2a658 100644 --- a/src/streaming/streaming/streaming.cpp +++ b/src/streaming/streaming/streaming.cpp @@ -1,7 +1,3 @@ -extern "C" { -#include -} - #include #include #include @@ -34,7 +30,7 @@ void init() { GstPlugin *audio_plugin = gst_plugin_load_by_name("rtpmoonlightpay_audio"); gst_element_register(audio_plugin, "rtpmoonlightpay_audio", GST_RANK_PRIMARY, gst_TYPE_rtp_moonlight_pay_audio); - reed_solomon_init(); + moonlight::fec::init(); } static gboolean msg_handler(GstBus *bus, GstMessage *message, gpointer data) { diff --git a/src/wolf/state/data-structures.hpp b/src/wolf/state/data-structures.hpp index ad3d5d4d..985f0010 100644 --- a/src/wolf/state/data-structures.hpp +++ b/src/wolf/state/data-structures.hpp @@ -72,7 +72,7 @@ constexpr std::string_view DEFAULT_SINK = "rtpmoonlightpay_video name=moonlight_pay payload_size={payload_size} fec_percentage={fec_percentage} " "min_required_fec_packets={min_required_fec_packets}" " ! " - "udpsink host={client_ip} port={client_port}"; + "udpsink host={client_ip} port={client_port} sync=false"; } // namespace video namespace audio { @@ -85,7 +85,7 @@ constexpr std::string_view DEFAULT_SINK = "rtpmoonlightpay_audio name=moonlight_ "packet_duration={packet_duration} " "encrypt={encrypt} aes_key=\"{aes_key}\" aes_iv=\"{aes_iv}\" " " ! " - "udpsink host={client_ip} port={client_port}"; + "udpsink host={client_ip} port={client_port} sync=false"; } // namespace audio } // namespace gstreamer diff --git a/tests/testGSTPlugin.cpp b/tests/testGSTPlugin.cpp index d75e6498..fa08f4bb 100644 --- a/tests/testGSTPlugin.cpp +++ b/tests/testGSTPlugin.cpp @@ -1,13 +1,11 @@ #include "catch2/catch_all.hpp" using Catch::Matchers::Equals; -extern "C" { -#include -} - +#include #include #include #include + using namespace std::string_literals; /* UTILS */ @@ -41,7 +39,7 @@ class GStreamerTestsFixture { public: GStreamerTestsFixture() { gst_init(nullptr, nullptr); - reed_solomon_init(); + moonlight::fec::init(); } }; @@ -262,7 +260,6 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Create RTP VIDEO packets", "[GSTPlugin] SECTION("REED SOLOMON") { auto data_shards = 2; auto parity_shards = 2; - auto packet_size = rtpmoonlightpay->payload_size + (int)sizeof(state::VideoRTPHeaders); auto total_shards = data_shards + parity_shards; auto flatten_packets = gst_buffer_list_unfold(rtp_packets); @@ -270,40 +267,40 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Create RTP VIDEO packets", "[GSTPlugin] unsigned char *packets_ptr[total_shards]; for (int shard_idx = 0; shard_idx < total_shards; shard_idx++) { - packets_ptr[shard_idx] = &packets_content.front() + (shard_idx * packet_size); + packets_ptr[shard_idx] = &packets_content.front() + (shard_idx * rtp_packet_size); } SECTION("If no package is marked nothing should change") { std::vector marks = {0, 0, 0, 0}; - auto rs = reed_solomon_new(data_shards, parity_shards); - auto result = reed_solomon_reconstruct(rs, packets_ptr, &marks.front(), total_shards, packets_content.size()); + auto rs = moonlight::fec::create(data_shards, parity_shards); + auto result = moonlight::fec::decode(rs.get(), packets_ptr, &marks.front(), total_shards, rtp_packet_size); REQUIRE(result == 0); REQUIRE_THAT(packets_content, Equals(gst_buffer_copy_content(flatten_packets))); } SECTION("Missing one packet should still lead to successfully reconstruct") { - auto missing_pkt = std::vector(packet_size); + auto missing_pkt = std::vector(rtp_packet_size); packets_ptr[0] = &missing_pkt[0]; std::vector marks = {1, 0, 0, 0}; - auto rs = reed_solomon_new(data_shards, parity_shards); - auto result = reed_solomon_reconstruct(rs, packets_ptr, &marks.front(), total_shards, packet_size); + auto rs = moonlight::fec::create(data_shards, parity_shards); + auto result = moonlight::fec::decode(rs.get(), packets_ptr, &marks.front(), total_shards, rtp_packet_size); REQUIRE(result == 0); // Here the packet headers will be wrongly reconstructed because we are manually // modifying the parity packets after creation // We can only check the packet payload here which should be correctly reconstructed + auto pay_size = rtpmoonlightpay->payload_size - MAX_RTP_HEADER_SIZE; auto missing_pkt_payload = std::vector( missing_pkt.begin() + sizeof(state::VideoRTPHeaders), - missing_pkt.begin() + sizeof(state::VideoRTPHeaders) + rtpmoonlightpay->payload_size); - auto first_packet_pay_before_fec = gst_buffer_copy_content(gst_buffer_list_get(rtp_packets, 0), - sizeof(state::VideoRTPHeaders), - rtpmoonlightpay->payload_size); - // TODO: fix this - // REQUIRE_THAT(missing_pkt_payload, Equals(first_packet_pay_before_fec)); + missing_pkt.begin() + sizeof(state::VideoRTPHeaders) + pay_size); + auto first_packet_pay_before_fec = + gst_buffer_copy_content(gst_buffer_list_get(rtp_packets, 0), sizeof(state::VideoRTPHeaders), pay_size); + + REQUIRE_THAT(missing_pkt_payload, Equals(first_packet_pay_before_fec)); } } } @@ -333,10 +330,10 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Audio RTP packet creation", "[GSTPlugin REQUIRE(gst_buffer_list_length(rtp_packets) == 1); REQUIRE(rtpmoonlightpay->cur_seq_number == 1); + auto first_pkt = gst_buffer_list_get(rtp_packets, 0); SECTION("First packet") { - auto buf = gst_buffer_list_get(rtp_packets, 0); - auto rtp_packet = get_rtp_audio_from_buf(buf); + auto rtp_packet = get_rtp_audio_from_buf(first_pkt); REQUIRE(rtp_packet->rtp.ssrc == 0); REQUIRE(rtp_packet->rtp.packetType == 97); @@ -344,7 +341,7 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Audio RTP packet creation", "[GSTPlugin REQUIRE(rtp_packet->rtp.sequenceNumber == 0); REQUIRE(rtp_packet->rtp.timestamp == 0); - auto rtp_payload = gst_buffer_copy_content(buf, sizeof(state::AudioRTPHeaders)); + auto rtp_payload = gst_buffer_copy_content(first_pkt, sizeof(state::AudioRTPHeaders)); auto decrypted = crypto::aes_decrypt_cbc(std::string(rtp_payload.begin(), rtp_payload.end()), rtpmoonlightpay->aes_key, @@ -356,10 +353,10 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Audio RTP packet creation", "[GSTPlugin rtp_packets = audio::split_into_rtp(rtpmoonlightpay, payload); REQUIRE(gst_buffer_list_length(rtp_packets) == 1); REQUIRE(rtpmoonlightpay->cur_seq_number == 2); + auto second_pkt = gst_buffer_list_get(rtp_packets, 0); SECTION("Second packet") { - auto buf = gst_buffer_list_get(rtp_packets, 0); - auto rtp_packet = get_rtp_audio_from_buf(buf); + auto rtp_packet = get_rtp_audio_from_buf(second_pkt); REQUIRE(rtp_packet->rtp.ssrc == 0); REQUIRE(rtp_packet->rtp.packetType == 97); @@ -367,7 +364,7 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Audio RTP packet creation", "[GSTPlugin REQUIRE(boost::endian::big_to_native(rtp_packet->rtp.sequenceNumber) == 1); REQUIRE(boost::endian::big_to_native(rtp_packet->rtp.timestamp) == 5); - auto rtp_payload = gst_buffer_copy_content(buf, sizeof(state::AudioRTPHeaders)); + auto rtp_payload = gst_buffer_copy_content(second_pkt, sizeof(state::AudioRTPHeaders)); auto decrypted = crypto::aes_decrypt_cbc(std::string(rtp_payload.begin(), rtp_payload.end()), rtpmoonlightpay->aes_key, @@ -379,44 +376,93 @@ TEST_CASE_METHOD(GStreamerTestsFixture, "Audio RTP packet creation", "[GSTPlugin rtp_packets = audio::split_into_rtp(rtpmoonlightpay, payload); REQUIRE(gst_buffer_list_length(rtp_packets) == 1); REQUIRE(rtpmoonlightpay->cur_seq_number == 3); + auto third_pkt = gst_buffer_list_get(rtp_packets, 0); + SECTION("Third packet") { + auto rtp_packet = get_rtp_audio_from_buf(third_pkt); + + REQUIRE(rtp_packet->rtp.ssrc == 0); + REQUIRE(rtp_packet->rtp.packetType == 97); + REQUIRE(rtp_packet->rtp.header == 0x80); + REQUIRE(boost::endian::big_to_native(rtp_packet->rtp.sequenceNumber) == 2); + REQUIRE(boost::endian::big_to_native(rtp_packet->rtp.timestamp) == 10); + + auto rtp_payload = gst_buffer_copy_content(third_pkt, sizeof(state::AudioRTPHeaders)); + + auto decrypted = crypto::aes_decrypt_cbc(std::string(rtp_payload.begin(), rtp_payload.end()), + rtpmoonlightpay->aes_key, + derive_iv(rtpmoonlightpay->aes_iv, rtpmoonlightpay->cur_seq_number - 1), + true); + REQUIRE_THAT(decrypted, Equals(payload_str)); + } + + /* When the 4th packet arrives, we'll also FEC encode all the previous and return + * the data packet + 2 more FEC packets + */ rtp_packets = audio::split_into_rtp(rtpmoonlightpay, payload); - REQUIRE(gst_buffer_list_length(rtp_packets) == 3); + REQUIRE(gst_buffer_list_length(rtp_packets) == 3); // One data packet + 2 FEC packets REQUIRE(rtpmoonlightpay->cur_seq_number == 4); + SECTION("FEC") { + SECTION("First FEC packet") { + auto fec_packet = (state::AudioFECPacket *)copy_buffer_data((gst_buffer_list_get(rtp_packets, 1))).first; + + REQUIRE(fec_packet->rtp.ssrc == 0); + REQUIRE(fec_packet->rtp.packetType == 127); + REQUIRE(fec_packet->rtp.header == 0x80); + REQUIRE(fec_packet->rtp.timestamp == 0); + + REQUIRE(boost::endian::big_to_native(fec_packet->rtp.sequenceNumber) == 3); + REQUIRE(fec_packet->fec_header.payloadType == 97); + REQUIRE(fec_packet->fec_header.ssrc == 0); + REQUIRE(fec_packet->fec_header.fecShardIndex == 0); + } + + SECTION("Second FEC packet") { + auto fec_packet = (state::AudioFECPacket *)copy_buffer_data((gst_buffer_list_get(rtp_packets, 2))).first; + + REQUIRE(fec_packet->rtp.ssrc == 0); + REQUIRE(fec_packet->rtp.packetType == 127); + REQUIRE(fec_packet->rtp.header == 0x80); + REQUIRE(fec_packet->rtp.timestamp == 0); + + REQUIRE(boost::endian::big_to_native(fec_packet->rtp.sequenceNumber) == 4); + REQUIRE(fec_packet->fec_header.payloadType == 97); + REQUIRE(fec_packet->fec_header.ssrc == 0); + REQUIRE(fec_packet->fec_header.fecShardIndex == 1); + } + } + SECTION("REED SOLOMON") { auto packet_size = gst_buffer_get_size(gst_buffer_list_get(rtp_packets, 0)); - auto total_shards = AUDIO_TOTAL_SHARDS; SECTION("If no package is marked nothing should change") { std::vector marks = {0, 0, 0, 0, 0, 0}; - auto result = reed_solomon_reconstruct(rtpmoonlightpay->rs, - rtpmoonlightpay->packets_buffer.data(), - &marks.front(), - total_shards, - packet_size); + auto result = moonlight::fec::decode(rtpmoonlightpay->rs.get(), + rtpmoonlightpay->packets_buffer, + &marks.front(), + AUDIO_TOTAL_SHARDS, + packet_size); REQUIRE(result == 0); } - SECTION("Missing one packet should still lead to successfully reconstruct") { - auto original_pkt = std::vector(rtpmoonlightpay->packets_buffer[0], - rtpmoonlightpay->packets_buffer[0] + packet_size); + SECTION("Missing one packet should still lead to successful reconstruct") { + auto original_pkt = gst_buffer_copy_content(first_pkt, sizeof(state::AudioRTPHeaders)); auto missing_pkt = std::vector(packet_size); rtpmoonlightpay->packets_buffer[0] = &missing_pkt[0]; std::vector marks = {1, 0, 0, 0, 0, 0}; - auto result = reed_solomon_reconstruct(rtpmoonlightpay->rs, - rtpmoonlightpay->packets_buffer.data(), - &marks.front(), - total_shards, - packet_size); + auto result = moonlight::fec::decode(rtpmoonlightpay->rs.get(), + rtpmoonlightpay->packets_buffer, + &marks.front(), + AUDIO_TOTAL_SHARDS, + packet_size); REQUIRE(result == 0); - // TODO: this fails on clang when building as release - // see: https://github.com/games-on-whales/wolf/actions/runs/3553743568/jobs/5969436029 - // REQUIRE_THAT(missing_pkt, Equals(original_pkt)); + REQUIRE_THAT(std::string(missing_pkt.begin() + sizeof(state::AudioRTPHeaders), missing_pkt.end()), + Equals(std::string(original_pkt.begin(), original_pkt.end()))); } g_object_unref(rtpmoonlightpay);