UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
gpujpeg.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2011-2024 CESNET
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#include <algorithm>
39#include <cassert>
40#include <condition_variable>
41#include <initializer_list>
42#include <libgpujpeg/gpujpeg_common.h>
43#include <libgpujpeg/gpujpeg_encoder.h>
44#include <libgpujpeg/gpujpeg_version.h>
45#include <map>
46#include <memory>
47#include <mutex>
48#include <set>
49#include <thread>
50#include <vector>
51
52#include "debug.h"
53#include "host.h"
54#include "lib_common.h"
55#include "module.h"
56#include "tv.h"
57#include "utils/color_out.h"
58#include "utils/macros.h"
61#include "video.h"
62#include "video_compress.h"
63
64#ifndef GPUJPEG_VERSION_INT
65#error "Old GPUJPEG API detected!"
66#endif
67#if GPUJPEG_VERSION_INT >= GPUJPEG_MK_VERSION_INT(0, 25, 0)
68#define NEW_PARAM_IMG_NO_COMP_COUNT
69#else
70typedef int gpujpeg_sampling_factor_t;
71#endif
72
73#define MOD_NAME "[GPUJPEG enc.] "
74
75using std::condition_variable;
76using std::map;
77using std::max;
78using std::mutex;
79using std::set;
80using std::shared_ptr;
81using std::thread;
82using std::unique_lock;
83using std::unique_ptr;
84using std::vector;
85
86namespace {
87struct state_video_compress_gpujpeg;
88
92struct encoder_state {
93private:
94 void cleanup_state();
95 shared_ptr<video_frame> compress_step(shared_ptr<video_frame> frame);
96 bool configure_with(struct video_desc desc);
97
98 struct state_video_compress_gpujpeg *m_parent_state;
99 int m_device_id;
100 struct gpujpeg_encoder *m_encoder;
101 struct video_desc m_saved_desc;
102 video_frame_pool m_pool;
103 decoder_t m_decoder;
104 codec_t m_enc_input_codec{};
105 unique_ptr<char []> m_decoded;
106
107 struct gpujpeg_parameters m_encoder_param{};
108 struct gpujpeg_image_parameters m_param_image{};
109public:
110 encoder_state(struct state_video_compress_gpujpeg *s, int device_id) :
111 m_parent_state(s), m_device_id(device_id), m_encoder{}, m_saved_desc{},
112 m_decoder{}, m_occupied{}
113 {
114 }
115 ~encoder_state() {
116 cleanup_state();
117 }
118 void worker();
119 void compress(shared_ptr<video_frame> frame);
120
122 thread m_thread_id;
123 bool m_occupied;
124};
125
126struct state_video_compress_gpujpeg {
127private:
128 state_video_compress_gpujpeg(struct module *parent, const char *opts);
129
130 vector<struct encoder_state *> m_workers;
131 bool m_uses_worker_threads;
132
133 map<uint32_t, shared_ptr<struct video_frame>> m_out_frames;
134 uint32_t m_in_seq;
135 uint32_t m_out_seq;
136
137 size_t m_ended_count;
138
139public:
140 ~state_video_compress_gpujpeg() {
141 if (m_uses_worker_threads) {
142 for (auto worker : m_workers) {
143 worker->m_thread_id.join();
144 }
145 }
146
147 for (auto worker : m_workers) {
148 delete worker;
149 }
150 }
151 static state_video_compress_gpujpeg *create(struct module *parent, const char *opts);
152 bool parse_fmt(char *fmt);
153 void push(std::shared_ptr<video_frame> in_frame);
154 std::shared_ptr<video_frame> pop();
155
156 struct module m_module_data;
157 int m_restart_interval;
158 int m_quality;
159 bool m_force_interleaved = false;
160 bool m_compress_alpha = false;
161 gpujpeg_sampling_factor_t m_subsampling = 0; // -> autoselect
162 enum gpujpeg_color_space m_use_internal_codec = GPUJPEG_NONE; // requested internal codec
163
165 mutex m_occupancy_lock;
166 condition_variable m_worker_finished;
167};
168
178void encoder_state::compress(shared_ptr<video_frame> frame)
179{
180 if (frame) {
181 char vf_metadata[VF_METADATA_SIZE];
182 vf_store_metadata(frame.get(), vf_metadata); // seq and compress_start
183 auto out = compress_step(std::move(frame));
184 if (out) {
185 vf_restore_metadata(out.get(), vf_metadata);
186 out->compress_end = get_time_in_ns();
187 } else {
188 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Failed to encode frame!\n");
189 out = shared_ptr<video_frame>(vf_alloc(1), vf_free);
190 vf_restore_metadata(out.get(), vf_metadata);
191 }
192 m_parent_state->m_out_queue.push(out);
193 } else { // pass poison pill
194 m_parent_state->m_out_queue.push({});
195 }
196}
197
202void encoder_state::worker() {
203 while (true) {
204 auto frame = m_in_queue.pop();
205
206 if (!frame) { // poison pill - pass and exit
207 m_parent_state->m_out_queue.push(frame);
208 break;
209 }
210
211 compress(std::move(frame));
212
213 unique_lock<mutex> lk(m_parent_state->m_occupancy_lock);
214 m_occupied = false;
215 lk.unlock();
216 m_parent_state->m_worker_finished.notify_one();
217 }
218}
219
220static decoder_t get_decoder(codec_t in_codec, codec_t *out_codec)
221{
222 codec_t candidate_codecs[] = { UYVY, RGB,
223#if GJ_RGBA_SUPP == 1
224 RGBA,
225#endif
226 };
227
228 return get_best_decoder_from(in_codec, candidate_codecs, out_codec);
229}
230
231gpujpeg_sampling_factor_t
232subsampling_to_gj(int ug_subs)
233{
234 switch (ug_subs) {
235 case 444:
236 return GPUJPEG_SUBSAMPLING_444;
237 case 422:
238 return GPUJPEG_SUBSAMPLING_422;
239 case 420:
240 return GPUJPEG_SUBSAMPLING_420;
241 default:
242 abort();
243 }
244}
245
249bool encoder_state::configure_with(struct video_desc desc)
250{
251 struct video_desc compressed_desc;
252 compressed_desc = desc;
253 compressed_desc.color_spec = JPEG;
254
255 if (desc.color_spec == I420) {
256 m_decoder = nullptr;
257 m_enc_input_codec = desc.color_spec;
258 } else {
259 m_decoder = get_decoder(desc.color_spec, &m_enc_input_codec);
260 if (!m_decoder) {
261 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unsupported codec: %s\n",
263 return false;
264 }
265 }
266
268 LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Converting from " << get_bits_per_component(desc.color_spec) <<
269 " to 8 bits. You may directly capture 8-bit signal to improve performance.\n";
270 }
271
272 gpujpeg_set_default_parameters(&m_encoder_param);
273 if (m_parent_state->m_quality != -1) {
274 m_encoder_param.quality = m_parent_state->m_quality;
275 } else {
276 log_msg(LOG_LEVEL_INFO, MOD_NAME "setting default encode parameters (quality: %d)\n",
277 m_encoder_param.quality);
278 }
279
280 m_encoder_param.verbose = max<int>(0, log_level - LOG_LEVEL_INFO);
281 m_encoder_param.segment_info = 1;
282 gpujpeg_sampling_factor_t subsampling = m_parent_state->m_subsampling;
283#if !defined NEW_PARAM_IMG_NO_COMP_COUNT
284 if (subsampling == 0) {
286 subsampling_to_gj(get_subsampling(m_enc_input_codec) / 10);
287 }
288#endif
289 gpujpeg_parameters_chroma_subsampling(&m_encoder_param, subsampling);
290 m_encoder_param.interleaved = (codec_is_a_rgb(m_enc_input_codec) && !m_parent_state->m_force_interleaved) ? 0 : 1;
291 m_encoder_param.color_space_internal = IF_NOT_NULL_ELSE(m_parent_state->m_use_internal_codec, codec_is_a_rgb(m_enc_input_codec)
292 ? GPUJPEG_RGB : GPUJPEG_YCBCR_BT709);
293
294 gpujpeg_image_set_default_parameters(&m_param_image);
295
296 m_param_image.width = desc.width;
297 m_param_image.height = desc.height;
298
299
300#if !defined NEW_PARAM_IMG_NO_COMP_COUNT
301 m_param_image.comp_count = 3;
302#endif
303 if (m_parent_state->m_compress_alpha) {
304 if (desc.color_spec == RGBA) {
305#ifdef NEW_PARAM_IMG_NO_COMP_COUNT
306 gpujpeg_parameters_chroma_subsampling(
307 &m_encoder_param, GPUJPEG_SUBSAMPLING_4444);
308#else
309 m_param_image.comp_count = 4;
310#endif
311 } else {
312 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Requested alpha encode but input codec is unsupported pixel format: "
313 << get_codec_name(desc.color_spec) << "\n";
314 }
315 }
316 m_param_image.color_space = codec_is_a_rgb(m_enc_input_codec) ? GPUJPEG_RGB : GPUJPEG_YCBCR_BT709;
317
318 switch (m_enc_input_codec) {
319 case I420: m_param_image.pixel_format = GPUJPEG_420_U8_P0P1P2; break;
320 case RGB: m_param_image.pixel_format = GPUJPEG_444_U8_P012; break;
321#ifdef NEW_PARAM_IMG_NO_COMP_COUNT
322 case RGBA: m_param_image.pixel_format = GPUJPEG_4444_U8_P0123; break;
323#else
324 case RGBA: m_param_image.pixel_format = GPUJPEG_444_U8_P012Z; break;
325#endif
326 case UYVY: m_param_image.pixel_format = GPUJPEG_422_U8_P1020; break;
327 default:
328 log_msg(LOG_LEVEL_FATAL, MOD_NAME "Unexpected codec: %s\n",
329 get_codec_name(m_enc_input_codec));
330 abort();
331 }
332 m_encoder_param.restart_interval = IF_NOT_UNDEF_ELSE(m_parent_state->m_restart_interval,
333#if GPUJPEG_VERSION_INT >= GPUJPEG_MK_VERSION_INT(0, 25, 3)
334 RESTART_AUTO);
335#elif GPUJPEG_VERSION_INT >= GPUJPEG_MK_VERSION_INT(0, 20, 4)
336 gpujpeg_encoder_suggest_restart_interval(&m_param_image, subsampling, m_encoder_param.interleaved, m_encoder_param.verbose));
337#else
338 codec_is_a_rgb(m_enc_input_codec) ? 8 : 4);
339#endif
340 m_encoder = gpujpeg_encoder_create(NULL);
341
342 int data_len = desc.width * desc.height * 3;
343 m_pool.reconfigure(compressed_desc, data_len);
344
345 if(!m_encoder) {
346 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to create GPUJPEG encoder.\n");
347 exit_uv(EXIT_FAILURE);
348 return false;
349 }
350
351 m_decoded = unique_ptr<char []>(new char[4 * desc.width * desc.height]);
352
353 m_saved_desc = desc;
354
355 return true;
356}
357
358bool state_video_compress_gpujpeg::parse_fmt(char *fmt)
359{
360 if (!fmt || fmt[0] == '\0') {
361 return true;
362 }
363 char *tok, *save_ptr = NULL;
364 int pos = 0;
365 while ((tok = strtok_r(fmt, ":", &save_ptr)) != nullptr) {
366 if (isdigit(tok[0]) && pos == 0) {
367 m_quality = atoi(tok);
368 if (m_quality <= 0 || m_quality > 100) {
369 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Error: Quality should be in interval [1-100]!\n");
370 return false;
371 }
372 } else if (isdigit(tok[0]) && pos == 1) {
373 m_restart_interval = atoi(tok);
374 if (m_restart_interval < 0) {
375 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Error: Restart interval should be non-negative!\n");
376 return false;
377 }
378 } else {
379 if (IS_KEY_PREFIX(tok, "quality")) {
380 m_quality = atoi(strchr(tok, '=') + 1);
381 } else if (IS_KEY_PREFIX(tok, "restart")) {
382 m_restart_interval = atoi(strchr(tok, '=') + 1);
383 } else if (IS_PREFIX(tok, "interleaved")) {
384 m_force_interleaved = true;
385 } else if (strcasecmp(tok, "Y601") == 0) {
386 m_use_internal_codec = GPUJPEG_YCBCR_BT601;
387 } else if (strcasecmp(tok, "Y601full") == 0) {
388 m_use_internal_codec = GPUJPEG_YCBCR_BT601_256LVLS;
389 } else if (strcasecmp(tok, "Y709") == 0) {
390 m_use_internal_codec = GPUJPEG_YCBCR_BT709;
391 } else if (strcasecmp(tok, "RGB") == 0) {
392 m_use_internal_codec = GPUJPEG_RGB;
393 } else if (IS_KEY_PREFIX(tok, "subsampling")) {
394 m_subsampling = subsampling_to_gj(
395 atoi(strchr(tok, '=') + 1));
396 } else if (strcmp(tok, "alpha") == 0) {
397#if GPUJPEG_VERSION_INT < GPUJPEG_MK_VERSION_INT(0, 20, 2)
398 log_msg(LOG_LEVEL_WARNING, MOD_NAME "GPUJPEG v0.20.2 is required for alpha support, %s found.\n",
399 gpujpeg_version_to_string(gpujpeg_version()));
400#endif
401 m_compress_alpha = true;
402 } else {
403 log_msg(LOG_LEVEL_WARNING, MOD_NAME "WARNING: Trailing configuration parameter: %s\n", tok);
404 }
405 }
406 fmt = nullptr;
407 pos += 1;
408 }
409
410 return true;
411}
412
413state_video_compress_gpujpeg::state_video_compress_gpujpeg(struct module *parent, const char *opts) :
414 m_uses_worker_threads{}, m_in_seq{},
415 m_out_seq{}, m_ended_count{},
416 m_module_data{}, m_restart_interval(UNDEF), m_quality(-1)
417{
418 if(opts && opts[0] != '\0') {
419 char *fmt = strdup(opts);
420 if (!parse_fmt(fmt)) {
421 free(fmt);
422 throw 1;
423 }
424 free(fmt);
425 }
426
427 module_init_default(&m_module_data);
428 m_module_data.cls = MODULE_CLASS_DATA;
429 m_module_data.priv_data = this;
430 static auto deleter = [](struct module *mod) {
431 struct state_video_compress_gpujpeg *s = (struct state_video_compress_gpujpeg *) mod->priv_data;
432 delete s;
433 };
434 m_module_data.deleter = deleter;
435
436 module_register(&m_module_data, parent);
437}
438
443state_video_compress_gpujpeg *state_video_compress_gpujpeg::create(struct module *parent, const char *opts) {
444 assert(cuda_devices_count > 0);
445
446 auto ret = new state_video_compress_gpujpeg(parent, opts);
447
448 for (unsigned int i = 0; i < cuda_devices_count; ++i) {
449 ret->m_workers.push_back(new encoder_state(ret, cuda_devices[i]));
450 }
451
452 if (cuda_devices_count > 1) {
453 ret->m_uses_worker_threads = true;
454 }
455
456 if (ret->m_uses_worker_threads) {
457 for (auto worker : ret->m_workers) {
458 worker->m_thread_id = thread(&encoder_state::worker, worker);
459 }
460 }
461
462 return ret;
463}
464
465static const struct {
466 const char *label;
467 const char *key;
468 const char *help_name;
469 const char *description;
470 const char *opt_str;
471 bool is_boolean;
472 const char *placeholder;
473} usage_opts[] = {
474 {"Quality", "quality", "quality",
475 "\t\tJPEG quality coefficient [0..100] - more is better\n", ":q=", false, "80"},
476 {"Restart interval", "restart_interval", "restart_interval",
477 "\t\tInterval between independently entropy encoded block of MCUs,\n"
478 "\t\t0 to disable. Using large intervals or disable (0) slightly\n"
479 "\t\treduces bandwidth at the expense of worse parallelization (if\n"
480 "\t\treset intervals disabled, Huffman encoding is run on CPU). Leave\n"
481 "\t\tuntouched if unsure.\n",
482 ":restart=", false, ""},
483 {"Interleaved", "interleaved", "interleaved",
484 "\t\tForce interleaved encoding (default for YCbCr input formats).\n"
485 "\t\tNon-interleaved has slightly better performance for RGB at the\n"
486 "\t\texpense of worse compatibility. Therefore this option may be\n"
487 "\t\tenabled safely.\n",
488 ":interleaved", true, ""},
489 {"Color space", "color_space", "RGB|Y601|Y601full|Y709",
490 "\t\tforce internal JPEG color space (otherwise source color space is kept).\n",
491 ":", false, "Y601"},
492 {"Subsampling", "subsampling", "sub",
493 "\t\tUse specified JPEG subsampling (444, 422 or 420).\n",
494 ":sub=", false, "422"},
495 {"Alpha", "alpha", "alpha",
496 "\t\tCompress (keep) alpha channel of RGBA.\n",
497 ":alpha", true, ""},
498};
499
500struct module * gpujpeg_compress_init(struct module *parent, const char *opts)
501{
502 if (gpujpeg_version() >> 8 != GPUJPEG_VERSION_INT >> 8) {
503 LOG(LOG_LEVEL_WARNING) << "GPUJPEG API version mismatch! (compiled: " <<
504 gpujpeg_version_to_string(GPUJPEG_VERSION_INT) << ", library present: " <<
505 gpujpeg_version_to_string(gpujpeg_version()) << ", required same minor version)\n";
506 }
507 struct state_video_compress_gpujpeg *s;
508
509 if(opts && strcmp(opts, "help") == 0) {
510 col() << "GPUJPEG comperssion usage:\n";
512 "\t" TBOLD(TRED(
513 "-c gpujpeg")
514 "[:q=<quality>][:r=<restart_interval>][:"
515 "interleaved][:RGB|Y601|Y601full|Y709][:"
516 "subsampling=<sub>][:alpha]\n"));
517 col() << "where\n";
518
519 for(const auto& i : usage_opts){
520 col() << "\t" << TBOLD(<< i.help_name <<) << "\n" << i.description;
521 }
522
523 col() << "\n";
524 return static_cast<module*>(INIT_NOERR);
525 }
526 if (opts && strcmp(opts, "check") == 0) {
527 auto device_info = gpujpeg_get_devices_info();
528 return device_info.device_count == 0 ? nullptr : static_cast<module*>(INIT_NOERR);
529 }
530 if (opts && strcmp(opts, "list_devices") == 0) {
531 printf("CUDA devices:\n");
532#if GPUJPEG_VERSION_INT >= GPUJPEG_MK_VERSION_INT(0, 16, 0)
533 return gpujpeg_print_devices_info() == 0 ? static_cast<module*>(INIT_NOERR) : nullptr;
534#else
535 gpujpeg_print_devices_info();
536 return static_cast<module*>(INIT_NOERR);
537#endif
538 }
539
540 try {
541 s = state_video_compress_gpujpeg::create(parent, opts);
542 } catch (...) {
543 return NULL;
544 }
545
546 return &s->m_module_data;
547}
548
553shared_ptr<video_frame> encoder_state::compress_step(shared_ptr<video_frame> tx)
554{
555 gpujpeg_set_device(m_device_id);
556
557 // first run - initialize device
558 if (!m_encoder) {
559 log_msg(LOG_LEVEL_INFO, "Initializing CUDA device %d...\n", m_device_id);
560 const int ret =
561 gpujpeg_init_device(m_device_id, GPUJPEG_VERBOSE);
562 if(ret != 0) {
563 log_msg(LOG_LEVEL_ERROR, MOD_NAME "initializing CUDA device %d failed.\n", m_device_id);
564 exit_uv(EXIT_FAILURE);
565 return {};
566 }
567 }
568
569 struct video_desc desc = video_desc_from_frame(tx.get());
570
571 // if format has changed, reconfigure
572 if(!video_desc_eq_excl_param(m_saved_desc, desc, PARAM_INTERLACING)) {
573 cleanup_state();
574 int ret = configure_with(desc);
575 if(!ret) {
576 exit_uv(EXIT_FAILURE);
577 return NULL;
578 }
579 }
580
581 shared_ptr<video_frame> out = m_pool.get_frame();
582
583 for (unsigned int x = 0; x < out->tile_count; ++x) {
584 struct tile *in_tile = vf_get_tile(tx.get(), x);
585 struct tile *out_tile = vf_get_tile(out.get(), x);
586 uint8_t *jpeg_enc_input_data;
587
588 if (m_decoder && m_decoder != vc_memcpy) {
589 assert(tx.get()->mem_location == CPU_MEM);
590 unsigned char *line1 = (unsigned char *) in_tile->data;
591 unsigned char *line2 = (unsigned char *) m_decoded.get();
592
593 for (int i = 0; i < (int) in_tile->height; ++i) {
594 m_decoder(line2, line1,
596 m_enc_input_codec),
597 0, 8, 16);
598 line1 += vc_get_linesize(in_tile->width, tx->color_spec);
599 line2 += vc_get_linesize(desc.width, m_enc_input_codec);
600 }
601 jpeg_enc_input_data = (uint8_t *) m_decoded.get();
602 } else {
603 jpeg_enc_input_data = (uint8_t *) in_tile->data;
604 }
605
606 uint8_t *compressed;
607#if GPUJPEG_VERSION_INT < GPUJPEG_MK_VERSION_INT(0, 21, 0)
608 int size;
609#else
610 size_t size = 0;
611#endif
612
613 struct gpujpeg_encoder_input encoder_input;
614 if(tx.get()->mem_location == CUDA_MEM){
615 gpujpeg_encoder_input_set_gpu_image(&encoder_input, jpeg_enc_input_data);
616 } else {
617 gpujpeg_encoder_input_set_image(&encoder_input, jpeg_enc_input_data);
618 }
619
620 const int ret = gpujpeg_encoder_encode(m_encoder, &m_encoder_param, &m_param_image, &encoder_input, &compressed, &size);
621 if(ret != 0) {
622 return {};
623 }
624
625 out_tile->data_len = size;
626 memcpy(out_tile->data, compressed, size);
627 }
628
629 return out;
630}
631
632void encoder_state::cleanup_state()
633{
634 if (m_encoder)
635 gpujpeg_encoder_destroy(m_encoder);
636 m_encoder = NULL;
637}
638
639void state_video_compress_gpujpeg::push(std::shared_ptr<video_frame> in_frame)
640{
641
642 if (in_frame) {
643 in_frame->seq = m_in_seq++;
644 }
645
646 if (!m_uses_worker_threads) {
647 m_workers[0]->compress(std::move(in_frame));
648 return;
649 }
650 if (!in_frame) { // pass poison pill to all workers
651 for (auto *worker : m_workers) {
652 worker->m_in_queue.push({});
653 }
654 return;
655 }
656
657 int index = 0;
658 unique_lock<mutex> lk(m_occupancy_lock);
659 // wait for/select not occupied worker
660 m_worker_finished.wait(lk, [this, &index] {
661 for (auto *worker : m_workers) {
662 if (!worker->m_occupied) {
663 return true;
664 }
665 index++;
666 }
667 return false;
668 });
669 m_workers[index]->m_occupied = true;
670 lk.unlock();
671 m_workers[index]->m_in_queue.push(in_frame);
672}
673
684std::shared_ptr<video_frame> state_video_compress_gpujpeg::pop()
685{
686start:
687 if (m_out_frames.find(m_out_seq) != m_out_frames.end()) {
688 auto frame = m_out_frames[m_out_seq];
689 m_out_frames.erase(m_out_seq);
690 m_out_seq += 1;
691 if (frame->tiles[0].data_len == 0) { // was error processing that frame, skip
692 goto start;
693 } else {
694 return frame;
695 }
696 } else {
697 while (true) {
698 auto frame = m_out_queue.pop();
699 if (!frame) {
700 if (++m_ended_count == m_workers.size()) {
701 return {};
702 } else {
703 continue;
704 }
705 }
706 if (frame->seq == m_out_seq) {
707 m_out_seq += 1;
708 if (frame->tiles[0].data_len == 0) { // error - skip this frame
709 goto start;
710 } else {
711 return frame;
712 }
713 } else {
714 m_out_frames[frame->seq] = frame;
715 }
716 }
717 }
718}
719
720static compress_module_info get_gpujpeg_module_info(){
721 compress_module_info module_info;
722 module_info.name = "gpujpeg";
723
724 for(const auto& opt : usage_opts){
725 std::string desc = opt.description;
726 desc.erase(std::remove(desc.begin(), desc.end(), '\t'), desc.end());
727 std::replace(desc.begin(), desc.end(), '\n', ' ');
728 module_info.opts.emplace_back(
729 module_option{ opt.label, std::move(desc), opt.placeholder, opt.key,
730 opt.opt_str, opt.is_boolean });
731 }
732
734 codec_info.name = "Jpeg";
735 codec_info.priority = 300;
736 codec_info.encoders.emplace_back(encoder{"default", ""});
737
738 module_info.codecs.emplace_back(std::move(codec_info));
739
740 return module_info;
741}
742
743static auto gpujpeg_compress_push(struct module *mod, std::shared_ptr<video_frame> in_frame) {
744 static_cast<struct state_video_compress_gpujpeg *>(mod->priv_data)->push(std::move(in_frame));
745}
746
747static auto gpujpeg_compress_pull (struct module *mod) {
748 return static_cast<struct state_video_compress_gpujpeg *>(mod->priv_data)->pop();
749}
750
751const struct video_compress_info gpujpeg_info = {
752 "GPUJPEG",
753 gpujpeg_compress_init,
754 NULL,
755 NULL,
756 gpujpeg_compress_push,
757 gpujpeg_compress_pull,
758 NULL,
759 NULL,
760 get_gpujpeg_module_info
761};
762
763static auto gpujpeg_compress_init_deprecated(struct module *parent, const char *opts) {
764 log_msg(LOG_LEVEL_WARNING, "Name \"-c JPEG\" deprecated, use \"-c GPUJPEG\" instead.\n");
765 return gpujpeg_compress_init(parent, opts);
766}
767
768const struct video_compress_info deprecated_jpeg_info = {
769 "JPEG",
770 gpujpeg_compress_init_deprecated,
771 NULL,
772 NULL,
773 gpujpeg_compress_push,
774 gpujpeg_compress_pull,
775 NULL,
776 NULL,
777 NULL
778};
779
780
783
784} // end of anonymous namespace
785
struct video_desc desc
struct video_frame * frame
const char * fmt
int void(** deleter)(void *))
struct video_frame * vf_alloc(int count)
Allocates blank video frame.
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_desc video_desc_from_frame(const struct video_frame *frame)
Returns struct video_desc from video frame.
int vc_get_linesize(unsigned int width, codec_t codec)
const char * get_codec_name(codec_t codec)
#define MOD_NAME
Definition audio.cpp:110
static _Bool parse_fmt(const char *cfg, PaTime *latency, int *input_device_idx, char device_name[static STR_LEN])
Definition portaudio.c:126
simple blocking synchronized queue
int color_printf(const char *format,...)
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_FATAL
errors that prevent UG run
Definition debug.h:58
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
static const struct video_decompress_info gpujpeg_info
Definition gpujpeg.c:368
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
@ MODULE_CLASS_DATA
Definition module.h:86
#define VIDEO_COMPRESS_ABI_VERSION
unsigned int cuda_devices_count
Definition host.cpp:168
void exit_uv(int status)
Definition host.cpp:625
unsigned int cuda_devices[MAX_CUDA_DEVICES]
Definition host.cpp:167
#define INIT_NOERR
Definition host.h:73
volatile int log_level
Definition debug.cpp:65
@ LIBRARY_CLASS_VIDEO_COMPRESS
Definition lib_common.h:82
#define REGISTER_HIDDEN_MODULE(name, info, lclass, abi)
Definition lib_common.h:156
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define IF_NOT_UNDEF_ELSE(val, default_val)
Definition macros.h:53
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
Definition macros.h:138
#define UNDEF
Definition macros.h:52
#define IF_NOT_NULL_ELSE(val, default_val)
Definition macros.h:51
#define IS_PREFIX(tok, key)
similar as above, but also key without a value is accepted (value optional)
Definition macros.h:142
decoder_t get_best_decoder_from(codec_t in, const codec_t *out_candidates, codec_t *out)
decoder_func_t * decoder_t
Definition pixfmt_conv.h:96
decoder_func_t vc_memcpy
dummy conversion - ptr to it returned if no conversion needed
static void * worker(void *args)
Definition alsa.c:190
#define max(a, b)
Definition rtp.c:98
static void start()
Definition sdp.c:202
const char * parent
Definition settings.cpp:198
const char * name
displayed name
std::vector< codec > codecs
std::vector< module_option > opts
void * priv_data
Definition module.h:126
Struct tile is an area of video_frame.
Definition types.h:279
unsigned int width
Definition types.h:280
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int data_len
length of the data
Definition types.h:288
unsigned int height
Definition types.h:281
codec_t color_spec
Definition types.h:182
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
std::shared_ptr< video_frame > get_frame()
void reconfigure(struct video_desc new_desc, size_t new_size=SIZE_MAX)
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
uint32_t seq
seq num, used internally by JPEG enc, file cap
Definition types.h:333
static time_ns_t get_time_in_ns()
Definition tv.h:101
@ CUDA_MEM
Definition types.h:299
@ CPU_MEM
Definition types.h:298
subsampling
Definition types.h:132
#define VF_METADATA_SIZE
Definition types.h:348
codec_t
Definition types.h:65
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ JPEG
JPEG image, restart intervals may be used. Compatible with GPUJPEG.
Definition types.h:81
@ I420
planar YCbCr 4:2:0
Definition types.h:97
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
This is an umbrella header for video functions.
bool codec_is_a_rgb(codec_t codec)
Returns TRUE if specified pixelformat is some form of RGB (not YUV).
int get_subsampling(codec_t codec)
int get_bits_per_component(codec_t codec)
static const struct codec_info_t codec_info[]
const char * placeholder
Definition cineform.cpp:128
const char * opt_str
Definition cineform.cpp:127
struct @75 usage_opts[]
static bool configure_with(struct state_video_compress_cineform *s, struct video_desc desc)
Definition cineform.cpp:243
const char * description
Definition cineform.cpp:126
const char * label
Definition cineform.cpp:124
const char * key
Definition cineform.cpp:125
const bool is_boolean
API for video compress drivers.
bool video_desc_eq_excl_param(struct video_desc a, struct video_desc b, unsigned int excluded_params)
Compares two video descriptions.
void vf_store_metadata(const struct video_frame *f, void *s)
struct tile * vf_get_tile(struct video_frame *buf, int pos)
Returns pointer to n-th tile from video frame Equivalent to &video_frame::tiles[pos].
void vf_restore_metadata(struct video_frame *f, void *s)
#define PARAM_INTERLACING
Definition video_frame.h:66