New ScreenCapture separate from Device, with Window selection

For now only LINUX support for window selection. New icons for Loopback and for ScreenCapture. Important BugFix on DeviceSource and Device management.
This commit is contained in:
Bruno Herbelin
2023-08-15 22:26:42 +02:00
parent bb4e81b00a
commit a033b74f7f
28 changed files with 2094 additions and 371 deletions

View File

@@ -89,6 +89,11 @@ if(UNIX)
find_package(GTK 3.0 REQUIRED)
macro_log_feature(GTK_FOUND "GTK" "GTK cross-platform widget toolkit" "http://www.gtk.org" TRUE)
find_package(X11 REQUIRED COMPONENTS xcb)
include_directories(
${X11_INCLUDE_DIR}
)
endif()
add_definitions(-DUNIX)
elseif(WIN32)

Binary file not shown.

View File

@@ -1,40 +1,32 @@
ply
format ascii 1.0
comment Created by Blender 2.91.2 - www.blender.org
element vertex 14
comment Created by Blender 2.90.1 - www.blender.org
element vertex 12
property float x
property float y
property float z
element face 16
element face 10
property list uchar uint vertex_indices
end_header
-0.100035 0.057779 0.000000
-0.002114 0.090894 0.000000
-0.002069 0.111204 0.000000
0.095897 0.057200 0.000000
-0.072133 0.052704 0.000000
0.068377 0.052027 0.000000
-0.100035 -0.050237 0.000000
0.078085 0.037024 0.000000
0.095897 -0.050237 0.000000
-0.002025 0.014469 0.000000
0.006837 -0.000988 0.000000
0.078085 -0.039552 0.000000
0.006837 -0.077564 0.000000
-0.002069 -0.102495 0.000000
-0.120212 -0.072047 0.000000
-0.101239 0.089191 0.000000
-0.120212 0.108160 0.000000
0.119230 0.108160 0.000000
0.100257 0.089191 0.000000
0.119230 -0.072047 0.000000
-0.101239 -0.053078 0.000000
0.100257 -0.053078 0.000000
-0.057945 -0.110557 0.000000
0.031238 -0.082104 0.000000
-0.033289 -0.082104 0.000000
0.055894 -0.110557 0.000000
3 0 1 2
3 1 3 2
3 0 4 1
3 5 3 1
3 6 4 0
3 5 7 3
3 7 8 3
3 6 9 4
3 9 7 5
3 9 10 7
3 11 8 7
3 6 10 9
3 6 12 10
3 12 8 11
3 13 12 6
3 12 13 8
3 1 4 3
3 4 5 3
3 0 6 1
3 7 5 4
3 0 7 6
3 0 5 7
3 8 9 10
3 8 11 9

View File

@@ -1,32 +1,788 @@
ply
format ascii 1.0
comment Created by Blender 2.90.1 - www.blender.org
element vertex 12
comment Created by Blender 3.4.1 - www.blender.org
element vertex 391
property float x
property float y
property float z
element face 10
element face 387
property list uchar uint vertex_indices
end_header
-0.120212 -0.072047 0.000000
-0.101239 0.089191 0.000000
-0.120212 0.108160 0.000000
0.119230 0.108160 0.000000
0.100257 0.089191 0.000000
0.119230 -0.072047 0.000000
-0.101239 -0.053078 0.000000
0.100257 -0.053078 0.000000
-0.057945 -0.110557 0.000000
0.031238 -0.082104 0.000000
-0.033289 -0.082104 0.000000
0.055894 -0.110557 0.000000
-0.074560 0.075085 0.000000
-0.072225 0.075085 0.000000
-0.073393 0.075159 0.000000
-0.075712 0.074862 0.000000
-0.071073 0.074862 0.000000
-0.076833 0.074490 0.000000
-0.069952 0.074490 0.000000
-0.077908 0.073969 0.000000
-0.068877 0.073969 0.000000
-0.078921 0.073300 0.000000
-0.067864 0.073300 0.000000
-0.079857 0.072482 0.000000
-0.066927 0.072482 0.000000
-0.080612 0.071727 0.000000
-0.066173 0.071727 0.000000
-0.064086 0.069641 0.000000
-0.082699 0.069641 0.000000
-0.060934 0.066490 0.000000
-0.085851 0.066490 0.000000
-0.056983 0.062540 0.000000
-0.089802 0.062540 0.000000
-0.052499 0.058057 0.000000
-0.094286 0.058057 0.000000
-0.018788 0.061512 0.000000
-0.015014 0.061709 0.000000
-0.016840 0.061709 0.000000
-0.009967 0.061709 0.000000
-0.002343 0.061709 0.000000
0.007215 0.061709 0.000000
0.018060 0.061709 0.000000
0.029551 0.061709 0.000000
0.041041 0.061709 0.000000
0.051887 0.061709 0.000000
0.061444 0.061709 0.000000
0.069068 0.061709 0.000000
0.074115 0.061709 0.000000
0.075941 0.061709 0.000000
0.077182 0.061626 0.000000
0.078372 0.061383 0.000000
-0.020547 0.060953 0.000000
0.079500 0.060991 0.000000
0.080556 0.060461 0.000000
-0.022096 0.060081 0.000000
0.081528 0.059805 0.000000
-0.023416 0.058943 0.000000
0.082406 0.059032 0.000000
0.083179 0.058154 0.000000
-0.024486 0.057589 0.000000
0.083836 0.057182 0.000000
-0.047749 0.053308 0.000000
-0.099036 0.053308 0.000000
-0.025287 0.056066 0.000000
0.084365 0.056126 0.000000
0.084757 0.054998 0.000000
-0.025798 0.054424 0.000000
0.085000 0.053809 0.000000
-0.025999 0.052710 0.000000
0.085084 0.052568 0.000000
-0.042999 0.048559 0.000000
-0.103786 0.048558 0.000000
-0.025870 0.050972 0.000000
0.085084 0.050904 0.000000
-0.025392 0.049260 0.000000
0.085084 0.046302 0.000000
-0.024543 0.047621 0.000000
-0.038515 0.044076 0.000000
-0.108270 0.044075 0.000000
-0.023305 0.046104 0.000000
0.085084 0.039350 0.000000
-0.023185 0.045985 0.000000
-0.022853 0.045653 0.000000
-0.022353 0.045152 0.000000
-0.021725 0.044525 0.000000
-0.021012 0.043812 0.000000
-0.034564 0.040126 0.000000
-0.112221 0.040125 0.000000
-0.020257 0.043057 0.000000
-0.019502 0.042303 0.000000
-0.018790 0.041590 0.000000
-0.018162 0.040963 0.000000
-0.017661 0.040462 0.000000
-0.017330 0.040130 0.000000
-0.017210 0.040010 0.000000
-0.031412 0.036974 0.000000
-0.115373 0.036974 0.000000
-0.016769 0.039598 0.000000
-0.016304 0.039217 0.000000
0.060703 0.037334 0.000000
0.085084 0.030635 0.000000
-0.015817 0.038869 0.000000
-0.015310 0.038555 0.000000
-0.014785 0.038275 0.000000
-0.014243 0.038030 0.000000
-0.013687 0.037820 0.000000
-0.013117 0.037647 0.000000
-0.012536 0.037511 0.000000
-0.011946 0.037413 0.000000
-0.011348 0.037354 0.000000
-0.010745 0.037334 0.000000
0.060703 -0.032030 0.000000
-0.029326 0.034888 0.000000
-0.117460 0.034888 0.000000
-0.028571 0.034134 0.000000
-0.118214 0.034133 0.000000
-0.061202 0.033570 0.000000
-0.027753 0.033198 0.000000
-0.119033 0.033197 0.000000
-0.085583 0.033570 0.000000
-0.061202 -0.035794 0.000000
-0.045806 0.017272 0.000000
-0.085886 0.033249 0.000000
-0.085583 -0.051029 0.000000
-0.086724 0.032362 0.000000
-0.027083 0.032185 0.000000
-0.119702 0.032184 0.000000
-0.087989 0.031023 0.000000
-0.026562 0.031110 0.000000
-0.120223 0.031109 0.000000
-0.026190 0.029989 0.000000
-0.120595 0.029988 0.000000
-0.089575 0.029344 0.000000
0.085084 0.020746 0.000000
-0.025967 0.028838 0.000000
-0.120818 0.028837 0.000000
-0.091375 0.027439 0.000000
-0.025893 0.027670 0.000000
-0.120892 0.027670 0.000000
-0.025967 0.026503 0.000000
-0.120818 0.026502 0.000000
-0.093281 0.025420 0.000000
-0.026190 0.025352 0.000000
-0.120595 0.025351 0.000000
-0.095188 0.023402 0.000000
-0.026562 0.024231 0.000000
-0.120223 0.024230 0.000000
-0.027083 0.023156 0.000000
-0.119702 0.023155 0.000000
-0.096988 0.021497 0.000000
-0.027753 0.022143 0.000000
-0.119033 0.022142 0.000000
-0.028571 0.021207 0.000000
-0.118214 0.021206 0.000000
-0.098574 0.019818 0.000000
-0.028652 0.021126 0.000000
-0.118133 0.021125 0.000000
-0.028876 0.020901 0.000000
-0.117909 0.020901 0.000000
-0.029215 0.020563 0.000000
-0.117570 0.020562 0.000000
0.085084 0.010269 0.000000
-0.029640 0.020138 0.000000
-0.117146 0.020137 0.000000
-0.030122 0.019656 0.000000
-0.116664 0.019656 0.000000
-0.099839 0.018478 0.000000
-0.030632 0.019146 0.000000
-0.116153 0.019145 0.000000
-0.031143 0.018635 0.000000
-0.115643 0.018635 0.000000
-0.031625 0.018153 0.000000
-0.115161 0.018153 0.000000
-0.100677 0.017592 0.000000
-0.032049 0.017729 0.000000
-0.114736 0.017728 0.000000
-0.032388 0.017390 0.000000
-0.114397 0.017390 0.000000
-0.100980 0.017271 0.000000
-0.032612 0.017166 0.000000
-0.114173 0.017165 0.000000
-0.044874 0.016412 0.000000
-0.101911 0.016411 0.000000
-0.032693 0.017085 0.000000
-0.114092 0.017084 0.000000
-0.033649 0.016252 0.000000
-0.113136 0.016251 0.000000
-0.043859 0.015705 0.000000
-0.102926 0.015704 0.000000
-0.034684 0.015574 0.000000
-0.112101 0.015574 0.000000
-0.042777 0.015151 0.000000
-0.104009 0.015150 0.000000
-0.035782 0.015051 0.000000
-0.111004 0.015050 0.000000
-0.041643 0.014750 0.000000
-0.105142 0.014749 0.000000
-0.036926 0.014682 0.000000
-0.109859 0.014682 0.000000
-0.040475 0.014502 0.000000
-0.106310 0.014502 0.000000
-0.038100 0.014468 0.000000
-0.108685 0.014468 0.000000
-0.039289 0.014408 0.000000
-0.107496 0.014408 0.000000
0.085084 -0.000208 0.000000
0.085084 -0.010097 0.000000
0.085084 -0.018812 0.000000
0.037601 -0.012928 0.000000
0.039977 -0.012962 0.000000
0.038790 -0.012868 0.000000
0.105810 -0.012963 0.000000
0.108185 -0.012929 0.000000
0.106997 -0.012869 0.000000
0.036427 -0.013142 0.000000
0.109360 -0.013143 0.000000
0.041144 -0.013210 0.000000
0.104642 -0.013210 0.000000
0.035283 -0.013511 0.000000
0.110504 -0.013511 0.000000
0.042278 -0.013611 0.000000
0.103509 -0.013611 0.000000
0.034185 -0.014034 0.000000
0.111602 -0.014034 0.000000
0.043360 -0.014165 0.000000
0.102427 -0.014165 0.000000
0.033150 -0.014712 0.000000
0.112637 -0.014712 0.000000
0.044375 -0.014872 0.000000
0.101412 -0.014872 0.000000
0.032195 -0.015545 0.000000
0.113592 -0.015545 0.000000
0.045307 -0.015732 0.000000
0.100480 -0.015732 0.000000
0.032113 -0.015626 0.000000
0.113674 -0.015626 0.000000
0.031889 -0.015850 0.000000
0.113898 -0.015851 0.000000
0.045610 -0.016052 0.000000
0.085084 -0.032030 0.000000
0.031550 -0.016189 0.000000
0.114237 -0.016189 0.000000
0.046447 -0.016939 0.000000
0.031126 -0.016613 0.000000
0.114661 -0.016614 0.000000
0.030644 -0.017095 0.000000
0.115143 -0.017096 0.000000
0.047712 -0.018278 0.000000
0.030133 -0.017606 0.000000
0.115654 -0.017606 0.000000
0.029622 -0.018116 0.000000
0.116165 -0.018117 0.000000
0.029140 -0.018598 0.000000
0.116647 -0.018599 0.000000
0.049298 -0.019957 0.000000
0.028716 -0.019023 0.000000
0.117071 -0.019023 0.000000
0.085084 -0.025764 0.000000
0.028377 -0.019362 0.000000
0.117410 -0.019362 0.000000
0.028153 -0.019586 0.000000
0.117634 -0.019586 0.000000
0.028072 -0.019667 0.000000
0.117716 -0.019667 0.000000
0.027253 -0.020603 0.000000
0.118534 -0.020603 0.000000
0.051098 -0.021862 0.000000
0.026584 -0.021616 0.000000
0.119203 -0.021617 0.000000
0.026063 -0.022691 0.000000
0.119724 -0.022691 0.000000
0.053005 -0.023881 0.000000
0.025691 -0.023812 0.000000
0.120096 -0.023812 0.000000
0.025468 -0.024963 0.000000
0.120319 -0.024964 0.000000
0.054911 -0.025899 0.000000
0.025394 -0.026130 0.000000
0.120393 -0.026131 0.000000
0.085084 -0.030366 0.000000
0.056711 -0.027804 0.000000
0.025468 -0.027297 0.000000
0.120319 -0.027298 0.000000
0.025691 -0.028449 0.000000
0.120096 -0.028450 0.000000
0.058297 -0.029483 0.000000
0.026063 -0.029570 0.000000
0.119724 -0.029571 0.000000
0.059562 -0.030823 0.000000
0.026584 -0.030645 0.000000
0.119203 -0.030646 0.000000
0.027253 -0.031658 0.000000
0.118533 -0.031659 0.000000
0.060400 -0.031709 0.000000
0.028072 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032594 0.000000
0.117715 -0.032595 0.000000
0.117715 -0.032595 0.000000
0.117715 -0.032595 0.000000
0.117715 -0.032595 0.000000
0.117715 -0.032595 0.000000
0.028826 -0.033349 0.000000
0.079359 -0.070943 0.000000
0.030913 -0.035435 0.000000
0.034065 -0.038586 0.000000
-0.059796 -0.035794 0.000000
-0.055910 -0.035794 0.000000
-0.050039 -0.035794 0.000000
-0.042679 -0.035794 0.000000
-0.034327 -0.035794 0.000000
-0.025479 -0.035794 0.000000
-0.016630 -0.035794 0.000000
-0.008278 -0.035794 0.000000
-0.000919 -0.035794 0.000000
0.004953 -0.035794 0.000000
0.008839 -0.035794 0.000000
0.010245 -0.035794 0.000000
0.010849 -0.035814 0.000000
0.011447 -0.035873 0.000000
0.012037 -0.035971 0.000000
0.012618 -0.036107 0.000000
0.013187 -0.036280 0.000000
0.013744 -0.036490 0.000000
0.014286 -0.036735 0.000000
0.014811 -0.037015 0.000000
0.015317 -0.037330 0.000000
0.015804 -0.037678 0.000000
0.016269 -0.038059 0.000000
0.016710 -0.038471 0.000000
0.016830 -0.038591 0.000000
0.038016 -0.042536 0.000000
0.017162 -0.038923 0.000000
0.017662 -0.039424 0.000000
0.018290 -0.040051 0.000000
0.019003 -0.040764 0.000000
0.019758 -0.041518 0.000000
0.020513 -0.042273 0.000000
0.021225 -0.042986 0.000000
0.042500 -0.047019 0.000000
0.021853 -0.043613 0.000000
0.022354 -0.044114 0.000000
0.022686 -0.044446 0.000000
0.022806 -0.044566 0.000000
0.024044 -0.046082 0.000000
0.024892 -0.047721 0.000000
0.047250 -0.051768 0.000000
0.025371 -0.049433 0.000000
0.025499 -0.051171 0.000000
-0.085500 -0.052270 0.000000
0.025298 -0.052885 0.000000
0.052000 -0.056518 0.000000
-0.085257 -0.053459 0.000000
0.024788 -0.054527 0.000000
-0.084865 -0.054587 0.000000
0.023987 -0.056050 0.000000
-0.084335 -0.055643 0.000000
-0.083678 -0.056615 0.000000
0.022917 -0.057404 0.000000
0.056484 -0.061000 0.000000
-0.082905 -0.057493 0.000000
0.021597 -0.058542 0.000000
-0.082027 -0.058266 0.000000
-0.081055 -0.058922 0.000000
0.020048 -0.059414 0.000000
-0.079999 -0.059452 0.000000
0.018289 -0.059973 0.000000
-0.078871 -0.059844 0.000000
-0.077681 -0.060087 0.000000
0.016340 -0.060170 0.000000
-0.076440 -0.060170 0.000000
-0.074615 -0.060170 0.000000
-0.069568 -0.060170 0.000000
-0.061943 -0.060170 0.000000
-0.052386 -0.060170 0.000000
-0.041540 -0.060170 0.000000
-0.030050 -0.060170 0.000000
-0.018560 -0.060170 0.000000
-0.007714 -0.060170 0.000000
0.001843 -0.060170 0.000000
0.009468 -0.060170 0.000000
0.014515 -0.060170 0.000000
0.060435 -0.064951 0.000000
0.063587 -0.068102 0.000000
0.065674 -0.070188 0.000000
0.066429 -0.070943 0.000000
0.067365 -0.071761 0.000000
0.078422 -0.071761 0.000000
0.068378 -0.072430 0.000000
0.077409 -0.072430 0.000000
0.069453 -0.072951 0.000000
0.076334 -0.072951 0.000000
0.070574 -0.073322 0.000000
0.075213 -0.073322 0.000000
0.071726 -0.073545 0.000000
0.074061 -0.073545 0.000000
0.072894 -0.073620 0.000000
3 0 1 2
3 1 3 2
3 1 4 3
3 4 5 3
3 0 6 1
3 7 5 4
3 0 7 6
3 0 5 7
3 8 9 10
3 8 11 9
3 3 1 0
3 3 4 1
3 5 4 3
3 5 6 4
3 7 6 5
3 7 8 6
3 9 8 7
3 9 10 8
3 11 10 9
3 11 12 10
3 13 12 11
3 13 14 12
3 13 15 14
3 16 15 13
3 16 17 15
3 18 17 16
3 18 19 17
3 20 19 18
3 20 21 19
3 22 21 20
3 23 24 25
3 23 26 24
3 23 27 26
3 23 28 27
3 23 29 28
3 23 30 29
3 23 31 30
3 23 32 31
3 23 33 32
3 23 34 33
3 23 35 34
3 23 36 35
3 23 37 36
3 23 38 37
3 39 38 23
3 39 40 38
3 39 41 40
3 42 41 39
3 42 43 41
3 44 43 42
3 44 45 43
3 44 46 45
3 47 46 44
3 47 48 46
3 22 49 21
3 50 49 22
3 51 48 47
3 51 52 48
3 51 53 52
3 54 53 51
3 54 55 53
3 56 55 54
3 56 57 55
3 50 58 49
3 59 58 50
3 60 57 56
3 60 61 57
3 62 61 60
3 62 63 61
3 64 63 62
3 59 65 58
3 66 65 59
3 67 63 64
3 67 68 63
3 69 68 67
3 70 68 69
3 71 68 70
3 72 68 71
3 73 68 72
3 66 74 65
3 75 74 66
3 76 68 73
3 77 68 76
3 78 68 77
3 79 68 78
3 80 68 79
3 81 68 80
3 82 68 81
3 75 83 74
3 84 83 75
3 85 68 82
3 86 68 85
3 86 87 68
3 87 88 68
3 89 87 86
3 90 87 89
3 91 87 90
3 92 87 91
3 93 87 92
3 94 87 93
3 95 87 94
3 96 87 95
3 97 87 96
3 98 87 97
3 99 88 87
3 84 100 83
3 101 100 84
3 101 102 100
3 103 102 101
3 103 104 102
3 104 105 102
3 106 107 103
3 107 104 103
3 107 108 104
3 109 105 104
3 106 110 107
3 111 108 107
3 106 112 110
3 109 113 105
3 114 112 106
3 114 115 112
3 109 116 113
3 117 115 114
3 109 118 116
3 119 115 117
3 119 120 115
3 99 121 88
3 109 122 118
3 123 120 119
3 123 124 120
3 109 125 122
3 126 124 123
3 109 127 125
3 128 124 126
3 128 129 124
3 109 130 127
3 131 129 128
3 131 132 129
3 109 133 130
3 134 132 131
3 109 135 133
3 136 132 134
3 136 137 132
3 109 138 135
3 139 137 136
3 109 140 138
3 141 137 139
3 141 142 137
3 109 143 140
3 144 142 141
3 109 145 143
3 146 142 144
3 109 147 145
3 148 142 146
3 99 149 121
3 109 150 147
3 151 142 148
3 109 152 150
3 153 142 151
3 153 154 142
3 109 155 152
3 156 154 153
3 109 157 155
3 158 154 156
3 109 159 157
3 160 154 158
3 160 161 154
3 109 162 159
3 163 161 160
3 109 164 162
3 165 161 163
3 165 166 161
3 109 167 164
3 168 166 165
3 169 167 109
3 168 170 166
3 169 171 167
3 172 170 168
3 169 173 171
3 174 170 172
3 175 173 169
3 174 176 170
3 175 177 173
3 178 176 174
3 179 177 175
3 178 180 176
3 179 181 177
3 182 180 178
3 183 181 179
3 182 184 180
3 183 185 181
3 186 184 182
3 187 185 183
3 186 188 184
3 187 189 185
3 190 188 186
3 191 189 187
3 190 192 188
3 99 193 149
3 99 194 193
3 99 195 194
3 196 197 198
3 199 200 201
3 202 197 196
3 199 203 200
3 202 204 197
3 205 203 199
3 206 204 202
3 205 207 203
3 206 208 204
3 209 207 205
3 210 208 206
3 209 211 207
3 210 212 208
3 213 211 209
3 214 212 210
3 213 215 211
3 214 216 212
3 217 215 213
3 218 216 214
3 217 219 215
3 218 220 216
3 221 219 217
3 222 220 218
3 221 223 219
3 224 220 222
3 221 225 223
3 224 226 220
3 227 225 221
3 228 226 224
3 227 229 225
3 228 230 226
3 231 230 228
3 227 232 229
3 233 230 231
3 227 234 232
3 233 235 230
3 236 235 233
3 227 237 234
3 238 235 236
3 227 239 237
3 240 235 238
3 227 241 239
3 240 242 235
3 243 242 240
3 227 244 241
3 99 245 195
3 246 242 243
3 227 247 244
3 248 242 246
3 227 249 247
3 250 242 248
3 227 251 249
3 252 242 250
3 227 253 251
3 252 254 242
3 255 254 252
3 227 256 253
3 257 254 255
3 227 258 256
3 257 259 254
3 260 259 257
3 227 261 258
3 262 259 260
3 227 263 261
3 262 264 259
3 265 264 262
3 227 266 263
3 99 267 245
3 265 268 264
3 269 268 265
3 227 270 266
3 271 268 269
3 227 272 270
3 271 273 268
3 274 273 271
3 227 275 272
3 274 276 273
3 277 276 274
3 227 278 275
3 99 227 267
3 279 276 277
3 227 280 278
3 279 281 276
3 282 281 279
3 227 283 280
3 283 284 280
3 284 285 280
3 285 286 280
3 286 287 280
3 287 288 280
3 288 289 280
3 289 290 280
3 290 291 280
3 291 292 280
3 292 293 280
3 293 294 280
3 294 295 280
3 282 99 281
3 282 227 99
3 282 283 227
3 296 283 282
3 296 297 283
3 298 297 296
3 299 297 298
3 111 300 108
3 111 301 300
3 111 302 301
3 111 303 302
3 111 304 303
3 111 305 304
3 111 306 305
3 111 307 306
3 111 308 307
3 111 309 308
3 111 310 309
3 111 311 310
3 111 312 311
3 111 313 312
3 111 314 313
3 111 315 314
3 111 316 315
3 111 317 316
3 111 318 317
3 111 319 318
3 111 320 319
3 111 321 320
3 111 322 321
3 111 323 322
3 111 324 323
3 325 297 299
3 111 326 324
3 111 327 326
3 111 328 327
3 111 329 328
3 111 330 329
3 111 331 330
3 111 332 331
3 333 297 325
3 111 334 332
3 111 335 334
3 111 336 335
3 111 337 336
3 111 338 337
3 111 339 338
3 340 297 333
3 111 341 339
3 111 342 341
3 343 342 111
3 343 344 342
3 345 297 340
3 346 344 343
3 346 347 344
3 348 347 346
3 348 349 347
3 350 349 348
3 351 349 350
3 351 352 349
3 353 297 345
3 354 352 351
3 354 355 352
3 356 355 354
3 357 355 356
3 357 358 355
3 359 358 357
3 359 360 358
3 361 360 359
3 362 360 361
3 362 363 360
3 364 363 362
3 365 363 364
3 366 363 365
3 367 363 366
3 368 363 367
3 369 363 368
3 370 363 369
3 371 363 370
3 372 363 371
3 373 363 372
3 374 363 373
3 375 363 374
3 376 297 353
3 377 297 376
3 378 297 377
3 379 297 378
3 380 297 379
3 380 381 297
3 382 381 380
3 382 383 381
3 384 383 382
3 384 385 383
3 386 385 384
3 386 387 385
3 388 387 386
3 388 389 387
3 390 389 388

View File

@@ -95,6 +95,7 @@ set(VMIX_SRCS
SrtReceiverSource.cpp
MultiFileRecorder.cpp
DisplaysView.cpp
ScreenCaptureSource.cpp
)
#####
@@ -134,6 +135,8 @@ ELSE(APPLE)
set(PLATFORM_LIBS
GTK::GTK
X11::X11
X11::xcb
)
ENDIF(APPLE)

View File

@@ -452,8 +452,8 @@ Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t)
shadows[PATTERN]= shadow;
icons[CAMERA] = new Mesh("mesh/icon_camera.ply");
shadows[CAMERA] = shadow;
icons[CUBE] = new Mesh("mesh/icon_cube.ply");
shadows[CUBE] = shadow;
icons[SCREEN] = new Mesh("mesh/icon_cube.ply");
shadows[SCREEN] = shadow;
icons[SHARE] = new Mesh("mesh/icon_share.ply");
shadows[SHARE] = shadow;
icons[RECEIVE] = new Mesh("mesh/icon_receive.ply");

View File

@@ -60,7 +60,7 @@ protected:
class Symbol : public Node
{
public:
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, SEQUENCE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE, RECEIVE,
typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, SEQUENCE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, SCREEN, SHARE, RECEIVE,
DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, TELEVISION, ARROWS, ROTATION, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type;
Symbol(Type t, glm::vec3 pos = glm::vec3(0.f));
~Symbol();

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
@@ -25,7 +25,6 @@
#include <gst/pbutils/pbutils.h>
#include <gst/gst.h>
#include "Log.h"
#include "Decorations.h"
#include "Stream.h"
@@ -35,17 +34,14 @@
#ifndef NDEBUG
#define DEVICE_DEBUG
#define GST_DEVICE_DEBUG
#endif
#if defined(APPLE)
std::string gst_plugin_device = "avfvideosrc";
std::string gst_plugin_vidcap = "avfvideosrc capture-screen=true";
#else
std::string gst_plugin_device = "v4l2src";
std::string gst_plugin_vidcap = "ximagesrc";
#endif
std::string pipelineForDevice(GstDevice *device, uint index)
@@ -119,7 +115,6 @@ private:
std::string _name;
};
struct hasConnectedSource
{
inline bool operator()(const DeviceHandle &elem) const {
@@ -131,6 +126,29 @@ private:
Source *s_;
};
std::string get_device_properties(GstDevice *device)
{
std::ostringstream oss;
GstStructure *stru = gst_device_get_properties(device);
gint n = gst_structure_n_fields (stru);
oss << "- " << gst_structure_get_name(stru) << " -" << std::endl;
for (gint i = 0; i < n; ++i) {
std::string name = gst_structure_nth_field_name (stru, i);
if (name.find("device.path") !=std::string::npos || name.find("object.path") !=std::string::npos)
oss << "Path : " << gst_structure_get_string(stru, name.c_str()) << std::endl;
if (name.find("device.api") !=std::string::npos)
oss << "Api : " << gst_structure_get_string(stru, name.c_str()) << std::endl;
if (name.find("device.card") !=std::string::npos || name.find("cap.card") !=std::string::npos)
oss << "Card : " << gst_structure_get_string(stru, name.c_str()) << std::endl;
if (name.find("device.driver") !=std::string::npos || name.find("cap.driver") !=std::string::npos)
oss << "Driver : " << gst_structure_get_string(stru, name.c_str()) << std::endl;
}
gst_structure_free (stru);
return oss.str();
}
void Device::add(GstDevice *device)
{
if (device==nullptr)
@@ -146,7 +164,7 @@ void Device::add(GstDevice *device)
if ( handle == handles_.cend() ) {
std::string p = pipelineForDevice(device, handles_.size());
DeviceConfigSet confs = getDeviceConfigs(p);
GstToolkit::PipelineConfigSet confs = GstToolkit::getPipelineConfigs(p);
// add if not in the list and valid
if (!p.empty() && !confs.empty()) {
@@ -154,12 +172,12 @@ void Device::add(GstDevice *device)
dev.name = device_name;
dev.pipeline = p;
dev.configs = confs;
handles_.push_back(dev);
dev.properties = get_device_properties (device);
#ifdef GST_DEVICE_DEBUG
gchar *stru = gst_structure_to_string( gst_device_get_properties(device) );
g_print("\nDevice %s plugged : %s\n", device_name, stru);
g_free (stru);
g_print("\n%s %s", device_name, gst_structure_to_string(stru) );
#endif
handles_.push_back(dev);
Log::Info("Device %s plugged.", device_name);
}
}
@@ -174,33 +192,46 @@ void Device::remove(GstDevice *device)
if (device==nullptr)
return;
gchar *device_name = gst_device_get_display_name (device);
std::string devicename = gst_device_get_display_name (device);
// lock before change
access_.lock();
// if a device with this name is listed
auto handle = std::find_if(handles_.cbegin(), handles_.cend(), hasDeviceName(device_name) );
auto handle = std::find_if(handles_.cbegin(), handles_.cend(), hasDeviceName(devicename) );
if ( handle != handles_.cend() ) {
// remove the handle if there is no source connected
if (handle->connected_sources.empty())
handles_.erase(handle);
else {
// otherwise unplug all sources
for (auto sit = handle->connected_sources.begin(); sit != handle->connected_sources.end(); ++sit)
(*sit)->unplug();
// and inform user
Log::Warning("Device %s unplugged.", device_name);
// NB; the handle will be removed at the destruction of the last DeviceSource
// just inform the user if there is no source connected
if (handle->connected_sources.empty()) {
Log::Info("Device %s unplugged.", devicename.c_str());
}
else {
// otherwise unplug all sources and close their streams
bool first = true;
for (auto sit = handle->connected_sources.begin(); sit != handle->connected_sources.end(); ++sit) {
// for the first connected source in the list
if (first) {
// close the stream
(*sit)->stream()->close();
first = false;
} else {
// all others set the stream to null, so it is not deleted in destructor of StreamSource
(*sit)->stream_ = nullptr;
}
// all connected sources are set to unplugged
(*sit)->unplug();
// and finally inform user
Log::Warning("Device %s unplugged: sources %s disconnected.",
devicename.c_str(), (*sit)->name().c_str());
}
}
// remove the handle
handles_.erase(handle);
}
// unlock access
access_.unlock();
g_free (device_name);
}
Device::Device(): monitor_(nullptr), monitor_initialized_(false), monitor_unplug_event_(false)
@@ -229,26 +260,6 @@ void Device::launchMonitoring(Device *d)
}
g_list_free(devices);
// Try to get captrure screen
DeviceConfigSet confs = getDeviceConfigs(gst_plugin_vidcap);
if (!confs.empty()) {
DeviceConfig best = *confs.rbegin();
DeviceConfigSet confscreen;
// limit framerate to 30fps
best.fps_numerator = MIN( best.fps_numerator, 30);
best.fps_denominator = 1;
confscreen.insert(best);
// add to config list
d->access_.lock();
DeviceHandle dev;
dev.name = "Screen capture";
dev.pipeline = gst_plugin_vidcap;
dev.configs = confscreen;
d->handles_.push_back(dev);
d->access_.unlock();
}
// monitor is initialized
d->monitor_initialized_ = true;
d->monitor_initialization_.notify_all();
@@ -343,9 +354,21 @@ std::string Device::description(int index)
return ret;
}
DeviceConfigSet Device::config(int index)
std::string Device::properties(int index)
{
DeviceConfigSet ret;
std::string ret = "";
access_.lock();
if (index > -1 && index < (int) handles_.size())
ret = handles_[index].properties;
access_.unlock();
return ret;
}
GstToolkit::PipelineConfigSet Device::config(int index)
{
GstToolkit::PipelineConfigSet ret;
access_.lock();
if (index > -1 && index < (int) handles_.size())
@@ -391,22 +414,27 @@ void DeviceSource::unsetDevice()
// if this is the last source connected to the device handler
// the stream will be removed by the ~StreamSource destructor
// and the device handler should not keep reference to it
if (h->connected_sources.empty()) {
// if the cause of deletion is the unplugging of the device
if (unplugged_)
// remove the handle entirely
Device::manager().handles_.erase(h);
else
// otherwise just cancel the reference to the stream
h->stream = nullptr;
}
if (h->connected_sources.empty())
// cancel the reference to the stream
h->stream = nullptr;
else
// else this means another DeviceSource is using this stream
// and we should avoid to delete the stream in the ~StreamSource destructor
else
stream_ = nullptr;
}
device_ = "";
}
void DeviceSource::reconnect()
{
// remember name
std::string d = device_;
// disconnect
unsetDevice();
// connect
setDevice(d);
}
void DeviceSource::setDevice(const std::string &devicename)
{
@@ -422,17 +450,27 @@ void DeviceSource::setDevice(const std::string &devicename)
if (!device_.empty())
unsetDevice();
// remember device name
// if the stream referenced in this source remains after unsetDevice
if (stream_) {
delete stream_;
stream_ = nullptr;
}
// set new device name
device_ = devicename;
// check existence of a device handle with that name
auto h = std::find_if(Device::manager().handles_.begin(), Device::manager().handles_.end(), hasDeviceName(device_));
// found a device handle
if ( h != Device::manager().handles_.end()) {
// find if a DeviceHandle with this device name already has a stream
if ( h->stream != nullptr) {
// find if a DeviceHandle with this device name already has a stream that is open
if ( h->stream != nullptr ) {
// just use it !
stream_ = h->stream;
// reinit to adapt to new stream
init();
}
else {
// start filling in the gstreamer pipeline
@@ -440,16 +478,16 @@ void DeviceSource::setDevice(const std::string &devicename)
pipeline << h->pipeline;
// test the device and get config
DeviceConfigSet confs = h->configs;
GstToolkit::PipelineConfigSet confs = h->configs;
#ifdef DEVICE_DEBUG
Log::Info("Device %s supported configs:", devicename.c_str());
for( DeviceConfigSet::iterator it = confs.begin(); it != confs.end(); ++it ){
for( GstToolkit::PipelineConfigSet::iterator it = confs.begin(); it != confs.end(); ++it ){
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
Log::Info(" - %s %s %d x %d %.1f fps", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
}
#endif
if (!confs.empty()) {
DeviceConfig best = *confs.rbegin();
GstToolkit::PipelineConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
Log::Info("Device %s selected its optimal config: %s %s %dx%d@%.1ffps", device_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
@@ -460,13 +498,12 @@ void DeviceSource::setDevice(const std::string &devicename)
pipeline << ",width=" << best.width;
pipeline << ",height=" << best.height;
// decode jpeg if needed
if ( best.stream.find("jpeg") != std::string::npos )
pipeline << " ! jpegdec";
if ( device_.find("Screen") != std::string::npos )
pipeline << " ! videoconvert ! video/x-raw,format=RGB ! queue max-size-buffers=3";
pipeline << " ! videoconvert";
// always convert
pipeline << " ! queue ! videoconvert";
// delete and reset render buffer to enforce re-init of StreamSource
if (renderbuffer_)
@@ -474,8 +511,6 @@ void DeviceSource::setDevice(const std::string &devicename)
renderbuffer_ = nullptr;
// new stream
if (stream_)
delete stream_;
stream_ = h->stream = new Stream;
// open gstreamer
@@ -492,7 +527,7 @@ void DeviceSource::setDevice(const std::string &devicename)
}
else {
unplugged_ = true;
Log::Warning("No such device '%s'", device_.c_str());
Log::Warning("No device named '%s'", device_.c_str());
}
}
@@ -517,9 +552,7 @@ void DeviceSource::setActive (bool on)
}
stream_->enable(streamactive);
}
}
}
}
@@ -535,176 +568,14 @@ Source::Failure DeviceSource::failed() const
return (unplugged_ || StreamSource::failed()) ? FAIL_CRITICAL : FAIL_NONE;
}
DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description)
{
DeviceConfigSet configs;
// create dummy pipeline to be tested
std::string description = src_description;
description += " name=devsrc ! fakesink name=sink";
// parse pipeline descriptor
GError *error = NULL;
GstElement *pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
Log::Warning("DeviceSource Could not construct test pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
return configs;
}
// get the pipeline element named "devsrc" from the Device class
GstElement *elem = gst_bin_get_by_name (GST_BIN (pipeline_), "devsrc");
if (elem) {
// initialize the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PAUSED);
if (ret != GST_STATE_CHANGE_FAILURE) {
// get the first pad and its content
GstIterator *iter = gst_element_iterate_src_pads(elem);
GValue vPad = G_VALUE_INIT;
GstPad* pad_ret = NULL;
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
{
pad_ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (pad_ret, NULL);
// loop over all caps offered by the pad
int C = gst_caps_get_size(device_caps);
for (int c = 0; c < C; ++c) {
// get GST cap
GstStructure *decice_cap_struct = gst_caps_get_structure (device_caps, c);
#ifdef GST_DEVICE_DEBUG
gchar *capstext = gst_structure_to_string (decice_cap_struct);
g_print("\nDevice caps: %s", capstext);
g_free(capstext);
#endif
// fill our config
DeviceConfig config;
// not managing opengl texture-target types
// TODO: support input devices texture-target video/x-raw(memory:GLMemory) for improved pipeline
if ( gst_structure_has_field (decice_cap_struct, "texture-target"))
continue;
// NAME : typically video/x-raw or image/jpeg
config.stream = gst_structure_get_name (decice_cap_struct);
// FORMAT : typically BGRA or YUVY
if ( gst_structure_has_field (decice_cap_struct, "format")) {
// get generic value
const GValue *val = gst_structure_get_value(decice_cap_struct, "format");
// if its a list of format string
if ( GST_VALUE_HOLDS_LIST(val)) {
int N = gst_value_list_get_size(val);
for (int n = 0; n < N; n++ ){
std::string f = gst_value_serialize( gst_value_list_get_value(val, n) );
// preference order : 1) RGBx, 2) JPEG, 3) ALL OTHER
// select f if it contains R (e.g. for RGBx) and not already RGB in config
if ( (f.find("R") != std::string::npos) && (config.format.find("R") == std::string::npos ) ) {
config.format = f;
break;
}
// default, take at least one if nothing yet in config
else if ( config.format.empty() )
config.format = f;
}
}
// single format
else {
config.format = gst_value_serialize(val);
}
}
// FRAMERATE : can be a fraction of a list of fractions
if ( gst_structure_has_field (decice_cap_struct, "framerate")) {
// get generic value
const GValue *val = gst_structure_get_value(decice_cap_struct, "framerate");
// if its a single fraction
if ( GST_VALUE_HOLDS_FRACTION(val)) {
config.fps_numerator = gst_value_get_fraction_numerator(val);
config.fps_denominator= gst_value_get_fraction_denominator(val);
}
// if its a range of fraction; take the max
else if ( GST_VALUE_HOLDS_FRACTION_RANGE(val)) {
config.fps_numerator = gst_value_get_fraction_numerator(gst_value_get_fraction_range_max(val));
config.fps_denominator= gst_value_get_fraction_denominator(gst_value_get_fraction_range_max(val));
}
// deal otherwise with a list of fractions; find the max
else if ( GST_VALUE_HOLDS_LIST(val)) {
gdouble fps_max = 1.0;
// loop over all fractions
int N = gst_value_list_get_size(val);
for (int i = 0; i < N; ++i ){
const GValue *frac = gst_value_list_get_value(val, i);
// read one fraction in the list
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
int n = gst_value_get_fraction_numerator(frac);
int d = gst_value_get_fraction_denominator(frac);
// keep only the higher FPS
gdouble f = 1.0;
gst_util_fraction_to_double( n, d, &f );
if ( f > fps_max ) {
config.fps_numerator = n;
config.fps_denominator = d;
fps_max = f;
}
}
}
}
}
else {
// default
config.fps_numerator = 30;
config.fps_denominator = 1;
}
// WIDTH and HEIGHT
if ( gst_structure_has_field (decice_cap_struct, "width"))
gst_structure_get_int (decice_cap_struct, "width", &config.width);
if ( gst_structure_has_field (decice_cap_struct, "height"))
gst_structure_get_int (decice_cap_struct, "height", &config.height);
// add this config
configs.insert(config);
}
}
gst_iterator_free(iter);
// terminate pipeline
gst_element_set_state (pipeline_, GST_STATE_NULL);
}
g_object_unref (elem);
}
gst_object_unref (pipeline_);
return configs;
}
glm::ivec2 DeviceSource::icon() const
{
if ( device_.find("Screen") != std::string::npos )
return glm::ivec2(ICON_SOURCE_DEVICE_SCREEN);
else
return glm::ivec2(ICON_SOURCE_DEVICE);
return glm::ivec2(ICON_SOURCE_DEVICE);
}
std::string DeviceSource::info() const
{
if ( device_.find("Screen") != std::string::npos )
return "Screen capture";
else
return "Device";
return "Device";
}
@@ -714,5 +585,3 @@ std::string DeviceSource::info() const

View File

@@ -4,11 +4,15 @@
#include <string>
#include <vector>
#include <set>
#include <map>
#include "GstToolkit.h"
#include "StreamSource.h"
class DeviceSource : public StreamSource
{
friend class Device;
public:
DeviceSource(uint64_t id = 0);
~DeviceSource();
@@ -24,10 +28,13 @@ public:
// specific interface
void setDevice(const std::string &devicename);
inline std::string device() const { return device_; }
void unplug() { unplugged_ = true; }
void reconnect();
glm::ivec2 icon() const override;
std::string info() const override;
inline std::string info() const override;
protected:
void unplug() { unplugged_ = true; }
private:
std::string device_;
@@ -35,48 +42,12 @@ private:
void unsetDevice();
};
struct DeviceConfig {
gint width;
gint height;
gint fps_numerator;
gint fps_denominator;
std::string stream;
std::string format;
DeviceConfig() {
width = 0;
height = 0;
fps_numerator = 30;
fps_denominator = 1;
stream = "";
format = "";
}
inline bool operator < (const DeviceConfig b) const
{
int formatscore = this->format.find("R") != std::string::npos ? 2 : 1; // best score for RGBx
int b_formatscore = b.format.find("R") != std::string::npos ? 2 : 1;
float fps = static_cast<float>(this->fps_numerator) / static_cast<float>(this->fps_denominator);
float b_fps = static_cast<float>(b.fps_numerator) / static_cast<float>(b.fps_denominator);
return ( fps * static_cast<float>(this->height * formatscore) < b_fps * static_cast<float>(b.height * b_formatscore));
}
};
struct better_device_comparator
{
inline bool operator () (const DeviceConfig a, const DeviceConfig b) const
{
return (a < b);
}
};
typedef std::set<DeviceConfig, better_device_comparator> DeviceConfigSet;
struct DeviceHandle {
std::string name;
std::string pipeline;
DeviceConfigSet configs;
std::string properties;
GstToolkit::PipelineConfigSet configs;
Stream *stream;
std::list<DeviceSource *> connected_sources;
@@ -106,21 +77,21 @@ public:
int numDevices () ;
std::string name (int index) ;
std::string description (int index) ;
DeviceConfigSet config (int index) ;
std::string properties (int index) ;
GstToolkit::PipelineConfigSet config (int index) ;
int index (const std::string &device);
bool exists (const std::string &device) ;
bool exists (const std::string &device);
void reload ();
static gboolean callback_device_monitor (GstBus *, GstMessage *, gpointer);
static DeviceConfigSet getDeviceConfigs(const std::string &src_description);
private:
static void launchMonitoring(Device *d);
static bool initialized();
void remove(GstDevice *device);
void add(GstDevice *device);
void remove(GstDevice *device);
std::mutex access_;
std::vector< DeviceHandle > handles_;

View File

@@ -16,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
//#define GST_DEVICE_DEBUG
#include <sstream>
#include <iomanip>
@@ -306,3 +307,157 @@ std::string GstToolkit::used_decoding_plugins(GstElement *gstbin)
return found;
}
GstToolkit::PipelineConfigSet GstToolkit::getPipelineConfigs(const std::string &src_description)
{
PipelineConfigSet configs;
// create dummy pipeline to be tested
std::string description = src_description;
description += " name=devsrc ! fakesink name=sink";
// parse pipeline descriptor
GError *error = NULL;
GstElement *pipeline_ = gst_parse_launch (description.c_str(), &error);
if (error != NULL) {
g_printerr("DeviceSource Could not construct test pipeline %s:\n%s", description.c_str(), error->message);
g_clear_error (&error);
return configs;
}
// get the pipeline element named "devsrc" from the Device class
GstElement *elem = gst_bin_get_by_name (GST_BIN (pipeline_), "devsrc");
if (elem) {
// initialize the pipeline
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PAUSED);
if (ret != GST_STATE_CHANGE_FAILURE) {
// get the first pad and its content
GstIterator *iter = gst_element_iterate_src_pads(elem);
GValue vPad = G_VALUE_INIT;
GstPad* pad_ret = NULL;
if (gst_iterator_next(iter, &vPad) == GST_ITERATOR_OK)
{
pad_ret = GST_PAD(g_value_get_object(&vPad));
GstCaps *device_caps = gst_pad_query_caps (pad_ret, NULL);
// loop over all caps offered by the pad
int C = gst_caps_get_size(device_caps);
for (int c = 0; c < C; ++c) {
// get GST cap
GstStructure *decice_cap_struct = gst_caps_get_structure (device_caps, c);
#ifdef GST_DEVICE_DEBUG
gchar *capstext = gst_structure_to_string (decice_cap_struct);
g_print("\nPipeline caps: %s", capstext);
g_free(capstext);
#endif
// fill our config
PipelineConfig config;
// not managing opengl texture-target types
// TODO: support input devices texture-target video/x-raw(memory:GLMemory) for improved pipeline
if ( gst_structure_has_field (decice_cap_struct, "texture-target"))
continue;
// NAME : typically video/x-raw or image/jpeg
config.stream = gst_structure_get_name (decice_cap_struct);
// FORMAT : typically BGRA or YUVY
if ( gst_structure_has_field (decice_cap_struct, "format")) {
// get generic value
const GValue *val = gst_structure_get_value(decice_cap_struct, "format");
// if its a list of format string
if ( GST_VALUE_HOLDS_LIST(val)) {
int N = gst_value_list_get_size(val);
for (int n = 0; n < N; n++ ){
std::string f = gst_value_serialize( gst_value_list_get_value(val, n) );
// preference order : 1) RGBx, 2) JPEG, 3) ALL OTHER
// select f if it contains R (e.g. for RGBx) and not already RGB in config
if ( (f.find("R") != std::string::npos) && (config.format.find("R") == std::string::npos ) ) {
config.format = f;
break;
}
// default, take at least one if nothing yet in config
else if ( config.format.empty() )
config.format = f;
}
}
// single format
else {
config.format = gst_value_serialize(val);
}
}
// FRAMERATE : can be a fraction of a list of fractions
if ( gst_structure_has_field (decice_cap_struct, "framerate")) {
// get generic value
const GValue *val = gst_structure_get_value(decice_cap_struct, "framerate");
// if its a single fraction
if ( GST_VALUE_HOLDS_FRACTION(val)) {
config.fps_numerator = gst_value_get_fraction_numerator(val);
config.fps_denominator= gst_value_get_fraction_denominator(val);
}
// if its a range of fraction; take the max
else if ( GST_VALUE_HOLDS_FRACTION_RANGE(val)) {
config.fps_numerator = gst_value_get_fraction_numerator(gst_value_get_fraction_range_max(val));
config.fps_denominator= gst_value_get_fraction_denominator(gst_value_get_fraction_range_max(val));
}
// deal otherwise with a list of fractions; find the max
else if ( GST_VALUE_HOLDS_LIST(val)) {
gdouble fps_max = 1.0;
// loop over all fractions
int N = gst_value_list_get_size(val);
for (int i = 0; i < N; ++i ){
const GValue *frac = gst_value_list_get_value(val, i);
// read one fraction in the list
if ( GST_VALUE_HOLDS_FRACTION(frac)) {
int n = gst_value_get_fraction_numerator(frac);
int d = gst_value_get_fraction_denominator(frac);
// keep only the higher FPS
gdouble f = 1.0;
gst_util_fraction_to_double( n, d, &f );
if ( f > fps_max ) {
config.fps_numerator = n;
config.fps_denominator = d;
fps_max = f;
}
}
}
}
}
else {
// default
config.fps_numerator = 30;
config.fps_denominator = 1;
}
// WIDTH and HEIGHT
if ( gst_structure_has_field (decice_cap_struct, "width"))
gst_structure_get_int (decice_cap_struct, "width", &config.width);
if ( gst_structure_has_field (decice_cap_struct, "height"))
gst_structure_get_int (decice_cap_struct, "height", &config.height);
// add this config
configs.insert(config);
}
}
gst_iterator_free(iter);
// terminate pipeline
gst_element_set_state (pipeline_, GST_STATE_NULL);
}
g_object_unref (elem);
}
gst_object_unref (pipeline_);
return configs;
}

View File

@@ -5,6 +5,7 @@
#include <string>
#include <list>
#include <set>
namespace GstToolkit
{
@@ -30,6 +31,46 @@ std::list<std::string> all_plugin_features(std::string pluginname);
bool has_feature (std::string name);
bool enable_feature (std::string name, bool enable);
struct PipelineConfig {
gint width;
gint height;
gint fps_numerator;
gint fps_denominator;
std::string stream;
std::string format;
PipelineConfig() {
width = 0;
height = 0;
fps_numerator = 30;
fps_denominator = 1;
stream = "";
format = "";
}
inline bool operator < (const PipelineConfig b) const
{
int formatscore = this->format.find("R") != std::string::npos ? 2 : 1; // best score for RGBx
int b_formatscore = b.format.find("R") != std::string::npos ? 2 : 1;
float fps = static_cast<float>(this->fps_numerator) / static_cast<float>(this->fps_denominator);
float b_fps = static_cast<float>(b.fps_numerator) / static_cast<float>(b.fps_denominator);
return ( fps * static_cast<float>(this->height * formatscore) < b_fps * static_cast<float>(b.height * b_formatscore));
}
};
struct better_config_comparator
{
inline bool operator () (const PipelineConfig a, const PipelineConfig b) const
{
return (a < b);
}
};
typedef std::set<PipelineConfig, better_config_comparator> PipelineConfigSet;
PipelineConfigSet getPipelineConfigs(const std::string &src_description);
}
#endif // __GSTGUI_TOOLKIT_H_

View File

@@ -286,7 +286,7 @@ bool ImGuiToolkit::IconButton(int i, int j, const char *tooltip, const char* sho
if (window->SkipItems)
return false;
ImGui::PushID( i * 20 + j );
ImGui::PushID( i * 20 + j + ( tooltip ? window->GetID(tooltip) : 0) );
// duplicate of ImGui::InvisibleButton to handle ImGuiButtonFlags_Repeat
const ImGuiID id = window->GetID("##iconijbutton");

View File

@@ -49,6 +49,7 @@
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "ScreenCaptureSource.h"
#include "NetworkSource.h"
#include "SrtReceiverSource.h"
#include "MultiFileSource.h"
@@ -1369,7 +1370,7 @@ void ImGuiVisitor::visit (PatternSource& s)
{
for (uint p = 0; p < Pattern::count(); ++p){
pattern_descriptor pattern = Pattern::get(p);
std::string label = pattern.label + (pattern.animated ? " " ICON_FA_CARET_RIGHT : " ");
std::string label = pattern.label + (pattern.animated ? " " ICON_FA_PLAY_CIRCLE : " ");
if (pattern.available && ImGui::Selectable( label.c_str(), p == s.pattern()->type() )) {
s.setPattern(p, s.pattern()->resolution());
info.reset();
@@ -1419,16 +1420,86 @@ void ImGuiVisitor::visit (DeviceSource& s)
for (int d = 0; d < Device::manager().numDevices(); ++d){
std::string namedev = Device::manager().name(d);
if (ImGui::Selectable( namedev.c_str() )) {
s.setDevice(namedev);
info.reset();
std::ostringstream oss;
oss << s.name() << " Device " << namedev;
Action::manager().store(oss.str());
if ( namedev.compare(s.device())==0 )
s.reconnect();
else {
s.setDevice(namedev);
info.reset();
std::ostringstream oss;
oss << s.name() << " Device " << namedev;
Action::manager().store(oss.str());
}
}
}
ImGui::EndCombo();
}
ImVec2 botom = ImGui::GetCursorPos();
// icon (>) to open player
if ( s.playable() ) {
ImGui::SetCursorPos(top);
std::string msg = s.playing() ? "Open Player\n(source is playing)" : "Open Player\n(source is paused)";
if (ImGuiToolkit::IconButton( s.playing() ? ICON_FA_PLAY_CIRCLE : ICON_FA_PAUSE_CIRCLE, msg.c_str()))
UserInterface::manager().showSourceEditor(&s);
top.x += ImGui::GetFrameHeight();
}
// icon to show gstreamer properties
ImGui::SetCursorPos(top);
ImGuiToolkit::Icon(16, 16, false);
if (ImGui::IsItemHovered()) {
int index = Device::manager().index( s.device() );
std::string prop = Device::manager().properties( index );
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 14.0f);
ImGui::TextUnformatted( prop.c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SetCursorPos(botom);
}
else {
info.reset();
}
}
void ImGuiVisitor::visit (ScreenCaptureSource& s)
{
ImVec2 top = ImGui::GetCursorPos();
top.x = 0.5f * ImGui::GetFrameHeight() + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN;
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
s.accept(info);
ImGui::Text("%s", info.str().c_str());
ImGui::PopTextWrapPos();
ImGui::Spacing();
if ( !s.failed() ) {
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("Window", s.window().c_str()))
{
for (int d = 0; d < ScreenCapture::manager().numWindow(); ++d){
std::string namedev = ScreenCapture::manager().name(d);
if (ImGui::Selectable( namedev.c_str() )) {
if ( namedev.compare(s.window())==0 )
s.reconnect();
else {
s.setWindow(namedev);
info.reset();
std::ostringstream oss;
oss << s.name() << " Window " << namedev;
Action::manager().store(oss.str());
}
}
}
ImGui::EndCombo();
}
ImVec2 botom = ImGui::GetCursorPos();
// icon (>) to open player
@@ -1441,11 +1512,15 @@ void ImGuiVisitor::visit (DeviceSource& s)
ImGui::SetCursorPos(botom);
}
else
else {
info.reset();
}
}
void ImGuiVisitor::visit (NetworkSource& s)
{
ImVec2 top = ImGui::GetCursorPos();

View File

@@ -30,6 +30,7 @@ public:
void visit (RenderSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (ScreenCaptureSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
void visit (GenericStreamSource& s) override;

View File

@@ -36,6 +36,7 @@
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "ScreenCaptureSource.h"
#include "NetworkSource.h"
#include "SrtReceiverSource.h"
#include "MultiFileSource.h"
@@ -296,9 +297,9 @@ void InfoVisitor::visit (DeviceSource& s)
std::ostringstream oss;
DeviceConfigSet confs = Device::manager().config( Device::manager().index(s.device()));
GstToolkit::PipelineConfigSet confs = Device::manager().config( Device::manager().index(s.device()));
if ( !confs.empty()) {
DeviceConfig best = *confs.rbegin();
GstToolkit::PipelineConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
if (brief_) {
@@ -322,6 +323,41 @@ void InfoVisitor::visit (DeviceSource& s)
current_id_ = s.id();
}
void InfoVisitor::visit (ScreenCaptureSource& s)
{
if (current_id_ == s.id())
return;
std::ostringstream oss;
GstToolkit::PipelineConfigSet confs = ScreenCapture::manager().config( ScreenCapture::manager().index(s.window()));
if ( !confs.empty()) {
GstToolkit::PipelineConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
if (brief_) {
oss << best.stream << " " << best.format << ", ";
oss << best.width << " x " << best.height << ", ";
oss << std::fixed << std::setprecision(0) << fps << "fps";
}
else {
oss << s.window() << std::endl;
oss << ScreenCapture::manager().description( ScreenCapture::manager().index(s.window()));
oss << ", " << best.stream << " " << best.format << std::endl;
oss << best.width << " x " << best.height << ", ";
oss << std::fixed << std::setprecision(1) << fps << " fps";
}
}
else {
oss << s.window() << " not available.";
}
information_ = oss.str();
current_id_ = s.id();
}
void InfoVisitor::visit (NetworkSource& s)
{
if (current_id_ == s.id())

View File

@@ -33,6 +33,7 @@ public:
void visit (CloneSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (ScreenCaptureSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
void visit (GenericStreamSource& s) override;

View File

@@ -47,6 +47,7 @@
#include "MediaSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "ScreenCaptureSource.h"
#include "MultiFileSource.h"
#include "StreamSource.h"
#include "NetworkSource.h"
@@ -390,6 +391,18 @@ Source * Mixer::createSourceDevice(const std::string &namedevice)
return s;
}
Source * Mixer::createSourceScreen(const std::string &namewindow)
{
// ready to create a source
ScreenCaptureSource *s = new ScreenCaptureSource;
s->setWindow(namewindow);
// propose a new name based on pattern name
s->setName( namewindow.substr(0, namewindow.find(" ")) );
return s;
}
Source * Mixer::createSourceNetwork(const std::string &nameconnection)
{
// ready to create a source

View File

@@ -55,6 +55,7 @@ public:
Source * createSourceStream (const std::string &gstreamerpipeline);
Source * createSourcePattern(uint pattern, glm::ivec2 res);
Source * createSourceDevice (const std::string &namedevice);
Source * createSourceScreen (const std::string &namedevice);
Source * createSourceNetwork(const std::string &nameconnection);
Source * createSourceSrt (const std::string &ip, const std::string &port);
Source * createSourceGroup ();

620
src/ScreenCaptureSource.cpp Normal file
View File

@@ -0,0 +1,620 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <thread>
#include <chrono>
#include <sstream>
#include <glm/gtc/matrix_transform.hpp>
#include <gst/pbutils/gstdiscoverer.h>
#include <gst/pbutils/pbutils.h>
#include <gst/gst.h>
#include "Log.h"
#include "Decorations.h"
#include "Stream.h"
#include "Visitor.h"
#include "ScreenCaptureSource.h"
#ifndef NDEBUG
#define SCREENCAPTURE_DEBUG
#endif
#if defined(APPLE)
std::string gst_plugin_vidcap = "avfvideosrc capture-screen=true";
#else
std::string gst_plugin_vidcap = "ximagesrc show-pointer=false";
#include <xcb/xcb.h>
#include <X11/Xlib.h>
#include <X11/Xproto.h>
int X11_error_handler(Display *d, XErrorEvent *e);
std::map<unsigned long, std::string> getListX11Windows();
#endif
struct hasScreenCaptureId
{
inline bool operator()(const ScreenCaptureHandle &elem) const {
return (elem.id == _id);
}
explicit hasScreenCaptureId(unsigned long id) : _id(id) { }
private:
unsigned long _id;
};
struct hasScreenCaptureName
{
inline bool operator()(const ScreenCaptureHandle &elem) const {
return (elem.name.compare(_name) == 0);
}
explicit hasScreenCaptureName(const std::string &name) : _name(name) { }
private:
std::string _name;
};
struct hasAssociatedSource
{
inline bool operator()(const ScreenCaptureHandle &elem) const {
auto sit = std::find(elem.associated_sources.begin(), elem.associated_sources.end(), s_);
return sit != elem.associated_sources.end();
}
explicit hasAssociatedSource(Source *s) : s_(s) { }
private:
Source *s_;
};
void ScreenCaptureHandle::update(const std::string &newname)
{
GstToolkit::PipelineConfigSet confs = GstToolkit::getPipelineConfigs(pipeline);
if (!confs.empty()) {
GstToolkit::PipelineConfig best = *confs.rbegin();
GstToolkit::PipelineConfigSet confscreen;
// limit framerate to 30fps
best.fps_numerator = MIN( best.fps_numerator, 30);
best.fps_denominator = 1;
confscreen.insert(best);
configs = confscreen;
}
name = newname;
}
void ScreenCapture::add(const std::string &windowname, const std::string &pipeline, unsigned long id)
{
std::string pln = pipeline;
#if defined(LINUX)
if (id > 0)
pln += " xid=" + std::to_string(id);
#endif
GstToolkit::PipelineConfigSet confs = GstToolkit::getPipelineConfigs(pln);
if (!confs.empty()) {
GstToolkit::PipelineConfig best = *confs.rbegin();
GstToolkit::PipelineConfigSet confscreen;
// limit framerate to 30fps
best.fps_numerator = MIN( best.fps_numerator, 30);
best.fps_denominator = 1;
confscreen.insert(best);
// remove previous occurence if already exists
remove(windowname, id);
// add to config list
access_.lock();
ScreenCaptureHandle handle;
handle.name = windowname;
handle.pipeline = pln;
handle.id = id;
handle.configs = confscreen;
handles_.push_back(handle);
access_.unlock();
}
}
void ScreenCapture::remove(const std::string &windowname, unsigned long id)
{
// lock before change
access_.lock();
std::vector< ScreenCaptureHandle >::const_iterator handle = handles_.cend();
// if id is provided, find a device with the id
if (id>0)
handle = std::find_if(handles_.cbegin(), handles_.cend(), hasScreenCaptureId(id) );
// nofound : find device with the window name
if ( handle == handles_.cend() )
handle = std::find_if(handles_.cbegin(), handles_.cend(), hasScreenCaptureName(windowname) );
// found handle either by id or by name
if ( handle != handles_.cend() ) {
// just inform if there is no source connected
if (handle->associated_sources.empty()) {
Log::Info("Window %s closed.", windowname.c_str());
}
else {
// otherwise unplug all sources and close their streams
for (auto sit = handle->associated_sources.begin(); sit != handle->associated_sources.end(); ++sit) {
// inform user
Log::Warning("Window %s closed: source %s deleted.", windowname.c_str(), (*sit)->name().c_str());
(*sit)->trash();
}
}
// remove the handle
handles_.erase(handle);
}
// unlock access
access_.unlock();
}
//#ifdef LINUX
//Display *x11_display = NULL;
//#endif
ScreenCapture::ScreenCapture(): monitor_initialized_(false)
{
//#ifdef LINUX
// // Capture X11 errors to prevent crashing when capturing a window that is closed
// x11_display = XOpenDisplay(NULL);
// XSetErrorHandler(X11_error_handler);
//#endif
// launch monitor
std::thread(launchMonitoring, this).detach();
}
ScreenCapture::~ScreenCapture()
{
//#ifdef LINUX
// XCloseDisplay(x11_display);
//#endif
}
void ScreenCapture::launchMonitoring(ScreenCapture *sc)
{
#if defined(LINUX)
// Get the list of windows in X11
std::map<unsigned long,std::string> windowlist = getListX11Windows();
// Add the window id=0 for screen capture entire screen
windowlist[0] = SCREEN_CAPTURE_NAME;
// Go through the current list of window handles
sc->access_.lock();
for (auto hit = sc->handles_.begin(); hit != sc->handles_.end();) {
// if this window handle is still in X11 list
if (windowlist.count(hit->id) > 0) {
// update name of window (could have changed)
hit->update(windowlist.at(hit->id));
// remove from list of X11 windows
windowlist.erase(hit->id);
// keep in the window handles
++hit;
}
else {
// remove from window handles
hit = sc->handles_.erase(hit);
}
}
sc->access_.unlock();
// Add in window handles list the remaining X11 windows
for (auto wit = windowlist.cbegin(); wit != windowlist.cend(); ++wit) {
// Add capture screen of this window
sc->add( wit->second, gst_plugin_vidcap, wit->first);
}
// monitor is initialized
sc->monitor_initialized_ = true;
sc->monitor_initialization_.notify_all();
// give time before continuing
std::this_thread::sleep_for ( std::chrono::seconds(2) );
// de-initialize so next call will update
sc->monitor_initialized_ = false;
#else
// only one screen capture possibility on OSX
sc->add(SCREEN_CAPTURE_NAME, gst_plugin_vidcap);
// monitor is initialized
sc->monitor_initialized_ = true;
sc->monitor_initialization_.notify_all();
#endif
}
bool ScreenCapture::initialized()
{
return ScreenCapture::manager().monitor_initialized_;
}
void ScreenCapture::reload()
{
if ( !ScreenCapture::initialized() ) {
// relaunch monitor
std::thread(launchMonitoring, this).detach();
// wait for monitor initialization (if not already initialized)
std::mutex mtx;
std::unique_lock<std::mutex> lck(mtx);
ScreenCapture::manager().monitor_initialization_.wait(lck, ScreenCapture::initialized);
}
}
int ScreenCapture::numWindow()
{
reload();
access_.lock();
int ret = handles_.size();
access_.unlock();
return ret;
}
bool ScreenCapture::exists(const std::string &window)
{
reload();
access_.lock();
auto h = std::find_if(handles_.cbegin(), handles_.cend(), hasScreenCaptureName(window));
bool ret = (h != handles_.cend());
access_.unlock();
return ret;
}
std::string ScreenCapture::name(int index)
{
std::string ret = "";
access_.lock();
if (index > -1 && index < (int) handles_.size())
ret = handles_[index].name;
access_.unlock();
return ret;
}
std::string ScreenCapture::description(int index)
{
std::string ret = "";
access_.lock();
if (index > -1 && index < (int) handles_.size())
ret = handles_[index].pipeline;
access_.unlock();
return ret;
}
GstToolkit::PipelineConfigSet ScreenCapture::config(int index)
{
GstToolkit::PipelineConfigSet ret;
access_.lock();
if (index > -1 && index < (int) handles_.size())
ret = handles_[index].configs;
access_.unlock();
return ret;
}
int ScreenCapture::index(const std::string &window)
{
int i = -1;
access_.lock();
auto h = std::find_if(handles_.cbegin(), handles_.cend(), hasScreenCaptureName(window));
if (h != handles_.cend())
i = std::distance(handles_.cbegin(), h);
access_.unlock();
return i;
}
ScreenCaptureSource::ScreenCaptureSource(uint64_t id) : StreamSource(id), failure_(FAIL_NONE)
{
// set symbol
symbol_ = new Symbol(Symbol::SCREEN, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
ScreenCaptureSource::~ScreenCaptureSource()
{
unsetWindow();
}
void ScreenCaptureSource::unsetWindow()
{
// unregister this device source from a Device handler
auto h = std::find_if(ScreenCapture::manager().handles_.begin(), ScreenCapture::manager().handles_.end(), hasAssociatedSource(this));
if (h != ScreenCapture::manager().handles_.end())
{
// remove this pointer to the list of connected sources
h->associated_sources.remove(this);
// if this is the last source connected to the device handler
// the stream will be removed by the ~StreamSource destructor
// and the device handler should not keep reference to it
if (h->associated_sources.empty())
// otherwise just cancel the reference to the stream
h->stream = nullptr;
// else this means another DeviceSource is using this stream
// and we should avoid to delete the stream in the ~StreamSource destructor
else
stream_ = nullptr;
}
window_ = "";
}
void ScreenCaptureSource::reconnect()
{
// remember name
std::string d = window_;
// disconnect
unsetWindow();
// connect
setWindow(d);
}
void ScreenCaptureSource::setWindow(const std::string &windowname)
{
if (window_.compare(windowname) == 0)
return;
// instanciate and wait for monitor initialization if not already initialized
ScreenCapture::manager().reload();
// if changing device
if (!window_.empty())
unsetWindow();
// if the stream referenced in this source remains after unsetDevice
if (stream_) {
delete stream_;
stream_ = nullptr;
}
// remember device name
window_ = windowname;
// check existence of a window handle with that name
auto h = std::find_if(ScreenCapture::manager().handles_.begin(), ScreenCapture::manager().handles_.end(), hasScreenCaptureName(window_));
// found a device handle
if ( h != ScreenCapture::manager().handles_.end()) {
// find if a DeviceHandle with this device name already has a stream that is open
if ( h->stream != nullptr ) {
// just use it !
stream_ = h->stream;
// reinit to adapt to new stream
init();
}
else {
// start filling in the gstreamer pipeline
std::ostringstream pipeline;
pipeline << h->pipeline;
// test the device and get config
GstToolkit::PipelineConfigSet confs = h->configs;
#ifdef SCREENCAPTURE_DEBUG
g_printerr("\nScreenCapture '%s' added with configs:\n", h->pipeline.c_str());
for( GstToolkit::PipelineConfigSet::iterator it = confs.begin(); it != confs.end(); ++it ){
float fps = static_cast<float>((*it).fps_numerator) / static_cast<float>((*it).fps_denominator);
g_printerr(" - %s %s %d x %d %.1f fps\n", (*it).stream.c_str(), (*it).format.c_str(), (*it).width, (*it).height, fps);
}
#endif
if (!confs.empty()) {
GstToolkit::PipelineConfig best = *confs.rbegin();
float fps = static_cast<float>(best.fps_numerator) / static_cast<float>(best.fps_denominator);
Log::Info("ScreenCapture %s selected its optimal config: %s %s %dx%d@%.1ffps", window_.c_str(), best.stream.c_str(), best.format.c_str(), best.width, best.height, fps);
pipeline << " ! " << best.stream;
if (!best.format.empty())
pipeline << ",format=" << best.format;
pipeline << ",framerate=" << best.fps_numerator << "/" << best.fps_denominator;
// convert (force alpha to 1)
pipeline << " ! alpha alpha=1 ! queue ! videoconvertscale";
// delete and reset render buffer to enforce re-init of StreamSource
if (renderbuffer_)
delete renderbuffer_;
renderbuffer_ = nullptr;
// new stream
stream_ = h->stream = new Stream;
// open gstreamer
h->stream->open( pipeline.str(), best.width, best.height);
h->stream->play(true);
}
}
// reference this source in the handle
h->associated_sources.push_back(this);
// will be ready after init and one frame rendered
ready_ = false;
}
else {
trash();
Log::Warning("No window named '%s'", window_.c_str());
}
}
void ScreenCaptureSource::setActive (bool on)
{
bool was_active = active_;
// try to activate (may fail if source is cloned)
Source::setActive(on);
if (stream_) {
// change status of stream (only if status changed)
if (active_ != was_active) {
// activate a source if any of the handled device source is active
auto h = std::find_if(ScreenCapture::manager().handles_.begin(), ScreenCapture::manager().handles_.end(), hasAssociatedSource(this));
if (h != ScreenCapture::manager().handles_.end()) {
bool streamactive = false;
for (auto sit = h->associated_sources.begin(); sit != h->associated_sources.end(); ++sit) {
if ( (*sit)->active_ )
streamactive = true;
}
stream_->enable(streamactive);
}
}
}
}
void ScreenCaptureSource::accept(Visitor& v)
{
StreamSource::accept(v);
v.visit(*this);
}
Source::Failure ScreenCaptureSource::failed() const
{
if ( StreamSource::failed() )
return FAIL_CRITICAL;
return failure_;
}
glm::ivec2 ScreenCaptureSource::icon() const
{
return glm::ivec2(ICON_SOURCE_DEVICE_SCREEN);
}
std::string ScreenCaptureSource::info() const
{
return "Screen capture";
}
#if defined(LINUX)
//int X11_error_handler(Display *d, XErrorEvent *e)
//{
//#ifdef SCREENCAPTURE_DEBUG
// char msg[80];
// XGetErrorText(d, e->error_code, msg, sizeof(msg));
// g_printerr("\nX11 Error %d (%s): request %d.%d on xid %lu\n",
// e->error_code, msg, e->request_code,
// e->minor_code, e->resourceid);
//#endif
// if (e->request_code == X_GetWindowAttributes || e->request_code == X_GetImage)
// ScreenCapture::manager().remove(SCREEN_CAPTURE_NAME, e->resourceid);
// return 0;
//}
xcb_atom_t getatom(xcb_connection_t* c, const char *atom_name)
{
xcb_intern_atom_cookie_t atom_cookie;
xcb_atom_t atom = 0;
xcb_intern_atom_reply_t *rep;
atom_cookie = xcb_intern_atom(c, 0, strlen(atom_name), atom_name);
rep = xcb_intern_atom_reply(c, atom_cookie, NULL);
if (NULL != rep) {
atom = rep->atom;
free(rep);
}
return atom;
}
std::map<unsigned long,std::string> getListX11Windows()
{
std::map<unsigned long,std::string> windowlist;
xcb_generic_error_t *e;
xcb_connection_t* c = xcb_connect(NULL, NULL);
static xcb_atom_t net_client_list = getatom(c, "_NET_CLIENT_LIST");
static xcb_atom_t net_wm_name = getatom(c, "_NET_WM_NAME");
xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
xcb_get_property_cookie_t prop_cookie_list;
xcb_get_property_reply_t *reply_prop_list;
prop_cookie_list = xcb_get_property(c, 0, screen->root, net_client_list, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
reply_prop_list = xcb_get_property_reply(c, prop_cookie_list, &e);
if(e) {
free(e);
}
if(reply_prop_list) {
int value_len = xcb_get_property_value_length(reply_prop_list);
if(value_len) {
xcb_window_t* win = (xcb_window_t*) xcb_get_property_value(reply_prop_list);
for(int i=0; i<value_len/4; i++) {
xcb_get_property_cookie_t prop_cookie = xcb_get_property(c, 0, win[i], net_wm_name, XCB_GET_PROPERTY_TYPE_ANY, 0, 1000);
xcb_get_property_reply_t *reply_prop = xcb_get_property_reply(c, prop_cookie, &e);
if(e) {
free(e);
}
if(reply_prop) {
int reply_len = xcb_get_property_value_length(reply_prop);
if( reply_len > 0) {
char windowname[1000];
snprintf(windowname, reply_len + 1, "%s", (char *) xcb_get_property_value(reply_prop) );
if (isalpha(windowname[0]))
windowlist[ win[i] ] = std::string ( windowname );
}
free(reply_prop);
}
}
}
free(reply_prop_list);
}
return windowlist;
}
#endif

110
src/ScreenCaptureSource.h Normal file
View File

@@ -0,0 +1,110 @@
#ifndef SCREENCAPTURESOURCE_H
#define SCREENCAPTURESOURCE_H
#include <string>
#include <vector>
#include <set>
#include <map>
#include "GstToolkit.h"
#include "StreamSource.h"
#define SCREEN_CAPTURE_NAME "Screen Capture"
class ScreenCaptureSource : public StreamSource
{
friend class ScreenCapture;
public:
ScreenCaptureSource(uint64_t id = 0);
~ScreenCaptureSource();
// Source interface
Failure failed() const override;
void accept (Visitor& v) override;
void setActive (bool on) override;
// StreamSource interface
Stream *stream() const override { return stream_; }
// specific interface
void setWindow(const std::string &windowname);
inline std::string window() const { return window_; }
void reconnect();
glm::ivec2 icon() const override;
inline std::string info() const override;
protected:
void unplug() { failure_ = FAIL_CRITICAL; }
void trash() { failure_ = FAIL_FATAL; }
private:
std::string window_;
std::atomic<Source::Failure> failure_;
void unsetWindow();
};
struct ScreenCaptureHandle {
std::string name;
std::string pipeline;
unsigned long id;
GstToolkit::PipelineConfigSet configs;
Stream *stream;
std::list<ScreenCaptureSource *> associated_sources;
ScreenCaptureHandle() : id(0), stream(nullptr) {}
void update(const std::string &newname);
};
class ScreenCapture
{
friend class ScreenCaptureSource;
ScreenCapture();
~ScreenCapture();
ScreenCapture(ScreenCapture const& copy) = delete;
ScreenCapture& operator=(ScreenCapture const& copy) = delete;
public:
static ScreenCapture& manager()
{
// The only instance
static ScreenCapture _instance;
return _instance;
}
int numWindow () ;
std::string name (int index) ;
std::string description (int index) ;
GstToolkit::PipelineConfigSet config (int index) ;
int index (const std::string &window);
bool exists (const std::string &window);
void reload ();
void add (const std::string &windowname, const std::string &pipeline, unsigned long id=0);
void remove(const std::string &windowname, unsigned long id=0);
private:
static void launchMonitoring(ScreenCapture *d);
static bool initialized();
static bool need_refresh();
std::mutex access_;
std::vector< ScreenCaptureHandle > handles_;
std::condition_variable monitor_initialization_;
bool monitor_initialized_;
std::mutex monitor_access_;
};
#endif // SCREENCAPTURESOURCE_H

View File

@@ -33,6 +33,7 @@
#include "StreamSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "ScreenCaptureSource.h"
#include "NetworkSource.h"
#include "SrtReceiverSource.h"
#include "MultiFileSource.h"
@@ -432,6 +433,9 @@ void SessionLoader::load(XMLElement *sessionNode)
else if ( std::string(pType) == "DeviceSource") {
load_source = new DeviceSource(id_xml_);
}
else if ( std::string(pType) == "ScreenCaptureSource") {
load_source = new ScreenCaptureSource(id_xml_);
}
else if ( std::string(pType) == "NetworkSource") {
load_source = new NetworkSource(id_xml_);
}
@@ -615,6 +619,9 @@ Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, Mode mode)
else if ( std::string(pType) == "DeviceSource") {
load_source = new DeviceSource(id__);
}
else if ( std::string(pType) == "ScreenCaptureSource") {
load_source = new ScreenCaptureSource(id__);
}
else if ( std::string(pType) == "NetworkSource") {
load_source = new NetworkSource(id__);
}
@@ -1212,6 +1219,16 @@ void SessionLoader::visit (DeviceSource& s)
}
void SessionLoader::visit (ScreenCaptureSource& s)
{
std::string winname = std::string ( xmlCurrent_->Attribute("window") );
// change only if different window
if ( winname != s.window() )
s.setWindow(winname);
}
void SessionLoader::visit (NetworkSource& s)
{
std::string connect = std::string ( xmlCurrent_->Attribute("connection") );

View File

@@ -60,6 +60,7 @@ public:
void visit (RenderSource& s) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (ScreenCaptureSource& s) override;
void visit (NetworkSource& s) override;
void visit (MultiFileSource& s) override;
void visit (GenericStreamSource& s) override;

View File

@@ -38,6 +38,7 @@ using namespace tinyxml2;
#include "SessionSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "ScreenCaptureSource.h"
#include "NetworkSource.h"
#include "SrtReceiverSource.h"
#include "MultiFileSource.h"
@@ -845,6 +846,12 @@ void SessionVisitor::visit (DeviceSource& s)
xmlCurrent_->SetAttribute("device", s.device().c_str() );
}
void SessionVisitor::visit (ScreenCaptureSource& s)
{
xmlCurrent_->SetAttribute("type", "ScreenCaptureSource");
xmlCurrent_->SetAttribute("window", s.window().c_str() );
}
void SessionVisitor::visit (NetworkSource& s)
{
xmlCurrent_->SetAttribute("type", "NetworkSource");

View File

@@ -65,6 +65,7 @@ public:
void visit (RenderSource&) override;
void visit (PatternSource& s) override;
void visit (DeviceSource& s) override;
void visit (ScreenCaptureSource& s) override;
void visit (NetworkSource& s) override;
void visit (MixingGroup& s) override;
void visit (MultiFileSource& s) override;

View File

@@ -15,14 +15,14 @@
#define ICON_SOURCE_VIDEO 18, 13
#define ICON_SOURCE_IMAGE 4, 9
#define ICON_SOURCE_DEVICE_SCREEN 19, 1
#define ICON_SOURCE_DEVICE_SCREEN 0, 2
#define ICON_SOURCE_DEVICE 2, 14
#define ICON_SOURCE_SEQUENCE 3, 9
#define ICON_SOURCE_NETWORK 18, 11
#define ICON_SOURCE_PATTERN 11, 5
#define ICON_SOURCE_SESSION 19, 6
#define ICON_SOURCE_GROUP 10, 6
#define ICON_SOURCE_RENDER 0, 2
#define ICON_SOURCE_RENDER 19, 1
#define ICON_SOURCE_CLONE 9, 2
#define ICON_SOURCE_GSTREAMER 16, 16
#define ICON_SOURCE_SRT 14, 5

View File

@@ -71,6 +71,7 @@
#include "MediaSource.h"
#include "PatternSource.h"
#include "DeviceSource.h"
#include "ScreenCaptureSource.h"
#include "MultiFileSource.h"
#include "ShmdataBroadcast.h"
#include "VideoBroadcast.h"
@@ -2383,7 +2384,7 @@ void UserInterface::RenderHelp()
ImGui::Text ("Connected webcam or frame grabber.");
ImGui::NextColumn();
ImGuiToolkit::Icon(ICON_SOURCE_DEVICE_SCREEN); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Screen"); ImGui::NextColumn();
ImGui::Text ("Screen capture.");
ImGui::Text ("Screen capture of the entire screen or a selected window.");
ImGui::NextColumn();
ImGuiToolkit::Icon(ICON_SOURCE_NETWORK); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Shared"); ImGui::NextColumn();
ImGui::Text ("Connected stream from another vimix in the local network (shared output stream).");
@@ -2405,8 +2406,8 @@ void UserInterface::RenderHelp()
ImGui::Text(ICON_FA_SYNC); ImGui::NextColumn();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Internal");ImGui::PopFont();
ImGui::NextColumn();
ImGuiToolkit::Icon(ICON_SOURCE_RENDER); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Rendering"); ImGui::NextColumn();
ImGui::Text ("Displays the rendering output as a source, with or without loopback.");
ImGuiToolkit::Icon(ICON_SOURCE_RENDER); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Loopback"); ImGui::NextColumn();
ImGui::Text ("Loopback the rendering output as a source, with or without recursion.");
ImGui::NextColumn();
ImGuiToolkit::Icon(ICON_SOURCE_CLONE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Clone"); ImGui::NextColumn();
ImGui::Text ("Clones the frames of a source into another one, and applies a filter on the way.");
@@ -2639,6 +2640,7 @@ void Navigator::clearNewPannel()
pattern_type = -1;
custom_pipeline = false;
custom_connected = false;
custom_screencapture = false;
sourceSequenceFiles.clear();
sourceMediaFileCurrent.clear();
new_media_mode_changed = true;
@@ -3661,32 +3663,59 @@ void Navigator::RenderNewPannel()
ImGui::Text("Input device or stream:");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##External", "Select "))
if (ImGui::BeginCombo("##ExternalConnected", "Select "))
{
// 1. Loopback source
if ( ImGui::Selectable("Display Loopback") ) {
custom_connected = false;
custom_screencapture = false;
new_source_preview_.setSource( Mixer::manager().createSourceRender(), "Display Loopback");
}
// 2. Screen capture (open selector if more than one window)
if (ScreenCapture::manager().numWindow() > 0) {
std::string namewin = ScreenCapture::manager().name(0);
if (ImGui::Selectable(namewin.c_str()) ) {
custom_connected = false;
if (ScreenCapture::manager().numWindow() > 1) {
new_source_preview_.setSource();
custom_screencapture = true;
}
else {
new_source_preview_.setSource( Mixer::manager().createSourceScreen(namewin), namewin);
custom_screencapture = false;
}
}
}
// 3. Network connected SRT
if ( ImGui::Selectable("SRT Broadcast") ) {
new_source_preview_.setSource();
custom_connected = true;
custom_screencapture = false;
}
// 4. Devices
ImGui::Separator();
for (int d = 0; d < Device::manager().numDevices(); ++d){
std::string namedev = Device::manager().name(d);
if (ImGui::Selectable( namedev.c_str() )) {
custom_connected = false;
custom_screencapture = false;
new_source_preview_.setSource( Mixer::manager().createSourceDevice(namedev), namedev);
}
}
// 5. Network connected vimix
for (int d = 1; d < Connection::manager().numHosts(); ++d){
std::string namehost = Connection::manager().info(d).name;
if (ImGui::Selectable( namehost.c_str() )) {
custom_connected = false;
custom_screencapture = false;
new_source_preview_.setSource( Mixer::manager().createSourceNetwork(namehost), namehost);
}
}
if ( ImGui::Selectable("Display Loopback") ) {
new_source_preview_.setSource( Mixer::manager().createSourceRender(), "Display Loopback");
}
if ( ImGui::Selectable("SRT Broadcaster") ) {
new_source_preview_.setSource();
custom_connected = true;
}
ImGui::EndCombo();
}
@@ -3697,11 +3726,11 @@ void Navigator::RenderNewPannel()
Device::manager().reload();
ImGui::SameLine();
ImGuiToolkit::HelpToolTip("Create a source capturing video streams from connected devices or machines;\n"
ICON_FA_CARET_RIGHT " webcams or frame grabbers\n"
ICON_FA_CARET_RIGHT " screen capture\n"
ICON_FA_CARET_RIGHT " vimix display loopback\n"
ICON_FA_CARET_RIGHT " vimix Peer-to-peer in local network\n"
ICON_FA_CARET_RIGHT " broadcasted with SRT over network.");
ICON_FA_CARET_RIGHT " vimix display loopback\n"
ICON_FA_CARET_RIGHT " screen capture\n"
ICON_FA_CARET_RIGHT " broadcasted with SRT over network.\n"
ICON_FA_CARET_RIGHT " webcams or frame grabbers\n"
ICON_FA_CARET_RIGHT " vimix Peer-to-peer in local network.");
if (custom_connected) {
@@ -3782,6 +3811,22 @@ void Navigator::RenderNewPannel()
ImGui::PopStyleColor(3);
}
if (custom_screencapture) {
ImGui::Text("\nWindow:");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if (ImGui::BeginCombo("##ScreenCaptureSelect", "Select ", ImGuiComboFlags_HeightLarge))
{
for (int d = 0; d < ScreenCapture::manager().numWindow(); ++d){
std::string namewin = ScreenCapture::manager().name(d);
if (ImGui::Selectable( namewin.c_str() )) {
new_source_preview_.setSource( Mixer::manager().createSourceScreen(namewin), namewin);
}
}
ImGui::EndCombo();
}
}
}
ImGui::NewLine();

View File

@@ -57,6 +57,7 @@ class Navigator
int pattern_type;
bool custom_pipeline;
bool custom_connected;
bool custom_screencapture;
void clearButtonSelection();
void clearNewPannel();
void applyButtonSelection(int index);

View File

@@ -33,6 +33,7 @@ class MediaSource;
class StreamSource;
class PatternSource;
class DeviceSource;
class ScreenCaptureSource;
class GenericStreamSource;
class SrtReceiverSource;
class SessionFileSource;
@@ -107,6 +108,7 @@ public:
virtual void visit (SrtReceiverSource&) {}
virtual void visit (GenericStreamSource&) {}
virtual void visit (DeviceSource&) {}
virtual void visit (ScreenCaptureSource&) {}
virtual void visit (PatternSource&) {}
virtual void visit (SessionFileSource&) {}
virtual void visit (SessionGroupSource&) {}