UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
video_compress.cpp
Go to the documentation of this file.
1
8/*
9 * Copyright (c) 2011-2024 CESNET z.s.p.o.
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, is permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of CESNET nor the names of its contributors may be
24 * used to endorse or promote products derived from this software without
25 * specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
28 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
29 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
30 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
31 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
32 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
37 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
38 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 */
40
41#include <cassert>
42#include <cinttypes>
43#include <cstdio>
44#include <cstring>
45#include <memory>
46#include <string>
47#include <thread>
48#include <vector>
49
50#include "messaging.h"
51#include "module.h"
52#include "tv.h"
54#include "utils/thread.h"
55#include "utils/vf_split.h"
56#include "utils/worker.h"
57#include "video.h"
58#include "video_compress.h"
59#include "lib_common.h"
60#include "debug.h"
61
62#define MOD_NAME "[vcompress] "
63
64using namespace std;
65
66struct compress_state;
67
68namespace {
72struct compress_state_real {
73private:
74 compress_state_real(struct module *parent, const char *config_string);
75 void start(struct compress_state *proxy);
76 void async_consumer(struct compress_state *s);
77 void async_tile_consumer(struct compress_state *s);
78 thread asynch_consumer_thread;
79public:
80 static compress_state_real *create(struct module *parent, const char *config_string,
81 struct compress_state *proxy) {
82 compress_state_real *s = new compress_state_real(parent, config_string);
83 s->start(proxy);
84 return s;
85 }
86 ~compress_state_real();
87 const video_compress_info *funcs;
88 vector<struct module *> state;
89 string compress_options;
90 volatile bool discard_frames;
91};
92}
93
101 struct module mod;
102 struct compress_state_real *ptr;
104 bool poisoned = false;
105};
106
107static shared_ptr<video_frame> compress_frame_tiles(struct compress_state *proxy,
108 shared_ptr<video_frame> frame);
109
111void show_compress_help(bool full)
112{
113 printf("Possible compression modules (see '-c <module>:help' for options):\n");
115}
116
117static void async_poison(struct compress_state_real *s){
118 if (s->funcs->compress_frame_async_push_func) {
119 s->funcs->compress_frame_async_push_func(s->state[0], {}); // poison
120 } else if (s->funcs->compress_tile_async_push_func){
121 for(size_t i = 0; i < s->state.size(); i++){
122 s->funcs->compress_tile_async_push_func(s->state[i], {}); // poison
123 }
124 }
125}
126
137{
138 struct response *r = NULL;
139 /* In this case we are only changing some parameter of compression.
140 * This means that we pass the parameter to compress driver. */
141 if(data->what == CHANGE_PARAMS) {
142 for(unsigned int i = 0; i < proxy->ptr->state.size(); ++i) {
143 struct msg_change_compress_data *tmp_data =
144 (struct msg_change_compress_data *)
146 tmp_data->what = data->what;
147 strncpy(tmp_data->config_string, data->config_string,
148 sizeof(tmp_data->config_string));
149 struct response *resp = send_message_to_receiver(proxy->ptr->state[i],
150 (struct message *) tmp_data);
153 free_response(r); // frees previous response
154 r = resp;
155 }
156
157 } else {
158 struct compress_state_real *new_state;
159 char config[1024];
160 strncpy(config, data->config_string, sizeof(config));
161
162 try {
163 new_state = compress_state_real::create(&proxy->mod, config, proxy);
164 } catch (int i) {
165 free_message((struct message *) data,
167 return;
168 }
169
170 struct compress_state_real *old = proxy->ptr;
171 // let the async processing finish
172 old->discard_frames = true;
173 async_poison(old);
174 delete old;
175 proxy->ptr = new_state;
176 r = new_response(RESPONSE_OK, NULL);
177 }
178
179 free_message((struct message *) data, r);
180}
181
193int compress_init(struct module *parent, const char *config_string, struct compress_state **state) {
194 struct compress_state *proxy;
195 proxy = new struct compress_state();
196
197 module_init_default(&proxy->mod);
199 proxy->mod.priv_data = proxy;
200
201 try {
202 proxy->ptr = compress_state_real::create(&proxy->mod, config_string, proxy);
203 } catch (int i) {
204 delete proxy;
205 return i;
206 }
207
208 module_register(&proxy->mod, parent);
209
210 *state = proxy;
211 return 0;
212}
213
221compress_state_real::compress_state_real(struct module *parent, const char *config_string) :
222 funcs(nullptr), discard_frames(false)
223{
224 string compress_name;
225
226 if (!config_string)
227 throw -1;
228
229 if (strcmp(config_string, "help") == 0 || strcmp(config_string, "fullhelp") == 0) {
230 show_compress_help(strcmp(config_string, "fullhelp") == 0);
231 throw 1;
232 }
233
234 char *tmp = strdup(config_string);
235 if (strchr(tmp, ':')) {
236 char *opts = strchr(tmp, ':') + 1;
237 *strchr(tmp, ':') = '\0';
238 compress_options = opts;
239 }
240 compress_name = tmp;
241 free(tmp);
242
243 auto vci = static_cast<const struct video_compress_info *>(load_library(compress_name.c_str(), LIBRARY_CLASS_VIDEO_COMPRESS, VIDEO_COMPRESS_ABI_VERSION));
244 if(!vci) {
246 << MOD_NAME "Unknown or unavailable compression: "
247 << config_string << "\n";
248 throw -1;
249 }
250
251 funcs = vci;
252
253 if (funcs->init_func) {
254 state.resize(1);
255 state[0] = funcs->init_func(parent, compress_options.c_str());
256 if(!state[0]) {
258 << MOD_NAME "Compression initialization failed: "
259 << config_string << "\n";
260 throw -1;
261 }
262 if(state[0] == INIT_NOERR) {
263 throw 1;
264 }
265 } else {
266 throw -1;
267 }
268}
269
270void compress_state_real::start(struct compress_state *proxy)
271{
273 asynch_consumer_thread = thread(&compress_state_real::async_consumer, this, proxy);
274 } else if (funcs->compress_tile_async_push_func){
275 asynch_consumer_thread = thread(&compress_state_real::async_tile_consumer, this, proxy);
276 }
277}
278
287static bool check_state_count(unsigned tile_count, struct compress_state *proxy)
288{
289 struct compress_state_real *s = proxy->ptr;
290
291 if(tile_count != s->state.size()) {
292 size_t old_size = s->state.size();
293 s->state.resize(tile_count);
294 for (unsigned int i = old_size; i < s->state.size(); ++i) {
295 s->state[i] = s->funcs->init_func(&proxy->mod, s->compress_options.c_str());
296 if(!s->state[i]) {
298 "Compression initialization failed\n";
299 return false;
300 }
301 }
302 }
303 return true;
304}
305
318void compress_frame(struct compress_state *proxy, shared_ptr<video_frame> frame)
319{
320 if (!proxy)
321 abort();
322
323 struct msg_change_compress_data *msg = NULL;
324 while ((msg = (struct msg_change_compress_data *) check_message(&proxy->mod))) {
326 }
327
328 struct compress_state_real *s = proxy->ptr;
329
330 if (!frame) {
331 proxy->poisoned = true;
332 }
333 if (frame) {
335 }
336
337 if (s->funcs->compress_frame_async_push_func) {
338 assert(s->funcs->compress_frame_async_pop_func);
339 s->funcs->compress_frame_async_push_func(s->state[0], frame);
340 return;
341 }
342 if (s->funcs->compress_tile_async_push_func) {
343 assert(s->funcs->compress_tile_async_pop_func);
344 if (!frame) {
345 async_poison(s);
346 return;
347 }
348
349 if(!check_state_count(frame->tile_count, proxy)){
350 return;
351 }
352
353 vector<shared_ptr<video_frame>> separate_tiles = vf_separate_tiles(frame);
354
355 for(unsigned i = 0; i < separate_tiles.size(); i++){
356 s->funcs->compress_tile_async_push_func(s->state[i], separate_tiles[i]);
357 }
358 return;
359 }
360
361 // sync APIs - pass poisoned pill to the queue but not to compressions,
362 if (!frame) { // which doesn't need that but use NULL frame differently
363 proxy->queue.push(shared_ptr<video_frame>());
364 return;
365 }
366
367 shared_ptr<video_frame> sync_api_frame;
368 do {
369 if (s->funcs->compress_frame_func) {
370 sync_api_frame = s->funcs->compress_frame_func(s->state[0], frame);
371 } else if(s->funcs->compress_tile_func) {
372 sync_api_frame = compress_frame_tiles(proxy, frame);
373 } else {
374 assert(!"No eligible compress API found");
375 }
376
377 // empty return value here represents error, but we don't want to pass it to queue, since it would
378 // be interpreted as poisoned pill
379 if (!sync_api_frame) {
380 return;
381 }
382 sync_api_frame->compress_end = get_time_in_ns();
383 proxy->queue.push(sync_api_frame);
384 frame = nullptr;
385 } while (true);
386}
387
397 struct module *state;
398 shared_ptr<video_frame> frame;
399
401 shared_ptr<video_frame> ret;
402};
403
409static void *compress_tile_callback(void *arg) {
411
412 s->ret = s->callback(s->state, s->frame);
413
414 return s;
415}
416
424static shared_ptr<video_frame> compress_frame_tiles(struct compress_state *proxy,
425 shared_ptr<video_frame> frame)
426{
427 struct compress_state_real *s = proxy->ptr;
428 const int tile_cnt = (int) proxy->ptr->state.size();
429 vector<shared_ptr<video_frame>> separate_tiles;
430 if (frame) {
431 if (!check_state_count(frame->tile_count, proxy)) {
432 return nullptr;
433 }
434 separate_tiles = vf_separate_tiles(frame);
435 } else {
436 separate_tiles.resize(tile_cnt);
437 }
438
439 // frame pointer may no longer be valid
440 frame = NULL;
441
442 vector<task_result_handle_t> task_handle(tile_cnt);
443
444 vector <compress_worker_data> data_tile(tile_cnt);
445 for (int i = 0; i < tile_cnt; ++i) {
446 struct compress_worker_data *data = &data_tile[i];
447 data->state = s->state[i];
448 data->frame = separate_tiles[i];
449 data->callback = s->funcs->compress_tile_func;
450
451 task_handle[i] = task_run_async(compress_tile_callback, data);
452 }
453
454 vector<shared_ptr<video_frame>> compressed_tiles(separate_tiles.size());
455
456 bool failed = false;
457 for(unsigned int i = 0; i < separate_tiles.size(); ++i) {
458 struct compress_worker_data *data = (struct compress_worker_data *)
459 wait_task(task_handle[i]);
460
461 if(!data->ret) {
462 failed = true;
463 }
464
465 compressed_tiles[i] = data->ret;
466 }
467
468 if (failed) {
469 return NULL;
470 }
471
472 return vf_merge_tiles(compressed_tiles);
473}
482void
484{
485 if (proxy == nullptr) {
486 return;
487 }
488
489 struct compress_state_real *s = proxy->ptr;
490 if (!proxy->poisoned) { // pass poisoned pill if it wasn't
491 compress_frame(proxy, {});
492 }
493
494 delete s;
495 module_done(&proxy->mod);
496 delete proxy;
497}
498
499compress_state_real::~compress_state_real()
500{
501 if (asynch_consumer_thread.joinable()) {
502 asynch_consumer_thread.join();
503 }
504
505 for(unsigned int i = 0; i < state.size(); ++i) {
506 module_done(state[i]);
507 }
508}
509
510namespace {
511void compress_state_real::async_tile_consumer(struct compress_state *s)
512{
513 set_thread_name(__func__);
514 vector<shared_ptr<video_frame>> compressed_tiles;
515 unsigned expected_seq = 0;
516 while (true) {
517 bool fail = false;
518 for(unsigned i = 0; i < state.size(); i++){
519 std::shared_ptr<video_frame> ret = nullptr;
520 //discard frames with seq lower than expected
521 do {
522 ret = funcs->compress_tile_async_pop_func(state[i]);
523 } while(ret && ret->seq < expected_seq);
524
525 if (!ret) {
526 if(!discard_frames)
527 s->queue.push(nullptr); //poison
528 return;
529 }
530
531 if(ret->seq > expected_seq){
533 "Expected sequence number %u but got %u!\n",
534 expected_seq,
535 ret->seq);
536 if(i == 0){
537 //If this is first tile we can continue
538 expected_seq = ret->seq;
539 } else {
540 expected_seq = ret->seq + 1;
541 fail = true;
542 break;
543 }
544 }
545
546 ret->compress_end = get_time_in_ns();
547 compressed_tiles.resize(state.size(), nullptr);
548 compressed_tiles[i] = std::move(ret);
549 }
550
551 if(fail)
552 continue;
553
554 if (!discard_frames) {
555 s->queue.push(vf_merge_tiles(compressed_tiles));
556 }
557 //If frames are not numbered they always have seq = 0
558 if(expected_seq > 0) expected_seq++;
559 }
560}
561
562void compress_state_real::async_consumer(struct compress_state *s)
563{
564 set_thread_name(__func__);
565 while (true) {
566 auto frame = funcs->compress_frame_async_pop_func(state[0]);
567 if (!discard_frames) {
568 s->queue.push(frame);
569
570 }
571 if (!frame) {
572 return;
573
574 }
575
576 }
577
578}
579} // end of anonymous namespace
580
586shared_ptr<video_frame> compress_pop(struct compress_state *proxy)
587{
588 if(!proxy)
589 return NULL;
590
591 auto f = proxy->queue.pop();
592 if (f) {
593 MSG(DEBUG, "Compressed frame size: %8u; duration: %7.3f ms\n",
594 vf_get_data_len(f.get()),
595 (f->compress_end - f->compress_start) / MS_IN_NS_DBL);
596 }
597 return f;
598}
599
void ** state
struct video_frame * frame
#define MOD_NAME
Definition audio.cpp:110
simple blocking synchronized queue
T pop(bool nonblocking=false)
void push(T const &message)
static void new_message(struct module *m)
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define MSG(l, fmt,...)
Definition debug.h:96
#define LOG(level)
Definition debug.h:313
void module_init_default(struct module *module_data)
Definition module.c:48
void module_register(struct module *module_data, struct module *parent)
Definition module.c:82
void module_done(struct module *module_data)
Definition module.c:93
@ MODULE_CLASS_COMPRESS
Definition module.h:85
#define VIDEO_COMPRESS_ABI_VERSION
void show_compress_help(bool full)
Displays list of available compressions.
shared_ptr< video_frame > compress_pop(struct compress_state *proxy)
void compress_frame(struct compress_state *proxy, shared_ptr< video_frame > frame)
int compress_init(struct module *parent, const char *config_string, struct compress_state **state)
This function initializes video compression.
std::shared_ptr< video_frame >(* compress_tile_t)(struct module *state, std::shared_ptr< video_frame > in_frame)
Compresses tile of a video frame.
void compress_done(struct compress_state *proxy)
Video compression cleanup function.
#define INIT_NOERR
Definition host.h:73
void list_modules(enum library_class cls, int abi_version, bool full)
const void * load_library(const char *name, enum library_class cls, int abi_version)
@ LIBRARY_CLASS_VIDEO_COMPRESS
Definition lib_common.h:82
#define DEBUG
Definition main.c:27
void free_message(struct message *msg, struct response *r)
struct response * new_response(int status, const char *text)
struct response * send_message_to_receiver(struct module *receiver, struct message *msg)
struct message * check_message(struct module *mod)
void free_response(struct response *r)
#define RESPONSE_OK
Definition messaging.h:57
@ CHANGE_PARAMS
Definition messaging.h:130
#define RESPONSE_INT_SERV_ERR
Definition messaging.h:63
static void start()
Definition sdp.c:202
const char * parent
Definition settings.cpp:198
Video compress state.
struct module mod
compress module data
synchronized_queue< shared_ptr< video_frame >, 1 > queue
struct compress_state_real * ptr
pointer to real compress state
Auxiliary structure passed to worker thread.
shared_ptr< video_frame > frame
uncompressed tile to be compressed
shared_ptr< video_frame > ret
OUT - returned compressed tile, NULL if failed.
struct module * state
compress driver status
compress_tile_t callback
tile compress callback
enum module_class cls
Definition module.h:115
void * priv_data
Definition module.h:126
enum compress_change_type what
Definition messaging.h:135
compress_tile_async_push_t compress_tile_async_push_func
Async tile API.
compress_frame_async_pop_t compress_frame_async_pop_func
Async API.
compress_init_t init_func
compress driver initialization function
compress_frame_async_push_t compress_frame_async_push_func
Async API.
compress_frame_t compress_frame_func
compress function for Frame API
compress_tile_t compress_tile_func
compress function for Tile API
compress_tile_async_pop_t compress_tile_async_pop_func
Async tile API.
int64_t compress_start
in ns from epoch
Definition types.h:338
unsigned int tile_count
Definition types.h:344
void set_thread_name(const char *name)
Definition thread.c:72
#define MS_IN_NS_DBL
Definition tv.h:86
static time_ns_t get_time_in_ns()
Definition tv.h:101
shared_ptr< video_frame > vf_merge_tiles(std::vector< shared_ptr< video_frame > > const &tiles)
Definition vf_split.cpp:135
vector< shared_ptr< video_frame > > vf_separate_tiles(shared_ptr< video_frame > frame)
Definition vf_split.cpp:114
This is an umbrella header for video functions.
static bool check_state_count(unsigned tile_count, struct compress_state *proxy)
static shared_ptr< video_frame > compress_frame_tiles(struct compress_state *proxy, shared_ptr< video_frame > frame)
#define MOD_NAME
static void * compress_tile_callback(void *arg)
This function is callback passed to a "thread pool".
static void async_poison(struct compress_state_real *s)
static void compress_process_message(struct compress_state *proxy, struct msg_change_compress_data *data)
Processes message.
API for video compress drivers.
unsigned int vf_get_data_len(struct video_frame *f)
void * wait_task(task_result_handle_t handle)
Definition worker.cpp:280
task_result_handle_t task_run_async(runnable_t task, void *data)
Runs task asynchronously.
Definition worker.cpp:262