/** * @file rtp/ldgm.pp * @author Martin Pulec */ /* * Copyright (c) 2012-2019 CESNET, z. s. p. o. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is 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. * * 3. Neither the name of CESNET nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESSED 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 OR CONTRIBUTORS 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #include "config_unix.h" #include "config_win32.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "host.h" #include "ldgm.h" #include "lib_common.h" #include "ldgm/src/ldgm-session.h" #include "ldgm/src/ldgm-session-cpu.h" #include "ldgm/src/ldgm-session-gpu.h" #include "ldgm/matrix-gen/matrix-generator.h" #include "ldgm/matrix-gen/ldpc-matrix.h" // LDGM_MAX_K #include "rtp/rtp.h" #include "rtp/rtp_callback.h" #include "transmit.h" #include "video.h" using namespace std; typedef enum { STD1500 = 1500, JUMBO9000 = 9000, } packet_type_t; typedef struct { packet_type_t packet_type; int frame_size; double loss; // result int k, m, c; } configuration_t; #define PCT2 2.0 #define PCT5 5.0 #define PCT10 10.0 typedef double loss_t; loss_t losses[] = { std::numeric_limits::min(), PCT2, PCT5, PCT10, }; #define JPEG60_SIZE (144 * 1000) #define JPEG80_SIZE (177 * 1000) #define JPEG90_SIZE (217 * 1000) #define UNCOMPRESSED_SIZE (1920 * 1080 * 2) const configuration_t suggested_configurations[] = { // JPEG 60 { STD1500, JPEG60_SIZE, PCT2, 750, 120, 5 }, { STD1500, JPEG60_SIZE, PCT5, 1500, 450, 6 }, { STD1500, JPEG60_SIZE, PCT10, 1000, 500, 7 }, // JPEG 80 { STD1500, JPEG80_SIZE, PCT2, 1500, 240, 5 }, { STD1500, JPEG80_SIZE, PCT5, 1250, 375, 6 }, { STD1500, JPEG80_SIZE, PCT10, 1500, 750, 8 }, // JPEG 90 { STD1500, JPEG90_SIZE, PCT2, 1500, 240, 6 }, { STD1500, JPEG90_SIZE, PCT5, 1500, 450, 6 }, { STD1500, JPEG90_SIZE, PCT10, 1500, 750, 8 }, // uncompressed { JUMBO9000, UNCOMPRESSED_SIZE, PCT2, 1500, 180, 5 }, { JUMBO9000, UNCOMPRESSED_SIZE, PCT5, 1000, 300, 6 }, { JUMBO9000, UNCOMPRESSED_SIZE, PCT10, 1000, 500, 7 }, { STD1500, UNCOMPRESSED_SIZE, PCT2, 1500, 250, 5 }, { STD1500, UNCOMPRESSED_SIZE, PCT5, 1500, 650, 6 }, { STD1500, UNCOMPRESSED_SIZE, PCT10, 1500, 1500, 8 }, }; #define MINIMAL_VALUE 64 ///< reasonable minimum (seems that 32 crashes sometimes) ///< @todo Check why doesn't lower values work out #define DEFAULT_K 256 #define DEFAULT_M 192 #define DEFAULT_C 5 #define MIN_C 2 // reasonable minimum #define MAX_C 63 // from packet format #define MAX_K (1<<13) - 1 static bool file_exists(char *filename); static void usage(void); static bool file_exists(char *filename) { struct stat sb; if (stat(filename, &sb)) { perror("stat"); return false; } return true; } void ldgm::set_params(unsigned int k, unsigned int m, unsigned int c, unsigned int seed) { m_k = k; m_m = m; m_c = c; m_seed = seed; m_coding_session->set_params(k, m, c); char filename[512]; char path[256]; int res; #ifdef WIN32 TCHAR tmpPath[MAX_PATH]; UINT ret = GetTempPath(MAX_PATH, tmpPath); if(ret == 0 || ret > MAX_PATH) { fprintf(stderr, "Unable to get temporary directory name.\n"); throw 1; } snprintf(path, 256, "%s\\ultragrid\\", tmpPath); #else snprintf(path, 256, "/var/tmp/ultragrid-%d/", (int) getuid()); #endif res = platform_mkdir(path); if(res != 0) { if(errno != EEXIST) { perror("mkdir"); fprintf(stderr, "[LDGM] Unable to create data directory.\n"); throw 1; } } snprintf(filename, 512, "%s/ldgm_matrix-%u-%u-%u-%u.bin", path, k, m, c, seed); if(!file_exists(filename)) { int ret = generate_ldgm_matrix(filename, k, m, c, seed, 0); if(ret != 0) { fprintf(stderr, "[LDGM] Unable to initialize LDGM matrix.\n"); throw 1; } } m_coding_session->set_pcMatrix(filename); } ADD_TO_PARAM(ldgm_device, "ldgm-device", "* ldgm-device={CPU|GPU}\n" " specify whether use CPU or GPU for LDGM\n"); void ldgm::init(unsigned int k, unsigned int m, unsigned int c, unsigned int seed) { bool ldgm_device_gpu = false; if (get_commandline_param("ldgm-device")) { ldgm_device_gpu = strcasecmp(get_commandline_param("ldgm-device"), "GPU") == 0; } if (ldgm_device_gpu) { LDGM_session_gpu *(*loader)(); loader = reinterpret_cast( const_cast( load_library("ldgm_gpu", LIBRARY_CLASS_UNDEFINED, LDGM_GPU_API_VERSION))); if (!loader) { throw string("GPU accelerated LDGM support is not compiled in"); } else { m_coding_session = unique_ptr(loader()); } } else { m_coding_session = unique_ptr(new LDGM_session_cpu()); } set_params(k, m, c, seed); } ldgm::ldgm(unsigned int k, unsigned int m, unsigned int c, unsigned int seed) { init(k, m, c, seed); } bool ldgm::decode(char *frame, int size, char **out, int *out_size, const map &packets) { char *decoded; decoded = m_coding_session->decode_frame(frame, size, out_size, packets); if (decoded) { *out = decoded; return true; } else { return false; } } ////////////////////////////////// // ENCODER ////////////////////////////////// static void usage() { printf("LDGM usage:\n" "\t-f ldgm:%% | ldgm[::[:c]]\n" "\n" "\t\t - expected maximal loss in percent (including '%%'-sign)\n" "\t\tPlease note that there are only a few presets for FullHD video\n" "\t\tthat will work correctly (JPEG and uncompressed, see wiki).\n\n" "\t\t - matrix width\n" "\t\t - matrix height\n" "\t\t - number of ones per column\n" "\t\t\tthe bigger ratio m/k, the better correction (but also needed bandwidth)\n" "\t\t\tk,m should be in interval [%d, %d]; c in [%d, %d]\n" "\t\t\tdefault: k = %d, m = %d, c = %d\n" "\n", MINIMAL_VALUE, MAX_K, MIN_C, MAX_C, DEFAULT_K, DEFAULT_M, DEFAULT_C ); } ldgm::ldgm(const char *cfg) { unsigned int k = DEFAULT_K, m = DEFAULT_M, c = DEFAULT_C; if(cfg && strlen(cfg) > 0) { if(strcasecmp(cfg, "help") == 0) { usage(); throw 0; } char *save_ptr = NULL; char *item; char *tmp = strdup(cfg); item = strtok_r(tmp, ":", &save_ptr); if(item) { k = atoi(item); } item = strtok_r(NULL, ":", &save_ptr); if(item) { m = atoi(item); item = strtok_r(NULL, ":", &save_ptr); if(item) { c = atoi(item); } } else { throw string("[LDGM] Was set k value but not m."); } free(tmp); } if(c < MIN_C || c > MAX_C) { ostringstream oss; oss << "[LDGM] C value shoud be inside interval [" << MIN_C << ", " << MAX_C << "]"; throw oss.str(); } if(k > MAX_K) { ostringstream oss; oss << "[LDGM] K value exceeds maximal value " << MAX_K; throw oss.str(); } if(k < MINIMAL_VALUE || m < MINIMAL_VALUE) { ostringstream oss; oss << "[LDGM] Either k or m is lower than minimal value " << MINIMAL_VALUE; throw oss.str(); } printf("[LDGM] Using values k = %u, m = %u, c = %u.\n", k, m, c); init(k, m, c); } ldgm::ldgm(int packet_size, int frame_size, double max_expected_loss) { packet_type_t packet_type; int nearest = INT_MAX; loss_t loss = -1.0; int k, m, c; assert(max_expected_loss >= 0.0 && max_expected_loss <= 100.0); if(frame_size < 2000000 && packet_size >= (STD1500 + JUMBO9000) / 2) { throw string("LDGM: with frames smaller than 2M you should use standard Ethernet frames."); } if(packet_size < (STD1500 + JUMBO9000) / 2) { packet_type = STD1500; } else { packet_type = JUMBO9000; } for(unsigned int i = 1; i < sizeof(losses) / sizeof(loss_t); ++i) { if(max_expected_loss >= losses[i - 1] && max_expected_loss <= losses[i]) { loss = losses[i]; break; } } if(loss == -1.0) { ostringstream oss; oss << "LDGM: Cannot provide predefined settings for correction of loss of " << setprecision(2) << max_expected_loss << endl << "LDGM: You have to try and set LDGM parameters manually. You can inform us if you need better protection."; throw oss.str(); } printf("LDGM: Choosing maximal loss %2.2f percent.\n", loss); for(unsigned int i = 0; i < sizeof(suggested_configurations) / sizeof(configuration_t); ++i) { if(suggested_configurations[i].packet_type == packet_type && suggested_configurations[i].loss == loss) { if(abs(suggested_configurations[i].frame_size - frame_size) < abs(nearest - frame_size)) { nearest = suggested_configurations[i].frame_size; k = suggested_configurations[i].k; m = suggested_configurations[i].m; c = suggested_configurations[i].c; } } } if (nearest == INT_MAX) { throw string("LDGM: Could not find matching configuration for your request.\n" "Please, set LDGM parameters manually."); } double difference_from_frame_size = abs(nearest - frame_size) / (double) frame_size; if(difference_from_frame_size > 0.2) { LOG(LOG_LEVEL_WARNING) << "LDGM: Chosen LDGM setting for frame size that is " << difference_from_frame_size * 100.0 << "% " << (nearest - frame_size > 0 ? "higher" : "lower") << " than your frame size.\n"; LOG(LOG_LEVEL_WARNING) << "You may wish to set the parameters manually.\n"; } if(difference_from_frame_size > 0.5) { throw string(""); } init(k, m, c); } shared_ptr ldgm::encode(shared_ptr tx_frame) { // We need to have copy of coding session shared pointer in order to exit // gracefully even when destructed LDGM state first with some frame still // existing. std::shared_ptr coding_session = this->m_coding_session; shared_ptr out(vf_alloc_desc(video_desc_from_frame(tx_frame.get())), [coding_session](struct video_frame *frame) { for (unsigned int i = 0; i < frame->tile_count; ++i) { coding_session->free_out_buf(frame->tiles[i].data); } vf_free(frame); }); for (unsigned int i = 0; i < tx_frame->tile_count; ++i) { video_payload_hdr_t video_hdr; format_video_header(tx_frame.get(), i, 0, video_hdr); int out_size; char *output = m_coding_session->encode_hdr_frame((char *) video_hdr, sizeof(video_hdr), tx_frame->tiles[i].data, tx_frame->tiles[i].data_len, &out_size); out->tiles[i].data = output; out->tiles[i].data_len = out_size; } out->fec_params.type = FEC_LDGM; out->fec_params.k = m_k; out->fec_params.m = m_m; out->fec_params.c = m_c; out->fec_params.seed = m_seed; out->fec_params.symbol_size = m_coding_session->get_packet_size(); return out; }