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>
64#ifndef GPUJPEG_VERSION_INT
65#error "Old GPUJPEG API detected!"
67#if GPUJPEG_VERSION_INT >= GPUJPEG_MK_VERSION_INT(0, 25, 0)
68#define NEW_PARAM_IMG_NO_COMP_COUNT
70typedef int gpujpeg_sampling_factor_t;
73#define MOD_NAME "[GPUJPEG enc.] "
75using std::condition_variable;
82using std::unique_lock;
87struct state_video_compress_gpujpeg;
95 shared_ptr<video_frame> compress_step(shared_ptr<video_frame>
frame);
98 struct state_video_compress_gpujpeg *m_parent_state;
100 struct gpujpeg_encoder *m_encoder;
105 unique_ptr<char []> m_decoded;
107 struct gpujpeg_parameters m_encoder_param{};
108 struct gpujpeg_image_parameters m_param_image{};
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{}
119 void compress(shared_ptr<video_frame>
frame);
126struct state_video_compress_gpujpeg {
128 state_video_compress_gpujpeg(
struct module *
parent,
const char *opts);
130 vector<struct encoder_state *> m_workers;
131 bool m_uses_worker_threads;
133 map<uint32_t, shared_ptr<struct video_frame>> m_out_frames;
137 size_t m_ended_count;
140 ~state_video_compress_gpujpeg() {
141 if (m_uses_worker_threads) {
142 for (
auto worker : m_workers) {
143 worker->m_thread_id.join();
147 for (
auto worker : m_workers) {
151 static state_video_compress_gpujpeg *create(
struct module *
parent,
const char *opts);
153 void push(std::shared_ptr<video_frame> in_frame);
154 std::shared_ptr<video_frame> pop();
156 struct module m_module_data;
157 int m_restart_interval;
159 bool m_force_interleaved =
false;
160 bool m_compress_alpha =
false;
161 gpujpeg_sampling_factor_t m_subsampling = 0;
162 enum gpujpeg_color_space m_use_internal_codec = GPUJPEG_NONE;
165 mutex m_occupancy_lock;
166 condition_variable m_worker_finished;
178void encoder_state::compress(shared_ptr<video_frame>
frame)
183 auto out = compress_step(std::move(
frame));
192 m_parent_state->m_out_queue.push(out);
194 m_parent_state->m_out_queue.push({});
202void encoder_state::worker() {
204 auto frame = m_in_queue.pop();
207 m_parent_state->m_out_queue.push(
frame);
211 compress(std::move(
frame));
213 unique_lock<mutex> lk(m_parent_state->m_occupancy_lock);
216 m_parent_state->m_worker_finished.notify_one();
231gpujpeg_sampling_factor_t
232subsampling_to_gj(
int ug_subs)
236 return GPUJPEG_SUBSAMPLING_444;
238 return GPUJPEG_SUBSAMPLING_422;
240 return GPUJPEG_SUBSAMPLING_420;
252 compressed_desc =
desc;
269 " to 8 bits. You may directly capture 8-bit signal to improve performance.\n";
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;
277 m_encoder_param.quality);
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
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;
292 ? GPUJPEG_RGB : GPUJPEG_YCBCR_BT709);
294 gpujpeg_image_set_default_parameters(&m_param_image);
300#if !defined NEW_PARAM_IMG_NO_COMP_COUNT
301 m_param_image.comp_count = 3;
303 if (m_parent_state->m_compress_alpha) {
305#ifdef NEW_PARAM_IMG_NO_COMP_COUNT
306 gpujpeg_parameters_chroma_subsampling(
307 &m_encoder_param, GPUJPEG_SUBSAMPLING_4444);
309 m_param_image.comp_count = 4;
316 m_param_image.color_space =
codec_is_a_rgb(m_enc_input_codec) ? GPUJPEG_RGB : GPUJPEG_YCBCR_BT709;
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;
324 case RGBA: m_param_image.pixel_format = GPUJPEG_444_U8_P012Z;
break;
326 case UYVY: m_param_image.pixel_format = GPUJPEG_422_U8_P1020;
break;
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)
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));
340 m_encoder = gpujpeg_encoder_create(NULL);
358bool state_video_compress_gpujpeg::parse_fmt(
char *
fmt)
360 if (!
fmt ||
fmt[0] ==
'\0') {
363 char *tok, *save_ptr = NULL;
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) {
372 }
else if (isdigit(tok[0]) && pos == 1) {
373 m_restart_interval = atoi(tok);
374 if (m_restart_interval < 0) {
380 m_quality = atoi(strchr(tok,
'=') + 1);
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;
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)
399 gpujpeg_version_to_string(gpujpeg_version()));
401 m_compress_alpha =
true;
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)
418 if(opts && opts[0] !=
'\0') {
419 char *
fmt = strdup(opts);
429 m_module_data.priv_data =
this;
431 struct state_video_compress_gpujpeg *s = (
struct state_video_compress_gpujpeg *) mod->priv_data;
434 m_module_data.deleter =
deleter;
443state_video_compress_gpujpeg *state_video_compress_gpujpeg::create(
struct module *
parent,
const char *opts) {
446 auto ret =
new state_video_compress_gpujpeg(
parent, opts);
449 ret->m_workers.push_back(
new encoder_state(ret,
cuda_devices[i]));
453 ret->m_uses_worker_threads =
true;
456 if (ret->m_uses_worker_threads) {
457 for (
auto worker : ret->m_workers) {
458 worker->m_thread_id = thread(&encoder_state::worker,
worker);
468 const char *help_name;
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",
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",
502 if (gpujpeg_version() >> 8 != GPUJPEG_VERSION_INT >> 8) {
504 gpujpeg_version_to_string(GPUJPEG_VERSION_INT) <<
", library present: " <<
505 gpujpeg_version_to_string(gpujpeg_version()) <<
", required same minor version)\n";
507 struct state_video_compress_gpujpeg *s;
509 if(opts && strcmp(opts,
"help") == 0) {
510 col() <<
"GPUJPEG comperssion usage:\n";
514 "[:q=<quality>][:r=<restart_interval>][:"
515 "interleaved][:RGB|Y601|Y601full|Y709][:"
516 "subsampling=<sub>][:alpha]\n"));
520 col() <<
"\t" <<
TBOLD(<< i.help_name <<) <<
"\n" << i.description;
526 if (opts && strcmp(opts,
"check") == 0) {
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;
535 gpujpeg_print_devices_info();
541 s = state_video_compress_gpujpeg::create(
parent, opts);
546 return &s->m_module_data;
553shared_ptr<video_frame> encoder_state::compress_step(shared_ptr<video_frame>
tx)
555 gpujpeg_set_device(m_device_id);
561 gpujpeg_init_device(m_device_id, GPUJPEG_VERBOSE);
581 shared_ptr<video_frame> out = m_pool.
get_frame();
583 for (
unsigned int x = 0; x < out->tile_count; ++x) {
586 uint8_t *jpeg_enc_input_data;
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();
593 for (
int i = 0; i < (int) in_tile->
height; ++i) {
594 m_decoder(line2, line1,
601 jpeg_enc_input_data = (uint8_t *) m_decoded.get();
603 jpeg_enc_input_data = (uint8_t *) in_tile->
data;
607#if GPUJPEG_VERSION_INT < GPUJPEG_MK_VERSION_INT(0, 21, 0)
613 struct gpujpeg_encoder_input encoder_input;
615 gpujpeg_encoder_input_set_gpu_image(&encoder_input, jpeg_enc_input_data);
617 gpujpeg_encoder_input_set_image(&encoder_input, jpeg_enc_input_data);
620 const int ret = gpujpeg_encoder_encode(m_encoder, &m_encoder_param, &m_param_image, &encoder_input, &compressed, &size);
626 memcpy(out_tile->
data, compressed, size);
632void encoder_state::cleanup_state()
635 gpujpeg_encoder_destroy(m_encoder);
639void state_video_compress_gpujpeg::push(std::shared_ptr<video_frame> in_frame)
643 in_frame->seq = m_in_seq++;
646 if (!m_uses_worker_threads) {
647 m_workers[0]->compress(std::move(in_frame));
651 for (
auto *
worker : m_workers) {
652 worker->m_in_queue.push({});
658 unique_lock<mutex> lk(m_occupancy_lock);
660 m_worker_finished.wait(lk, [
this, &index] {
661 for (
auto *
worker : m_workers) {
662 if (!
worker->m_occupied) {
669 m_workers[index]->m_occupied =
true;
671 m_workers[index]->m_in_queue.push(in_frame);
684std::shared_ptr<video_frame> state_video_compress_gpujpeg::pop()
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);
698 auto frame = m_out_queue.pop();
700 if (++m_ended_count == m_workers.size()) {
722 module_info.
name =
"gpujpeg";
725 std::string
desc = opt.description;
727 std::replace(
desc.begin(),
desc.end(),
'\n',
' ');
728 module_info.
opts.emplace_back(
730 opt.opt_str, opt.is_boolean });
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));
747static auto gpujpeg_compress_pull (
struct module *mod) {
748 return static_cast<struct state_video_compress_gpujpeg *
>(mod->
priv_data)->pop();
753 gpujpeg_compress_init,
756 gpujpeg_compress_push,
757 gpujpeg_compress_pull,
760 get_gpujpeg_module_info
763static auto gpujpeg_compress_init_deprecated(
struct module *
parent,
const char *opts) {
765 return gpujpeg_compress_init(
parent, opts);
770 gpujpeg_compress_init_deprecated,
773 gpujpeg_compress_push,
774 gpujpeg_compress_pull,
struct video_frame * frame
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)
static _Bool parse_fmt(const char *cfg, PaTime *latency, int *input_device_idx, char device_name[static STR_LEN])
simple blocking synchronized queue
int color_printf(const char *format,...)
void log_msg(int level, const char *format,...)
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_INFO
normal reporting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_FATAL
errors that prevent UG run
#define LOG_LEVEL_WARNING
less severe errors
static const struct video_decompress_info gpujpeg_info
void module_init_default(struct module *module_data)
void module_register(struct module *module_data, struct module *parent)
#define VIDEO_COMPRESS_ABI_VERSION
unsigned int cuda_devices_count
unsigned int cuda_devices[MAX_CUDA_DEVICES]
@ LIBRARY_CLASS_VIDEO_COMPRESS
#define REGISTER_HIDDEN_MODULE(name, info, lclass, abi)
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
#define IF_NOT_UNDEF_ELSE(val, default_val)
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
#define IF_NOT_NULL_ELSE(val, default_val)
#define IS_PREFIX(tok, key)
similar as above, but also key without a value is accepted (value optional)
decoder_t get_best_decoder_from(codec_t in, const codec_t *out_candidates, codec_t *out)
decoder_func_t * decoder_t
decoder_func_t vc_memcpy
dummy conversion - ptr to it returned if no conversion needed
static void * worker(void *args)
const char * name
displayed name
std::vector< codec > codecs
std::vector< module_option > opts
Struct tile is an area of video_frame.
char * data
Raw tile data. Pointer must be at least 4B aligned.
unsigned int data_len
length of the data
unsigned int width
width of every tile (!)
unsigned int height
height of every tile (!)
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]
uint32_t seq
seq num, used internally by JPEG enc, file cap
static time_ns_t get_time_in_ns()
@ RGB
RGB 8-bit (packed into 24-bit word)
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
@ JPEG
JPEG image, restart intervals may be used. Compatible with GPUJPEG.
@ RGBA
RGBA 8-bit, big-endian.
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[]
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