mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-20 09:40:08 +00:00
Use rather defined function as mkdir(3p) is, even though there is needed
a compat for MSW.
+ removed some strtok_r undef, which should no longer be needed (see
also commit 6c6253c9)
427 lines
15 KiB
C++
427 lines
15 KiB
C++
/**
|
|
* @file rtp/ldgm.cpp
|
|
* @author Martin Pulec <pulec@cesnet.cz>
|
|
*/
|
|
/*
|
|
* Copyright (c) 2012-2023 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.
|
|
*/
|
|
|
|
#define WANT_MKDIR
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /* HAVE_CONFIG_H */
|
|
#include "config_unix.h"
|
|
#include "config_win32.h"
|
|
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <limits>
|
|
|
|
#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<double>::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 = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
|
|
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={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<LDGM_session_gpu *(*)()>(
|
|
const_cast<void *>(
|
|
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<LDGM_session>(loader());
|
|
|
|
}
|
|
} else {
|
|
m_coding_session = unique_ptr<LDGM_session>(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<int, int> &packets) {
|
|
char *decoded;
|
|
decoded = m_coding_session->decode_frame(frame, size, out_size, packets);
|
|
if (*out_size > 0) {
|
|
*out = decoded;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////
|
|
// ENCODER
|
|
//////////////////////////////////
|
|
static void usage() {
|
|
printf("LDGM usage:\n"
|
|
"\t-f ldgm:<expected_loss>%% | ldgm[:<k>:<m>[:c]]\n"
|
|
"\n"
|
|
"\t\t<expected_loss> - 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<k> - matrix width\n"
|
|
"\t\t<m> - matrix height\n"
|
|
"\t\t<c> - 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<video_frame> ldgm::encode(shared_ptr<video_frame> 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<LDGM_session> coding_session = this->m_coding_session;
|
|
shared_ptr<video_frame> out(vf_alloc_desc(video_desc_from_frame(tx_frame.get())),
|
|
[coding_session = std::move(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;
|
|
}
|
|
|