39#include <condition_variable>
60#pragma GCC diagnostic push
61#pragma GCC diagnostic ignored "-Wcast-align"
62#pragma GCC diagnostic ignored "-Wcast-qual"
63#ifdef HAVE_OPENCV2_OPENCV_HPP
64#include <opencv2/opencv.hpp>
66#include <opencv2/core/mat.hpp>
67#include <opencv2/imgproc.hpp>
69#pragma GCC diagnostic pop
77#define MOD_NAME "[conference] "
80using clock = std::chrono::steady_clock;
83using unique_frame = std::unique_ptr<video_frame, frame_deleter>;
88 void set_pos_keep_aspect(
int x,
int y,
int w,
int h);
91 clock::time_point last_time_recieved;
105 frame = std::move(f);
106 last_time_recieved = clock::now();
112void Participant::set_pos_keep_aspect(
int x,
int y,
int w,
int h){
113 double fx =
static_cast<double>(w) / src_w;
114 double fy =
static_cast<double>(h) / src_h;
119 x += (w - width) / 2;
123 y += (h - height) / 2;
130void Participant::to_cv_frame(){
140 luma.create(cv::Size(frame_tile.width, frame_tile.height), CV_8UC1);
141 chroma.create(cv::Size(frame_tile.width / 2, frame_tile.height), CV_8UC2);
143 assert(luma.isContinuous() && chroma.isContinuous());
144 unsigned char *src =
reinterpret_cast<unsigned char *
>(frame_tile.data);
145 unsigned char *luma_dst = luma.ptr(0);
146 unsigned char *chroma_dst = chroma.ptr(0);
148 unsigned src_len = frame_tile.data_len;
151 __m128i uv_shuff = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 14, 12, 10, 8, 6, 4, 2, 0);
152 __m128i y_shuff = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 15, 13, 11, 9, 7, 5, 3, 1);
153 while(src_len >= 32){
154 __m128i uyvy = _mm_lddqu_si128((__m128i *)(
void *) src);
157 __m128i y_low = _mm_shuffle_epi8(uyvy, y_shuff);
158 __m128i uv_low = _mm_shuffle_epi8(uyvy, uv_shuff);
160 uyvy = _mm_lddqu_si128((__m128i *)(
void *) src);
163 __m128i y_high = _mm_shuffle_epi8(uyvy, y_shuff);
164 __m128i uv_high = _mm_shuffle_epi8(uyvy, uv_shuff);
166 _mm_store_si128((__m128i *)(
void *) luma_dst, _mm_or_si128(y_low, _mm_bslli_si128(y_high, 8)));
168 _mm_store_si128((__m128i *)(
void *) chroma_dst, _mm_or_si128(uv_low, _mm_bslli_si128(uv_high, 8)));
176 *chroma_dst++ = *src++;
177 *luma_dst++ = *src++;
178 *chroma_dst++ = *src++;
179 *luma_dst++ = *src++;
187 enum class Layout{ Invalid, Tiled, One_big };
189 Video_mixer(
int width,
int height, Layout l = Layout::Tiled);
194 void set_layout(Layout new_layout) { layout = new_layout; recompute_layout(); }
195 Layout get_layout()
const {
return layout; }
197 void set_primary_ssrc(uint32_t ssrc) { primary_ssrc = ssrc; recompute_layout(); }
198 uint32_t get_primary_ssrc()
const {
return primary_ssrc; }
200 std::vector<uint32_t> get_participant_ssrc_list()
const;
203 void recompute_layout();
205 void one_big_layout();
209 Layout layout = Layout::Tiled;
211 uint32_t primary_ssrc = 0;
214 cv::Mat mixed_chroma;
216 std::map<uint32_t, Participant> participants;
219Video_mixer::Video_mixer(
int width,
int height, Layout layout):
224 mixed_luma.create(cv::Size(width, height), CV_8UC1);
225 mixed_chroma.create(cv::Size(width / 2, height), CV_8UC2);
228void Video_mixer::tiled_layout(){
229 const unsigned rows = (unsigned) ceil(sqrt(participants.size()));
230 const unsigned tile_width = width / rows;
231 const unsigned tile_height = height / rows;
234 for(
auto& [ssrc, t]: participants){
236 t.set_pos_keep_aspect((pos % rows) * tile_width,
237 (pos / rows) * tile_height,
238 tile_width, tile_height);
244void Video_mixer::one_big_layout(){
245 const unsigned small_height = height / 4;
246 const unsigned big_height = small_height * 3;
247 const unsigned tile_width = width / 4;
250 for(
auto& [ssrc, t]: participants){
251 if(ssrc == primary_ssrc){
252 t.set_pos_keep_aspect(0, 0, width, big_height);
256 if(pos * tile_width > width)
259 t.set_pos_keep_aspect(pos * tile_width, big_height, tile_width, small_height);
264void Video_mixer::recompute_layout(){
265 mixed_luma.setTo(16);
266 mixed_chroma.setTo(128);
268 if(!primary_ssrc && !participants.empty()){
269 primary_ssrc = participants.begin()->first;
272 if(participants.size() == 1){
273 participants.begin()->second.set_pos_keep_aspect(0, 0, width, height);
281 case Layout::One_big:
284 case Layout::Invalid:
285 assert(
"Invalid layout" &&
false);
291 auto iter = participants.find(f->ssrc);
292 auto& p = participants[f->ssrc];
293 p.frame_recieved(std::move(f));
295 if(iter == participants.end()){
303 auto now = clock::now();
305 bool recompute =
false;
306 for(
auto it = participants.begin(); it != participants.end();){
307 auto& p = it->second;
308 if(now - p.last_time_recieved > std::chrono::seconds(2)){
309 if(it->first == primary_ssrc)
312 it = participants.erase(it);
321 for(
auto&& [ssrc, p] : participants){
326 cv::Size l_size(p.width, p.height);
327 cv::Size c_size(p.width / 2, p.height);
328 cv::resize(p.luma, mixed_luma(cv::Rect(p.x, p.y, p.width, p.height)), l_size, 0, 0);
329 cv::resize(p.chroma, mixed_chroma(cv::Rect(p.x / 2, p.y, p.width / 2, p.height)), c_size, 0, 0);
333 unsigned char *dst =
reinterpret_cast<unsigned char *
>(result->
tiles[0].
data);
334 unsigned char *chroma_src = mixed_chroma.ptr(0);
335 unsigned char *luma_src = mixed_luma.ptr(0);
336 assert(mixed_luma.isContinuous() && mixed_chroma.isContinuous());
341 __m128i y_shuff = _mm_set_epi8(7, -1, 6, -1, 5, -1, 4, -1, 3, -1, 2, -1, 1, -1, 0, -1);
342 __m128i uv_shuff = _mm_set_epi8(-1, 7, -1, 6, -1, 5, -1, 4, -1, 3, -1, 2, -1, 1, -1, 0);
343 while(dst_len >= 32){
344 __m128i luma = _mm_load_si128((__m128i
const*)(
const void *) luma_src);
346 __m128i chroma = _mm_load_si128((__m128i
const*)(
const void *) chroma_src);
349 __m128i res = _mm_or_si128(_mm_shuffle_epi8(luma, y_shuff), _mm_shuffle_epi8(chroma, uv_shuff));
350 _mm_storeu_si128((__m128i *)(
void *) dst, res);
353 luma = _mm_bsrli_si128(luma, 8);
354 chroma = _mm_bsrli_si128(chroma, 8);
356 res = _mm_or_si128(_mm_shuffle_epi8(luma, y_shuff), _mm_shuffle_epi8(chroma, uv_shuff));
357 _mm_storeu_si128((__m128i *)(
void *) dst, res);
365 *dst++ = *chroma_src++;
366 *dst++ = *luma_src++;
367 *dst++ = *chroma_src++;
368 *dst++ = *luma_src++;
374std::vector<uint32_t> Video_mixer::get_participant_ssrc_list()
const{
375 std::vector<uint32_t> ret;
376 ret.reserve(participants.size());
377 for(
const auto& i : participants){
378 ret.push_back(i.first);
384using unique_disp = std::unique_ptr<display, display_deleter>;
386struct {
const char *
name; Video_mixer::Layout layout; } layout_name_map[] = {
387 {
"tiled", Video_mixer::Layout::Tiled},
388 {
"one_big", Video_mixer::Layout::One_big},
391Video_mixer::Layout layout_from_name(std::string_view
name){
392 for(
const auto& i : layout_name_map){
396 return Video_mixer::Layout::Invalid;
399const char *layout_get_name(Video_mixer::Layout layout){
400 for(
const auto& i : layout_name_map){
401 if(layout == i.layout)
404 return "invalid_layout";
422 Video_mixer::Layout layout = Video_mixer::Layout::Tiled;
434 std::shared_ptr<state_conference_common>
common;
442 char fmt[2 +
sizeof(
void *) * 2 + 1] =
"";
446 "conference",
fmt, 0, NULL, &out);
447 if (rc == 0)
return out;
else return NULL;
453 if (from_reflector) {
457 "-r <width>:<height>[:<fps>]")
461 printf(
"Conference display\n");
463 printf(
"\t-d conference:<display_config>#<width>:<height>:[fps]:[layout]\n");
468 auto s = std::make_unique<state_conference>();
476 if (strcmp(
fmt,
"reflhelp") == 0) {
481 if (isdigit(
fmt[0])){
483 sscanf(
fmt,
"%p", &orig);
488 std::string requested_display =
"gl";
489 std::string disp_conf;
491 s->common = std::make_shared<state_conference_common>(
parent);
493 auto&
desc = s->common->desc;
503#define FAIL_IF(x, msg) \
506 log_msg(LOG_LEVEL_ERROR, msg);\
512 requested_display =
tokenize(disp_cfg,
':');
513 FAIL_IF(requested_display.empty(),
"Requested display cannot be empty\n");
514 disp_conf =
tokenize(disp_cfg,
':');
524 if(tok ==
"one_big"){
525 s->common->layout = Video_mixer::Layout::One_big;
550 Video_mixer mixer(s->desc.width, s->desc.height, s->layout);
558 auto next_frame_time = clock::now() + std::chrono::hours(1);
559 auto last_frame_time = clock::time_point::min();
564 std::string_view msg_text = msg_univ->
text;
570 if (
key ==
"layout") {
571 Video_mixer::Layout new_layout = layout_from_name(
val);
573 if(new_layout == Video_mixer::Layout::Invalid){
578 mixer.set_layout(new_layout);
582 }
else if (
key ==
"primary_ssrc"){
583 uint32_t parsed_ssrc = 0;
589 mixer.set_primary_ssrc(parsed_ssrc);
593 }
else if (
key ==
"info"){
594 auto ssrcs = mixer.get_participant_ssrc_list();
597 for(
const auto& p : ssrcs){
609 auto wait_p = [s]{
return !s->incoming_frames.empty(); };
610 std::unique_lock<std::mutex>
lock(s->incoming_frames_lock);
611 bool have_frame = s->new_incoming_frame_cv.wait_until(
lock, next_frame_time, wait_p);
613 frame = std::move(s->incoming_frames.front());
614 s->incoming_frames.pop();
619 s->incoming_frame_consumed.notify_one();
625 s->desc.fps = std::max(s->desc.fps,
frame->
fps);
627 mixer.process_frame(std::move(
frame));
630 auto now = clock::now();
631 if(next_frame_time <= now){
635 mixer.get_mixed(disp_frame);
639 last_frame_time = next_frame_time;
640 next_frame_time = now + std::chrono::hours(1);
644 using namespace std::chrono_literals;
645 next_frame_time = last_frame_time + std::chrono::duration_cast<clock::duration>(1s / s->desc.fps);
646 if(next_frame_time <= now){
647 next_frame_time = now;
738 std::unique_lock<std::mutex> lg(s->incoming_frames_lock);
746 s->incoming_frame_consumed.wait(lg,
750 s->incoming_frames.emplace(
frame);
752 s->new_incoming_frame_cv.notify_one();
760 *available_cards =
nullptr;
struct video_frame * frame
const char unsigned int flags
int void(** deleter)(void *))
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
static constexpr unsigned int IN_QUEUE_MAX_BUFFER_LEN
static constexpr chrono::milliseconds SOURCE_TIMEOUT(500)
static void check_reconf(struct state_blend_common *s, struct video_desc desc)
int color_printf(const char *format,...)
static void display_conference_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
static struct video_frame * display_conference_getf(void *state)
static bool display_conference_get_property(void *state, int property, void *val, size_t *len)
static bool display_conference_putf(void *state, struct video_frame *frame, long long flags)
static bool display_conference_reconfigure_audio(void *state, int quant_samples, int channels, int sample_rate)
static void display_conference_run(void *state)
static struct display * display_conference_fork(void *state)
static const struct video_display_info display_conference_info
static void display_conference_put_audio_frame(void *state, const struct audio_frame *frame)
static void display_conference_worker(std::shared_ptr< state_conference_common > s)
static void * display_conference_init(struct module *parent, const char *fmt, unsigned int flags)
static void display_conference_done(void *state)
static bool display_conference_reconfigure(void *state, struct video_desc desc)
void log_msg(int level, const char *format,...)
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_VERBOSE
#define LOG_LEVEL_WARNING
less severe errors
bool display_put_frame(struct display *d, struct video_frame *frame, long long timeout_ns)
Puts filled video frame. After calling this function, video frame cannot be used.
void display_run_mainloop(struct display *d)
Display mainloop function.
bool display_ctl_property(struct display *d, int property, void *val, size_t *len)
Gets property from video display.
#define VIDEO_DISPLAY_ABI_VERSION
#define DISPLAY_NO_GENERIC_FPS_INDICATOR
void display_done(struct display *d)
This function performs cleanup after done.
int initialize_video_display(struct module *parent, const char *requested_display, const char *fmt, unsigned int flags, const char *postprocess, struct display **out)
Initializes video display.
struct video_frame * display_get_frame(struct display *d)
Returns video framebuffer which will be written to.
@ DISPLAY_PROPERTY_SUPPORTS_MULTI_SOURCES
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
@ LIBRARY_CLASS_VIDEO_DISPLAY
#define REGISTER_HIDDEN_MODULE(name, info, lclass, abi)
void free_message(struct message *msg, struct response *r)
struct response * new_response(int status, const char *text)
struct message * check_message(struct module *mod)
#define RESPONSE_BAD_REQUEST
static void * worker(void *args)
#define PROFILE_DETAIL(name)
std::string_view tokenize(std::string_view &str, char delim, char quot)
Tokenizer for string_view.
bool parse_num(std::string_view sv, T &res, int base=10)
struct used to comunicate with audio capture/playback device
This struct represents initialized video display state.
void operator()(video_frame *f)
struct video_desc display_desc
std::queue< unique_frame > incoming_frames
std::condition_variable incoming_frame_consumed
std::condition_variable new_incoming_frame_cv
state_conference_common(struct module *parent)
std::mutex incoming_frames_lock
std::shared_ptr< state_conference_common > common
char * data
Raw tile data. Pointer must be at least 4B aligned.
unsigned int data_len
length of the data
enum interlacing_t interlacing
unsigned int width
width of every tile (!)
unsigned int height
height of every tile (!)
Struct video_frame represents a video frame and contains video description.
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
@ VIDEO_NORMAL
normal video (one tile)
@ PROGRESSIVE
progressive frame
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
This is an umbrella header for video functions.
std::unique_ptr< video_frame, frame_deleter > unique_frame
bool display_reconfigure(struct display *d, struct video_desc desc, enum video_mode video_mode)
Reconfigure display to new video format.
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.