From f8df2d6ae9255982e294166aedd9972706b6b310 Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Wed, 6 May 2026 18:50:06 +0700 Subject: [PATCH] fix(recording): soften native mic gap boundaries --- .../bin/win32-x64/helpers-manifest.json | 66 +++++++-------- electron/native/bin/win32-x64/wgc-capture.exe | Bin 101888 -> 104448 bytes electron/native/wgc-capture/src/main.cpp | 26 +++++- .../wgc-capture/src/wasapi_loopback.cpp | 78 ++++++++++++++++-- .../native/wgc-capture/src/wasapi_loopback.h | 8 ++ 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/electron/native/bin/win32-x64/helpers-manifest.json b/electron/native/bin/win32-x64/helpers-manifest.json index 3e34d6ed..dcc4ca13 100644 --- a/electron/native/bin/win32-x64/helpers-manifest.json +++ b/electron/native/bin/win32-x64/helpers-manifest.json @@ -1,35 +1,35 @@ { - "version": 1, - "platform": "win32", - "arch": "x64", - "helpers": { - "wgc-capture": { - "binaryName": "wgc-capture.exe", - "binarySha256": "31beb9528e79262da6aa31fef09b6125153a838f957060ccc1ec5d4da844516c", - "sourceDir": "electron/native/wgc-capture", - "sourceFingerprint": "4bb09b770a21eb32daf6325d46d73d0522f20a4cbce9dda8b3df2277f8cfd0be", - "updatedAt": "2026-05-05T18:41:50.078Z" - }, - "cursor-monitor": { - "binaryName": "cursor-monitor.exe", - "binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12", - "sourceDir": "electron/native/cursor-monitor", - "sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e", - "updatedAt": "2026-05-05T18:42:26.007Z" - }, - "recordly-gpu-export": { - "binaryName": "recordly-gpu-export.exe", - "binarySha256": "e79cb823dd2cfade857db67696ae6456ae0ba3ae9381b6792c948ac648521bd4", - "sourceDir": "electron/native/gpu-export-probe", - "sourceFingerprint": "45aaa4c0cbeb45ecaa9ff0a87b870c33bd0d5b09fb5b3acef45b246970a12e7f", - "updatedAt": "2026-05-05T18:41:54.966Z" - }, - "recordly-nvidia-cuda-compositor": { - "binaryName": "recordly-nvidia-cuda-compositor.exe", - "binarySha256": "2913d78e2c1114d32c92fb4238749f7ff46131ec92cf3ad0b55af910c7cdbb07", - "sourceDir": "electron/native/nvidia-cuda-compositor", - "sourceFingerprint": "a60538981da7dd3d9645f59926d42f4f2f0f1f0663d3905e0416fdff8becb314", - "updatedAt": "2026-05-05T18:42:20.123Z" - } - } + "version": 1, + "platform": "win32", + "arch": "x64", + "helpers": { + "wgc-capture": { + "binaryName": "wgc-capture.exe", + "binarySha256": "b016da7435ba68b9c235e85818b5f4e8c1647f2386b4f2205e9a690cd057f908", + "sourceDir": "electron/native/wgc-capture", + "sourceFingerprint": "7ef55b0d4bb9674de2b263e9dad2de2a593d0cb6aa09aea9ab3dc1832010a9d0", + "updatedAt": "2026-05-06T11:48:48.808Z" + }, + "cursor-monitor": { + "binaryName": "cursor-monitor.exe", + "binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12", + "sourceDir": "electron/native/cursor-monitor", + "sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e", + "updatedAt": "2026-05-05T18:42:26.007Z" + }, + "recordly-gpu-export": { + "binaryName": "recordly-gpu-export.exe", + "binarySha256": "e79cb823dd2cfade857db67696ae6456ae0ba3ae9381b6792c948ac648521bd4", + "sourceDir": "electron/native/gpu-export-probe", + "sourceFingerprint": "45aaa4c0cbeb45ecaa9ff0a87b870c33bd0d5b09fb5b3acef45b246970a12e7f", + "updatedAt": "2026-05-05T18:41:54.966Z" + }, + "recordly-nvidia-cuda-compositor": { + "binaryName": "recordly-nvidia-cuda-compositor.exe", + "binarySha256": "2913d78e2c1114d32c92fb4238749f7ff46131ec92cf3ad0b55af910c7cdbb07", + "sourceDir": "electron/native/nvidia-cuda-compositor", + "sourceFingerprint": "a60538981da7dd3d9645f59926d42f4f2f0f1f0663d3905e0416fdff8becb314", + "updatedAt": "2026-05-05T18:42:20.123Z" + } + } } diff --git a/electron/native/bin/win32-x64/wgc-capture.exe b/electron/native/bin/win32-x64/wgc-capture.exe index bdbbdcc4e2d1c4e31dbc5b84b0c24c20b2481856..54477a6ca0cdb7804643082f3b3c0f1b9d352ecb 100644 GIT binary patch delta 40968 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 SOBN*k$&%#04;D10W$4fV delta 38187 zcmb5X30zgh8#jK=0rZOC1?3|9j-Vj2%i?lTkb{Ea8kP%|qNOP+D6SU;RJ>8-s8_Aj zEX%BvvRtwSmju%iOmqDi?T2WgGM6&%|NG22SI~Oj&->@|ne)s%^UO2PJoC&m%b9a2 zt?5wue23~%)7-*7WeKin4Ym+s8Pu4FbciLRaUh53jS(Cg8{+_%KW=R7%V9c){T@Ht z;D~gBtFhr5K>fz_#%K;dY>4M@a3jG{k3Tkp>JaY#dp5dLuiOkln7XQi(9`d)$8EW- zLXgl|>JlN`0OolWJL?w~D;D%#|A;8OF9B663QZzljVRPOt0er{APPyqSN9YJpJxNT z#a#B4*iFjo_-vpTbCGm?S6>I23j*=B=SA*40t6vHYTnc-MNAAwu#YRz!Eh11H63+$H;GB*#UAX3&b{3JzV)x{CKl>6rpN2Zs8+VXg4Fh+oC~3{ z%pKL{0x{KBc_c$Xc9gyn9 z-~R+5+x&5s`COLyjI#58Y`#7^@aNA^G{|cB>MyP6qxVsb`5!y3A1$6|9xn6673?XO zY%!i)av3E3_6PHIEfh`cY1eeOQGf7O*@vjjI&C$C|AA(CGqLMRY4<5s(KSu#b&Az@ zohbbf$-=rl;kf=JHS?CT>Lh!vTfMaGB%9{8$TjM3U@Jv=#gSc5gVk_@{p#lDvh7!J zm(6y>T&{9Zn6Dv7_W_Vd3}BgtWNBYLTWJ_1HC<;X4Ex1hY?=EDQc4~3=`QQSA=+u> z#}jN;cdu^M=c(2)Av1lD*@wz>NdDd#$K4KSph<1F)G^>U@1Y(6HJ9S?Zq z*lGNsrtyTXOLKu>$WGG%mB=YY*?i;}OY`y<|6nWpy(2yTyS(ea%m4BR3-<_Pa&J@sM%?gCQq99$T7JpJ0w?eVy<41_Eu<|#d!Eh zpZ-SjO3Ti&goq^uGppp}aSPhGRYSUW>aIA*D%ok|*6tZQ$o{{tip=3+!aW#jr8{5z?DKQNQaZehQ{l4o3EZe^}@Yv`4NQt&=O$Ljv><)ykEf z$z}rdu9DJKCH(Uz*6iOy@>NTJdYYHs#Ciw#i%BdeV3l}@oeStIb^4Au2M&-jK43Y4 zzEV&#n-!SStAMC&`W;P)x{k0V;3kAblbudJR#Cg?N2}pU))43??RcNH1}>C(zRw;H z8Yz8on7tpAAw6@L*#&1v@?kbK*xx7k2i^fteYk9PmCX|ODt4b(4V7QPWf{7c9S_Hl zM*}Mr)B|T!c0|{lb&|am9IP+tOI3HK4t7$yeZsy8E|d;6Q8D&Jh)eLcgQ}+oiPd0e z;$Ev;`zq$Dv~P!aiKXn*kcXwm53!!11EkRJ*yEvPqQZU-ogkXo&>kCfZ+=65>-7Wd zhaO3~CwXG#0TvdPBE9x8dn{~#e#OU-rOG)P?UAbt5v(SxvE#7s(RXW)vo+zPg5LfL z3}_~K^IbtO>i3^$kz#9Qb5#wgFYdH*bU(WjUL%IHH+zl}&#&vXRwqH?(~9s8Ti2_v z?%0=ukxxB z)Hq8_2o&FBc?pTG?q`U>DLzfe*3f6pux$x((ucd)xr7X9;V$N#7!>*|pK@-)kj$6K zl$723v5Y$=7CFD?L5+qhiQdH?O&s5GJfv2l4zQz%xg8~nU)aw)`%W|#V2M-gESm@E zDl*v7wO3^_}Kaw?rzJw zY<5y_>Cc_)g(PpE@|{?RKP|=1U_L3~A#3<(H@t}2m8&Pw*Hwm{=c&uFt-9 zQ|c&b(#tGAwU78St4^I8`I3_e(}puq{(#X!n4e@ddG;#j)XmwJ|9K7T-EXj8zz#u( ztCi~pdytcmD+jw2U>@g_xYb}~Pxnji_#L`TN!!jo>*v|=JSjPBJNuDV5YH6YOYl|6il=_gx{Wmjd0 z*%8NJ3~A;I%IZC=V!$vzxpG2??jdsW@WHbA6D9R?-gK;ctcE&vcR-Z=raDZF#(_rZ z?LBPez+mwi_R7FS7aAaZ@}7u@)!@p08tAJJMs2*iPN2J7_An1)566x#5bl<8`*|iC zzcgjQ!OLccf?VjOqP92&V@HAyRt~(ui$eKjl-xtfN{4|OnWJp}7|Cc#rhZ&J5@hul z$Er34Pz{(zqqEI*=Fd^TQgeYSxkwH_Up6Fg7Z^Z`^zs(eHZKjDz|E4s#GMsLZH6k#ST& zha8lv#$LqKyGz;nE!oR3Yz!KdK}~G9oTz&b-t)9_a5MWWJxljEPfXv;hGj&AZp6w| z<+$Hr9pY<<;Sm_(STM|~QF{CYX6(fw@$5{7gLHKh`ywMWB+ypK?BIte7vgwZnH?=! znT`iKpab$Yv4@9*1nz;?gS-c^0@#V@nvC~OWE{2?+OC*>V1b#59TTX;7Z!GPNT`1w zkf{ZF@&fOl$oQbGz&|V?v!67^!X{;g2D}T)BGLm6dl5r5p#89ob}?xCc8qoa@5YVn ztIV*BejwxRHajHqvb881r+}^@ZZs*khULh8b>T-zw>>tp z!mJ^>%SR}2aRd7>%h!G4iF4V}B1dO2v%z zALbhz3ksVZMsaG@4mGpcrMO;Z%Z3e@ zi{a1C$W@aIt%feV?6IP8z!=xWVTc|eX2*&Gc(ETS1=`BhYtS64;a5smuC7KRyK2N- zEShC<9;opCL&R1CR)$IQzxNOhqzl#qhz$z@Wde&c`3}mO=eR4@3KDf2&RN^NaXMVQ%0nS zqgZfmPaL`A>J%PaV*<+s|d`LUC@>+fQ zOq?!dSM~1zRb^L&cH|&I2k^VzB_WcDqxb8_V!DT2WWm6<)A_}T(J`U#_7|}dTjj77 zu(=bMK`EONua;MHNO>!n&zNEQosb-*`;rT^8n&?oW8%X9?gvWDx2qj)BL<^e?jonw zFYhcz)VIo_^G`s=)vj{*lr@eS{Lq@c4>s_7VA?mZ$Km@MxF0hx+E(#4b~Cbh8IGGK z)*4qO45YfMobv#b>#Nz!vC-XK(KNo)b*iLFa++v;8@ZOfJ@#P?5$ADnl3OYpG%n0^ z>tupC2C;GugRH_nn5k-}#2NAudTIHlk%Jk&{iR zU?SRhOx2W)C{HEoVk5fyE{LX_;6y<;1i{3W9;Z?{+o&!P)hv~2BB(e~7nR7vM)VF5 z{qQkowaG@*yq+te&R*-jXNc(RQBL%^jp#TL(Jp)LyWO$x_*|K%O5N?IR@KYv+0wk2 zPX%8XmG`L@+?0BbLDq!HY29ikK+dBTUiKyfTqR3BQ$zRwF0A7!1#2N|Yw zD%EwAnPv#ZK32m^Y|!{baTZ%Nek_hD&Ws;J#~2Uib*)_oE|n7%&DB^953#2o&KA$I zD-UOgcUa&f9xe;ZATS(N&Mh+N4dpEBk#RlG?^flC;j&zyl;+?MQyg+(nGY%7Wc81X z5Hpy^gcxx&8#=+uX9t{wTya*vW-}sL<~8JosC#h;)+X2HEoAD&4ar2J-@Lv=R5Yg!FCDj|7OmCN;=#N{Dl zHRltBZ1c8FqzSBGeZaEcJ<3l&$=AGALw8O_r}{RsgnMK!wUM>%yc(OzCt%iC|lY=e55E9K?RZ8td|JETQuQ*-}H=H(k@}x&59#h`=A8ozZhm&&* zk3)g9p@^2v*W}8jG|}~Vl?CLxCwvArSe<8?Kchv@$|^MIf^7aY+wP_u@g+7IR7=?h zR10eRyJ#lzl&{&${7CUrwkbbW+OnIS&d)P!BDpGOp{Fa0LS)(eht&}J3QL~i9dHmi z{M?vSuS|Uk6`Y5jv+e$rBmQSKTw+C20>ztb@+Wur{PB0_+HJ zIU~+mc3WD8ncDC+pAVc5zXZGZYzInJ87zCKXJ~2zve^wr8g~}k3)2#sdh}~4inD>S(Q;K4j_n0yGYfglXu1e3;Q#_9%C8htHPiX7$ywe3l1E@JTVEo8U52(@<3*oklo9CQ4j=nMwQ)dlvJFEoq22$ z7r73YmKgTUV_i*k!RowK4wfhqpcXkqMNV($5^NxIr-p_AiB_n!Kf`OMqrwdzTCE3B z8r4^E&A~bXm(tY!ds|+u_uC+N9>2z}y<)@n~h>Ljx)nu+BX_U^xK#nC?D9X%H z1uoqVJOfu~8|DvjhXd36?p>^*%@wsMMT1?_VnpXaQh%`%%IC9X(=zcU=i_NToxgxf z0!cf#ypf^>JaYJryJVr$ps6__aihTB5JL#LXrto%M%`qWMRZYckG_H9*7KFy~8%26s(;t448gwy3 z!*OwkaNI5Ze&-3{Sf-Yr)K?tgotkjkO9ZvU5RR@b3NF7w-J>e8W~Lw*%dJIyfZ%Ml z0U@^pwx`LNrP5F#PSXzvx$<4!Q+UU)W4_u;0!HeJ{;#4o9H5!>> z?ZvI=Gi4%ZA-*NRT4*a;N71bE2g9~6)?dA9$EhJg*$#EVC5MY}! zCPc2zuBssY@p=2yZ=^9RoevJy@sts!SAyj_6T0Y2D{;kbaGZom& z3+O9g5WpCAsfAprP>5RSXSjJ@=~5ITPstT-fsgQ{6gQByNU8Qw)X{2i+QxpLIoxyy zLlzWCN4c$Fc;#{V4Vf$}T*a;zXpEu8U;MynN`$i8rUpK9%mG)`t$s)AAKm?wJ;6^~r3@j$D#L-7wB~B* zj%qc9q>)n=oAmk0Hi}mn*4W6qqd59kPg{HvztludU^lkQn*3aC@8J|mlm8=HO_-hY zQBIl98I$~+stBbtsot{K#1A9!hu%*(6YMm?HAGltxJ~mo`R6qz6|q^Z=511wvrYaD zr4UlNwvjiX&Q@d`ugiHgNUa7Di9=ICW;I+x&ylwrP7dM_3>q^`A)@J61LYm+K&L3U`h!c)~7CYVIiHo<0PRDJYqUvd}rF{l1|EB6;0sftIWC@Mis+CY%@3 zoAtUmOkbGbGHSUX;0)hO2zY&9%_F#J9Hon`H*ep$wL7e?u( zeu`C_^7LueRM_YjJX@=3)xWFiIGdfB8!GKs%5Kb+yPd`GZ3(v;PCx~xv&>&8nM>Ko zd1<=cr-YVHb0&NEW9>S!sHKaj!L(@EMXSj9l&o|(u94wb`rn&7x+n8 zi&)@-A?^uO?#B{axhN|4RS8?Rz(+zY&n<}Sasopl$9w{6t5@D$$F44TM~d9R)-4Q@ zb}VH37gl#)^&(fZ(q$na#KD`HQ%e7ZY+^~Mhr6aD>&B z_`7}&C1>I7x;aOo<$mT2cC#c}I`71KF7lBUda}$#e*H2rTM*L(v@v2Mgloc-ueQ-? zJCYYDS?Lg8+_RUT%KFD8hA5?lbu@{#P>=OA59~on7v2b57kNvWAK;NdPfl{tL3gbvFt}RHf zEN;MotJ0CIrLwq*m#aG?%zqEW_#59PueOXs_Gu zuudy_>rX)8_evevz%o`0Nt}YgRraG;+^w3w3{;9IVI8bpJE=HZ(pAMyZilrR{;6a~ zRv7(HT_#;D(ZPKU)1k?Sk$x`#I;AfAeIg53X^>V|vc4;44E&Bpmf80U0@&nw&^8h; zzp?*5WzE&K%2wxN$Z83u*dfHSF`!H6=o3TRdAkd{wz7v*&sc}Do>CQKF=cMUO=t_I z54jB1^~g$6z2RFsLc#a{u~OY1BHDx~vN}Bt3d-7qbarK;jKy3DX%pU~L`l8GSXrdx ze2E^_mB^=x=~4>6B9l=YH)*RdDN-G=wY zDG(&SxB?QllyZrSBu(NnAZbSn2@4U0#G9YeBo0UVUlO0X!0aj_#TQs@nU7>V%Z69H zFy!Gp@VJP(2*XhP<#oI=n&^~WCSF;Ls8wbJQae=WjVP$cnYGHkGqIReX+dA3wAG~= zyd31p)Rtwm+s~!$$d%36Ax7oULu~yjqqM7-RdHI?Eyc4@U= zuXkznY2#6dc@8|9F}jtdOF55>H5!j|EO1RXHm1@`oWw?~@skFA&Yr2Pb6>7Q-(ixF zVO9=vDPeYgRL5qm9p~E>DYfr(h*971_C>?oe(lJ|4ycZUK{4kKrVQal=b z%(vjD_2u6I2`4;zE!jyCPG>4RQdiB0J1JLA#;q8lKlLclXNSz7dm}w%vq&v2ggRZ; zvR|GWC~Z5#`c(OLD?I~+CZmAbsu{|hGi-8|k2LbkzGYRNMDcsJ`so$Yxz)_^nOI4; zmJN6&8}}2<&-9SIKV|!#ndE%}IQ&uzQb)%#=%h{B5{z2KFkTr~@ z^=_K=3#eqxa58laT69}Zt`hkP+p#X)@I7AsXI1*ObfFx&5LK^yevG$IQ(sTu^%vO$SPx;h7jL9}^F;7Rr->(%w$V`^L9 z)ZorzteIsnN!auulK`Fefj!euLzFD)g?Lg7T$@K4HSxWbcEQg zo*iBvC{}~0yJHDvV;Xo3cxS)fJ>(#iCRfJM6(o$vqL4hyMA4JUnkF4pMHqLKC2fe5 zbcSfl&g1c`Iu_%jQaPC8NA_QZra_f^FaT6VZd0j~%Y?ZxVODCnz zLu`&EM7p$-ZLmBeu_3I-rZLjSOty5BQ5uuUKHQWhInQU}=4#0_gt5($QqDYfWOG)0 zhavnr1+Gw34u&byxh>s?v>KY{LJ->@ze!c)>TbN!PyVbc$X(Fxmc#pek! zF~iOl0CLjr;3Psj27VDyWPMVsWsQG=eem29?$Lt;;m9xk#?l*NiyaO@Xii)@i1mLy z%-J47X*J#+#O6L9A>}u-t_;rpa-81{?cAaNbQLZT%PNaOl!}gHJCx+xU2Q_?p^-!)yR+buA4fvn?xA&B}i; zEA61rJ4QsIjV9LcLYUOs#BRUfEd`jE`&NG+Jtwbe-6M6T)18d$MR*{nf03<*a6Fn3S`eUE1pHY2<`jRYb_wjfUjq%z2xKS0A2B zzBRjY+TQn26NJdwt3(cD1Gjle-Da~#w#Dj|0q8T*D#ol~%VPHYHh;g@7L!!YXA7X* z@1}9(&i+AD?y>@QXMu5b2H0?C47=(z_}9?!NSn zBsnCpcV6k~Y)-m2GD?%!kFN}odiQ0quO>?one53|L#4(vxt}TViEPJfWzuTK z!gi0BsuS4C-KkwhOu>#`dHh$}_`bA`o!Y(MZ!}J3`HWl54eY+l@O|Uiw%5I-tAp74 zucz2~#S1K@OQ?(0@Y7^eqPVXkHpXZ7MZK{{ly1eaYj1k#+#nua6vV9C~B)BZ2K=u-f7VwBCDXu0ReC2AfN z&YBM7dbfuCA7<3gN3&rEy_4U7l(;gH7G1U|Tque+j}KQSph)UPefbDDR=rDRfW9YI zD{*IOMG~MSBNI!miZHhAU~j2Dmwk57+o>#=q#uD7ghz7Op9cf6SsqbHvUIQ-y5+Kn zLqnwbquBgIu;3nS_n{!KyisV*A(}mu*Fq`E56iY9%6>UCK&lUAQSW*6{B|U=NVP8^ zgO<6KnL61H`(1h`S((Wf2a}H}zGZCgdzq4bJbUlGRetY;fR@(DE5SXxsxT}IzAB4+ zl}_0d!X_Q=E~Uk>g@+TRvJkfSa8Q>=a!Iun*i)WX_O4)G9)4BY9n99eALZs03s!bF zvnm(rnsHo?qrs^o*eCBlBE1m9!agXMy2r3LKS;uR$nQT0?3i&C4DjZ3z=s~D7x6;! zAgb5*EZn8G}iHCG0ke9b>PonK)<7#Dy*M^anZ? zl-;r`?z~R~>O{sup=HvcyH?xrI6#0*UR zSn40M=7yVZ(MmrN6lKef3HnuC5i8L6VPOJ^>QLwa!bgZ-KseLJ05_%h*}~Wh6bZ<# z%J~S_H4lJwoi*1(WnI(-x1=*Z5{q=LY1Y-7AQe*fYU+8U%9b5KwR;f5y5|K|c7wpvB*vU;oh;^3AVh^I!6jU%Tg+Gi7|bHKOpKe?`m4n^WwM{ODjlnuYI3 zQg1CVWJTP{&>uK1t6zug_Y{Qbs6WH}Q-=9Yx_ z+9pSwksEKzslVx0#lx^=$kq5`)<>9-=+8uFh7m_`vuc4umifyTd|%Xs2Y4tMV8BPLKXH7(W1dlZP`3FZskUp@Wy%@uG|#mnllbyOP9q*@?el8$z`{#Mah-R zOfU|UGNj7>NmK*Bx|9zHlhu$n1Z$}bQ)`x(X~ZiC+3qA#XxGi(2qm`-r?&C<21YN9 z$FvWKmwt5uZeuhjbq3pt8~XAb7!&r0iMWXlWsm}FS~p#8jzQ+oDj$=6e?=+Eretun zoEIWzQlVfs3UB$^_ce(Lac9lIYW{`fb_JW(ZNv{B_Wbr-)>&u&f)|A|Q4sr9? z=ji=fEvA`*vbzELiv2wHm%V=F8~qSXGk=+FE`Bx3jLjMiiJNlcU0C`2PD88gP0f+$ zbN#Abd~{&YXanYwe$`EZ^*ipH?v6%gSH-(#r~Z+xAM{6d>P`J>3Ah})8)WXcTl#0M zxT~6bR@LyZtklo+tG>^7{Msjd6? zQUQB5SWjQz_p<0lH+Cq)-NZV9&ktVIdCRtuv^YL_6|gVh-WJRN*J$8z^QQ&x2r$CN z93QKqnV^IT*2SGnH(yFCvqBl)L;LCGBlJyGT2PQA%(Zb@?jx4F1` zHh0SAr2j%cXioYdzNm|u)iBQsjd3eNx*6Lcj>?E&jb0&6CRrVr9+3Q&R*;r@XWkXmk!C%bo;voxyuHo# zUPbg{b!~3WAz5anNXcmQ2z?bjj6tbZRI0eV{ zP9X`n|C>|rK)&*XyRB0k$SI@_qco01tDF|%I%IYPeLRZ80tpM@2fRcxt#|*sSL~@* zh*3K)!8NY+ivNR-VkLNy8_~Lhbbf~G9B-U30n)_dZ@jnaB5hqu)9<|`+fA%P?w6$J z`48(4{GMSxpP{$g+gQ!Ef0AO7_|UI(lMB+U$IvlX=k1HLrdf}qr{)|$P4g;f=&1wf z&Q0De@YVl=l3@;TPk%n(s{j9W3w9I(j(evkLl+NrOek>ww~lcj{XOKSbxe#M@0cV? z|GQ&;4f`>yNgf5O)%Na(aFo zNG@lXKgI|-T2ReuPj$x{zM#@6x4>G*2ghY?WS~1fLRSm(UZXx~Hr(Ldq0?7*sNEsz zS9U=x%?x*{E|%ccv9RUC;P(6ukg=pCZNXslIHpWwHQwS&RV=nmSf}EYZksYp!PIPm zQ}B}`Zp(J?#M&xtQ6Mx@W$)k4`o84WcMuX`P~WI5Hl=-7iUhYp+g2jo`~}+edA8kH zGD6;-`J;JzKL!5Ohj^2~3)twv>6Y14r{8-_-ZV(R-^ryQE#fo%M*$Z`(2=^lzQCJFZM^g|d93Cu$m2w35y45s z;^AZx&SgosGO29r%W?jPXl}w8b5s&mU+U4GH=-ct*EuPxo0^UvtdSn;ORLPETj+}0 z2$g3vNR+`U%hQ-6ILj-b(^%Fc3ToR+L6f0YMSL|#qb)?*wu>v81jGHZc|$9fXoeB} zX%}8)_$Y@9PmGi+i6~WNm};wNK9E{P`6}O&=q4^ho|;}{t4D{l)zCFI1?O$H;gO=P zZX)}>k2A#63Y}ok&C`RtUEdTfOFz8JGS5Em(wL39a&fepLB~*^4`6?s?c=;92?bz@wfK#KqR6InH@HL)B#7?3ITNgCWsPNPmGcs$(hhb5*mQvng*T3k|YuzgC$Q~E-Hm4#FFdM~ z5=m!*BO7#ataRRyZN9kDbwwN<)6jKz{zcN_IQGVf9?}y7*_ca_-HVp#_owmQcM)B@ z!C49}cPk6nwoC4v2jUZT>!@68ViQ>HB?FF^K!B~686;REt}a`HR;(HpQzNqA%RO23 za(g!QvK#L4FS^{P&(s)d^f$k-?HsjLY#iT zV!Pd19m^W7xJfHx+4on1XWqtaZhK+Gp^@$vMWf}>xLL%HD&8j>rK{(0H}Kg}T6FT< z@S+rYfoh~ z72K8enRu^#rD!tbc_hm`T-~OQ%rGCXd?REHPIj>H*jtxf^%TFzMPF~A(tr#*4Byke z;dRP69vjplMlFst{WKKAAX0a(Lu6G25jTsgWHThD>SE-G7P;{s*$!eT^E_}%^_E=r zahM!&Q7$`%=->Ve%P)^k*w(JVB-cH58&`gA*%~WI|$v9Q=T)Bbc}2N3;r`Z z&zAjAB)jmnuap$YZhRdqb&F))-*~0m#L>XNSdP8U`Ca+Xyjwi)qX;(V8!zdl2xk5U zR{CF*hPeYY!DjqdHrG@EoKTpuXMg@2$e(HaIha2)_%o9~ zv-opZI9u{=@c zokKUEkl@Pzs003P8h{e*#QnPCK`Zi_FR28BRe}&A=mdf~hx0XTr!r8g>B+uV21#xv z)~k7s^u}Ixx!FhBu$SFxPBKl{i^Aqh#nVyo!Y7nZaioBjhF4s{&p1>$4?(W-q9fOs z3(~_W{VY!xMa9;(#1-i0I5cC;A(z!#aU|?u0D)ZT;0HiSf=Gq|oN)MU58A@51Nu^Z zo+I1)qg&UNEFP&3P7=9D^QK5B^|@>{EX{Cvz-yTvKl(`rtiB2u5nhH zZgE>EAMc`Yt0IWy{G1>i4Pxa#he-BY+3ufxB0uDc%fKv~(P}k#AZ4zVV<_&X#;bBX z)O2JJ`~Bx+*A*{7JUD<{`DO#!{Pqj1-!J}tU)2C>4GjvU$*o2_h9#uK|A4~}JOQ#r zzXbc_qo960E-+R(e5&PqK(H)yXAL{}OOO;B$gchp;gbL!viSvtG*z|XBA<~?p&?ip z2eRN_2TETDu&KX}>NOlgk)|r@oLpK?8Re>sR(>PQYDhySzf@y24De@7zs5^z{F!^} z7-_d3o7MWHXKE(p_m2l?=_=!@BCG(fuU-yhQNMXHr{6kx>QxHe0~9leqDa4J3yt>H zfmXd-=P+dpd+WC(_a*4iG#p5$S2`3T+R`!I>@fXJX8kQiig=Ty{%*{y#MF7X12wFy z>296FawJvzC_#+u01BOksQ$el+~#|O=+`=j@oiZ{5v}yi#mn{I6HOjSyxmk*ud8!# zK-3PUD;?|*OS^F-plD)QeL$VVPnfo`@<7Iqh#dk|15EcU;_1OB!TulAcbrnGCyQfc zb4EbW(UhabfuPv~Y?<>uJdL2cQt7)BQBZFLzz^$~9o(uA)4%t9_xVn;{=HV*`E*#a ziJAWh>$T@~P_8%$pDfBZ8|7f1)%JfxS+FK_SHvnrE{-M?CKvLl717r@rR$u zgV=4T|bSkK6O-7dQg8hF;|H zwfs4iKZo;Y4Ht3f77pyW)Hgh+fWn`D@#h`>R5cOKDb<#h^Y}RaEa7Eiz1W056T>A) z*R~#(tJVg<44SVe@VAy2oND@8yhQyoLYm{we)u!6>(sVfbe_;$9?!gPL`ZsfmU$!C zMZ(b%{Xoy!T*`YVmMy-~TYA^PUb_)0;peFuZukr@jV718HjfD6GkwjgA)5ZHf^RVm+470OxsUIqOED&!kGG}ssR8X8 zbdb_h&G+bylz!PJ0Nz}>jO_xq9w3{4#0EB6`D+I&x)t)MTU&`}D$y=oLh0*k@8|eV zTbirn+qSgK%iC(KTDzUn@2q8i-HIAj-bQ95bWvM+(*x;JN{6)NCs8`REzRlU*RsX8 zdxkB-^h95fn+LU5q9mbfzmU*ng8FtgBr4FgYpghg}b5q>nFD`RGbin61I5HMn1ccQm+C zr8fyti_`*l7pU-k4bISDFAcU9tNG;`Jh50!zoEgc8l0d(L4!>i|KVD>t{OCQ0ZjNg zm02pIi$yA&t-+VIf;uf-KVQvXr$OE0YC1%N12i~QgR3;SOM{&L_k_yG zO@l!ijMreE21_)!QG=go@P-D5&Q$qq)!>gBOwt-SMuUYKtOlg|g&GafpuwS93!|P? z89t%G{TjTb!6fZ>h!<<{eGS^rQOn61T&uy;8gwaC^G6km4^jQXCJk{_g8_3@f~gwZ zrNJLH7&TALpQpjMH7IF<&sb!+EQte5*Jh}6KWdOyhR2pADn3_(4{Oj-gR?cbQiH#1 zwlq~s*J!XrgCQDhve}+mkx59=GHez9vQn-1sp%>#(cn}Kj?$n}gSK+=3YFfL-d?Jv z%Qa}j_t46@afr_CpdofmyPrYq0~OS0g1xW7mo;c(9IFY?rR;t_6Eu92M!&9oIWn%5 z?GZ?S_CWf&CfHf6C0-hoG{zr3(8BK@NblFmH$2e5aiHVIJxD81_&`DF0SESz#^A2$ z{uV}Sk2BXyn)8vjz$`42#ie>cJFwn|{D@Z3+9-5o_gN2i}u z{PCYGzj}yK8+D?KsI$20#IUYHFHFmz$M+5#wobPU)ro#7c@Yod)%-`NObwVaYu22{ zrW8FsXSN`e{JZ=PoftLfR@8r&FX{8&cp>^h{DH2W#6(eQh_MWH6o-f|mS-JBtEp6z zt0qmAr!-TA+hrBz4%gD-RoMSW6&6m{;8Yd5%v53SEEP6AqQaU98h)Y*Yp1BNVJL@^ zkZW;u5;q@^oW&E8r9m&gW_i&?>~5)b5f4gh;w+0@#S=sGC5ig(2zV}i;sKqJi63N~r!hP4NFyNT}hj`fKuVN;9c6Ay7? z=e+J}dPaBA(&{0)TS~n|Crg#5_?Xzw@|&mFeNeteQK-QZ4VG$fjRwi4Z2zpl8F44< zea@Vh<@if~s@3MEi&iF37(3XCv*#o7j2Av!W0V z9PHRq=qdU-Zr>mZyMQR=47@Bg-jMeqaj<0gh!K`eNLxyML=Q`$4`Nmij1aW}k?m@+ z9PttTJCp#G8%ctAZS)oGeXHA%)FQ4>es9zlB?=9exqhS$oyF)YdWa>K@xD-ji)Dqc z=X zLtLxpVgTw1Cra-wo7$1=ine$Kiao?q&cxC%M0DuVE?bBZEx!hc21{um$~UM)R85zd zcGajv?v_h|Vt1+}kSb|Muq(#m6$GO!jj{abC36YXbJ&0A=Dx$v1Cg@miizFbITGF42KdOC^~f*ga(&JTO5N?NkgVZmYLQ!cxVs^Sj?kNnG>K>xKUjPVgfB((E&(eTmQb}T4{avctmD-+Y z=^6$LEU_eqK^Lvas^_#`YJceZw$E&e0XJO(z zlRG{FI*xE|vLO5qK|yFmxQ(zXAD1u?UPjo1kTL~3?Wuw=5=RC7p1_qOgt-X5_zW~2 z;Tl4p1;8UbhVZ{-g75=^+j2pm?+h0ryoT^OUKEdAD+trpVv#%BjAKHCpAkAfB?ylq zcvlHRe}qf~^V5RxCc*)PFY$JIzy=%xAUw1Q#}Us9!eWF~c-iskQ9-zhU_1t)5EdYq z5VoHbga(9JpMVx&H^M%IGYF2SAkFKa;%7?{!Y>Je|7C1Cz7mA42muI@2qzHEBK(M8 z_!^T4LM*}@gl2?Rguf9SzQKVsf(JqX!gPdL2pzr!UxYOXhY&tOIDwG*y&#M+;ZgOA zAiRVS*NRu~zv1lZ4GH(9}=U$GoD)LbB4->h8lm%*AT$ zhkY4W91)ID32(J09HSBn{joN|JIkmN^*<|>ju8bn>J*;FuU+7e{tMJD5>o9be9i>H zCxH%7!{MF~j0U*24(%KgPe|I6OP1cf#fPLCXUnSI;(p24-!dRd^y+Fv-GLx!$P$FW zp)i{MmM5abD0kg$bOZ8cG~#gqLF&?aOR(&T65Tsf-gThzZ}+#Hi4tSw#E|>cU?@(x;G-~^-UUDtPV6yz(M@$Z*!ZU!oSGX97RYU~hWo^WdM2j(8gj(Q^ zk_aaexiHYOEm}1wLkPPTYxCkG6!Fj4daQ?kR zVGV{ab#tIj5_Iuy0=6>3G6V(W)H`iy%Z(VZ2Nx?hO;hD~MDo)tePYGlLD<^}lM!YB z7f8P$R|GqX?;y0{Ez4uY;Zt%aVq7CMaXM7cF6{~VI=n4KlTdjil!GKV6zPT1A@FAi zE`x|S+VC$N{Fsf#(mPJ{?n~`10eMmd9-9$%BRD!1_JN_L6ylWI7@f2nb|pym_8neC z9(oq;(qf7e`}00&1@2ME5PlmG!SY$0=rO%Y2*%Hd2FI@zg2h#WTWi-Qmt44i^kDR0 zQ3yh#!;=wNfY8-3APOJz+ozBniMn|%l>nVmYIdOIv4y~`=$O(3Q2RtJgxMZjdlab7 zDFj4IPP`Z+i5)Ga@nZBt)RHD}3~$Ec27++>_~@ zI7`<=u~*lA86+G2JTa9b^vkdePZYf*z$X&LsIKd^e0qnw6=8jbWp|?JCjou}x?pOs z+YrqJ0uhNtpziKTQ0?xHeZ}6~R;Ci+Ts$feHX^9DlAB^g_vE&-l`7<+Fs4<@%)X*; z7}atRIIh+sl$kBHSNS=h@-Gds?CmQWOhkMgh!QaqorNG1)VV+bk`tni-Mh-`Ku;6& zxNco(KS}fSyzX5KH(>Qn^Yw!6T@M0XO7r&O?p<9FucrBXoP9rGDEJ7Tj*{RwszWZ- zl@FeeKY_f;4W%qD8GHWt&HVHk{C3YB$8!V@<==;BiK8Zt`UTu`+iP)KE#f|RSbLbg1TEGF}wJx+{1#p4@<4xG^ush`HLbdBXevgph8 zZJz}~XFS{xBu-c={b?_`@>-k)$8~BgkwiyxP68tK1yVe;pwe_AeR4^1iDo<=#R!g$ zxsbjzk-SJ0J)Wk=?rI>n>$2@Xahzotog#V$QWKu1f;;gzh@eWF8%vb=v5>VYMf4v? zWLJ@!)QpEPR21A1934qD)hX1j6;dIWeuY$n2}Y+wwxrri6Bc%smJ~5&A~DHEM$&jZ z7{bd4ZoF7EY7&Gg(h~$qdF4~2q^~JeIFDL_A5x?ek$)GWeY0wG2h4 zZTXhP{X{Q5lhvav<}4xqQ<9_FvZSBr`3U**>qtLF8Mi58jo{fy6gp|n)hSyWz@CWo zM4-_dNYJ&ER@p`=O5%esRvm=>kfjYmi+6vqr&r+;I4lGi8Pu}2v}IC%(LJm!jbDU( zU@W|kGF+n{XIq}@FNP*?X@TTxn|}})ks}Hzj@x%oCsVf?^HT`q&e%SPU_RNa<T)ZmnPuz7vl(5G3%jtQa7A^Q_a9bsmqawyd`Xi2goQN*Io; zPIwp)?70MLWj_rN1NijQk0>(mn1;}m51$4;eDY^JL9*5GL~X-0lBVB4F(i|IxcMkT z5eks1?oUJUQ#;&;YwvmMQHHCkak%Bxfnt^?sVZcIC}6QEg!e`y9zk-fJ!t7<6#aSj z1Ypim_B2Fh@$AAwmNaCWY7Yv+C4?eiNtFB2-8%@~k2?xoi#owG>V({Pv5iDfr!Y)y z$io!@*y4kE=$Ll2u7OTM;M8tH;22jSFy2K7T<6>B-Q?NOz1HB)9gs^dH0X&(00NxZ zAxrR!(V~9jDJOPh>2$G}mDD-4sFJ#%FjzBVHtO^~_XSH7m z!W9JCfCLH{sI*%A7aoP%)6uju6-xY85Zn>2Vj@+Yg^toDAq>cJ)Jqy5g7)eA3ekvt zfRSs2ix9C!C-f-kBm~WH6mWbhbZd2O(lexSdj;9uk@xLwx?F6S_< zz%|*Un_D4l$amO+fF}ume;TF(dI9wKB{9vN-CxENH+qA56?qClQ$2*B`0hf`?;fr0 zO>PZcYh7~5>W_mlcM#NFpN_H&P8VZLO&{T~rxxm@T1mGs0!q6ev~o$%o`2!67;Rrp zfN_IHn5wS!+vl|(i?c?LmV(9 z5_%`qdj2d3e!t*91lx%mjzWBDyL2ae_?`c+y|WLFsygHFIfQ7ctuB}VHbGY)m;%aD zkVYwW2_P#nurnfrkJg}2S4CY~O2S9j6>vxLQZ*2`Coj_ z&_#x^7UBgNA;T1Ky$ap%Shv!l_wa6G0&`;##Eq2Oz7hA#Y z0X7{X_;9fvmTSk(N~~j`VA>AOy_LScd|QI19USHq9QOdRyrl!Xy42oP35Ez-yK2YIN~~j`VA>AO zoky@_nKo`M;+PmWU_TjSzRCE*JUua|h81K*3L+`GMg9dJu_?a(F?>EFtB+=5M~P*K z)wp75VxSLKRLAXY-Tn3M9BHs%xa4`*mZhAIaBl@nAR8ga!|5-TNE z<*K|^HaMJ^d#5Ev!zck`1_#Wxy5(=hW zu{5z5G2hB;Bbm*V;pjrH9UK~A#F-szb@jp&Vj;?9Ty+;k2*!tzFmM%IM=VOgURSK1 zSRFC1tMXA|oy0t@*lA)ZVsTg9@Usjc6DxPcYKi3$iaJ$`vao7N%U<6&tse9wg>lVfCOjf~6J`Ob_L}y9z!+tdxQoS8NNh zxY4{o55GE(?CLzStMkaN&Lg`zkL>C^vj4kzWM%*RJhGNii}k1>gT7YA{KxgQmXjep z`I^Je=*S>r&l+ZBMEKEid<~z6crguL?-^xJ5@N1`OcE+KqU&^w_uL_s%n>Ltc2tt^ z4_1Vv)s(u)c&Abq4fy^_oiDyhf6qptv9f9)RA|&zSw)-e)iXBeA|rRb zE~Myf>#Z8B8ELFrufI)bhaCdN#`!CuBBOYNRfF2n1{iJBZ_s1#CpPGNf!?Smf=L^# zr8V|aVxwL_u+PSfP1e$mO^g7S7_2KaZeOKu8_Wx#b5zWglp~o@ZEb5!wO-7U-mLSD zkE-=(+gq({wp+2$Y@=_pt{f;{9kGfhBKk!_lcM@K=!@#l!N<>9=-Og!W5`zh&q#S0 zZ%IS>8U>P%<=NqQZ4K3b#&G>$hTMhUU}%2?UU(xa!Dom3^?#)UBu!96Dq-I&J~YFL zs1+}~3w7XOL``F%n}uxNL0kEVqr?Nmg=>Z>wHz<3L}9$J+KETudn5S3%no(Ji>RG( zf@}_fG{DVds>m12ql|DB3eDyvR?L&AiWS+Ug0_+inL>_$xbPBc#|!(>DZDBo;en** z{BXoAj7-w77phx1%9)gbhL>S3d>roPv+7#%Y;pL<$Y{m@sN4y^ImX!#+~oKsc+TtJiH{_o+iI1Uh;gqk>dJD!7s$iHp%9E7Zu?pv-31E zIZa_7?YFQnLMp(&ASslFt0y@dg=6nxyyITF5njBTds_c+|rUa&7Y%`7cYDeO~DI8D1eW{$Qgb}!x`uaPzf*eIbOIENtqPvLRJsKKF0^| z=M*71%4}Yr;ib9E4MOvX3lF0ZUicQ;Hl0u9%yw1Y2kgp1&pa*+3JH6W)pam1pYElr zv#DuqrK~?)Ko41vVF$J!hF!}z|JKp4aA>(w zM=8X#Ts4#QbSK`Ork3<|@sGG2i3{&Uif;75wiP@Z@F|%0h}~Nr`0z@`s@RDzob?#J zN?oQ0t8+*W%)@`Xs=oCkk5Y~xn=|JLMXYQ!4<#xH=b)JE5X^mws~7Kq2iCJ89xT~3 zhqlrqi#IZ4ND@Q%L$nqzd=zcN4h~mn;_$)&8pEpwu8r3?$Ffm4kg^d@Im9WT0ipaUgIc_BHrj^|!l#-!|C`uQxS5xW z-_aAS@WA=6bMz!!+({Bitn5BaW#Yp9D1{duM5pn>W+z_J!dKc``E^1P!=1E|WRC9$ zJ4bon5w6l_Sg2(on?2!Zh<$(pR2B|yBZY?dz~|oJBBFc@PCdptydUm7&Q5axaoB~H zKgb0Ob5isc1M5N$l0kOi*b`j55ekX90~O(glaUWEyc_xPes~g{rm|!ojv?!j3>UQX z;9}>3@D!3e!kyNU6wu9YasDYIoQUf1!pKy9m@Ejt@2n7(Iq@J|-Cs7DadFa^Z~7)>E;%bM!>ym&4Qzu&8Lse!yu(93lJhNm-0`d70mqy0IFfC) z!yY8>6X&3}&#o){5=~&67y7vWSAD_5f`t&w|B^=V^WbAhHuM5Kb%B=%;-}#q7wxzo zzJlaxuZP`^7v^8G0QVq&1F@0vKS#DOCAv0>$m@zYDc9@-Jx0yD3&5U{3RE?!c ze=?8^Cd-o*$xt$!tV%|cHOW}AE*Vdn$>_nFgGR+J?VYc@949BTJD!gB#xwEDah1qR z#1m$sDbbopB{~wFiS9%?(VOVqo7sDLuiBTl&&wY8&46iI?FJh|jp4?s#%N 0) { metadataFile << ",\"dataDiscontinuityCount\":" << discontinuityCount; @@ -438,18 +442,32 @@ int main(int argc, char* argv[]) { std::cout << "Recording stopped. Output path: " << config.outputPath << std::endl; if (audioActive) { - if (loopback.dataDiscontinuityCount() > 0 || loopback.timestampErrorCount() > 0) { + if ( + loopback.dataDiscontinuityCount() > 0 || + loopback.timestampErrorCount() > 0 || + loopback.gapFillCount() > 0 + ) { std::cerr << "WARNING: System audio timing metadata includes discontinuities=" << loopback.dataDiscontinuityCount() - << " timestampErrors=" << loopback.timestampErrorCount() << std::endl; + << " timestampErrors=" << loopback.timestampErrorCount() + << " gapFills=" << loopback.gapFillCount() + << " insertedSilenceFrames=" << loopback.insertedSilenceFrames() + << std::endl; } std::cout << "Audio path: " << config.audioOutputPath << std::endl; } if (micActive) { - if (micCapture.dataDiscontinuityCount() > 0 || micCapture.timestampErrorCount() > 0) { + if ( + micCapture.dataDiscontinuityCount() > 0 || + micCapture.timestampErrorCount() > 0 || + micCapture.gapFillCount() > 0 + ) { std::cerr << "WARNING: Microphone timing metadata includes discontinuities=" << micCapture.dataDiscontinuityCount() - << " timestampErrors=" << micCapture.timestampErrorCount() << std::endl; + << " timestampErrors=" << micCapture.timestampErrorCount() + << " gapFills=" << micCapture.gapFillCount() + << " insertedSilenceFrames=" << micCapture.insertedSilenceFrames() + << std::endl; } std::cout << "Mic path: " << config.micOutputPath << std::endl; } diff --git a/electron/native/wgc-capture/src/wasapi_loopback.cpp b/electron/native/wgc-capture/src/wasapi_loopback.cpp index 745f86e3..f7565ec7 100644 --- a/electron/native/wgc-capture/src/wasapi_loopback.cpp +++ b/electron/native/wgc-capture/src/wasapi_loopback.cpp @@ -3,12 +3,16 @@ #include #include #include +#include #pragma comment(lib, "ole32.lib") namespace { constexpr int64_t kHundredNanosecondsPerSecond = 10000000; constexpr uint64_t kSilenceWriteChunkFrames = 4096; +constexpr uint32_t kMinimumGapFillThresholdMs = 50; +constexpr uint32_t kDiscontinuityGapFillThresholdMs = 10; +constexpr uint32_t kBoundaryFadeInMs = 5; int64_t queryPerformanceCounterHns() { LARGE_INTEGER counter; @@ -187,6 +191,9 @@ bool WasapiCapture::start() { accumulatedPausedQpcHns_ = 0; dataDiscontinuityCount_ = 0; timestampErrorCount_ = 0; + gapFillCount_ = 0; + insertedSilenceFrames_ = 0; + fadeInFramesRemaining_ = 0; writeWavHeader(outputFile_, 0); HRESULT hr = audioClient_->Start(); @@ -220,6 +227,7 @@ bool WasapiCapture::resume() { } pauseStartQpcHns_ = 0; paused_ = false; + fadeInFramesRemaining_ = boundaryFadeInFrameCount(); return true; } @@ -242,6 +250,7 @@ void WasapiCapture::stop() { paused_ = false; pauseStartQpcHns_ = 0; accumulatedPausedQpcHns_ = 0; + fadeInFramesRemaining_ = 0; } static int16_t floatToInt16(float v) { @@ -282,6 +291,34 @@ void WasapiCapture::writePcmFrames(const int16_t* samples, UINT32 frameCount, WO } const DWORD bytesToWrite = frameCount * channels * sizeof(int16_t); + const uint32_t fadeFrames = fadeInFramesRemaining_.load(); + if (fadeFrames > 0) { + const uint32_t totalFadeFrames = boundaryFadeInFrameCount(); + const uint32_t remainingFadeFrames = (std::min)(fadeFrames, totalFadeFrames); + const uint32_t framesToFade = + (std::min)(static_cast(frameCount), remainingFadeFrames); + const uint32_t completedFadeFrames = totalFadeFrames - remainingFadeFrames; + std::vector faded(samples, samples + frameCount * channels); + for (uint32_t frame = 0; frame < framesToFade; frame++) { + const double scale = + static_cast(completedFadeFrames + frame + 1) / + static_cast(totalFadeFrames); + for (WORD channel = 0; channel < channels; channel++) { + const size_t index = static_cast(frame) * channels + channel; + const long scaled = std::lround(static_cast(faded[index]) * scale); + faded[index] = static_cast( + std::clamp(scaled, -32768, 32767)); + } + } + fadeInFramesRemaining_ = remainingFadeFrames - framesToFade; + + DWORD written = 0; + WriteFile(outputFile_, faded.data(), bytesToWrite, &written, nullptr); + totalDataBytes_.fetch_add(written); + framesWritten_.fetch_add(written / (channels * sizeof(int16_t))); + return; + } + DWORD written = 0; WriteFile(outputFile_, samples, bytesToWrite, &written, nullptr); totalDataBytes_.fetch_add(written); @@ -293,12 +330,15 @@ void WasapiCapture::writeSilenceFrames(uint64_t frameCount, WORD channels) { return; } + gapFillCount_.fetch_add(1); + insertedSilenceFrames_.fetch_add(frameCount); std::vector silence(static_cast(kSilenceWriteChunkFrames * channels), 0); while (frameCount > 0) { - const uint64_t chunkFrames = std::min(frameCount, kSilenceWriteChunkFrames); + const uint64_t chunkFrames = (std::min)(frameCount, kSilenceWriteChunkFrames); writePcmFrames(silence.data(), static_cast(chunkFrames), channels); frameCount -= chunkFrames; } + fadeInFramesRemaining_ = boundaryFadeInFrameCount(); } uint64_t WasapiCapture::capturedDurationMs() const { @@ -309,6 +349,23 @@ uint64_t WasapiCapture::capturedDurationMs() const { return (framesWritten_.load() * 1000) / mixFormat_->nSamplesPerSec; } +uint32_t WasapiCapture::sampleRate() const { + return mixFormat_ ? mixFormat_->nSamplesPerSec : 0; +} + +uint16_t WasapiCapture::channelCount() const { + return mixFormat_ ? mixFormat_->nChannels : 0; +} + +uint32_t WasapiCapture::boundaryFadeInFrameCount() const { + const uint32_t rate = sampleRate(); + if (rate == 0) { + return 1; + } + const uint32_t fadeFrames = (rate * kBoundaryFadeInMs) / 1000; + return fadeFrames > 0 ? fadeFrames : 1; +} + void WasapiCapture::captureThread() { // COM must be initialized on every thread that uses COM objects. // The main thread calls winrt::init_apartment(MTA) but that only covers @@ -362,7 +419,9 @@ void WasapiCapture::captureThread() { break; } - if ((flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { + const bool hasDataDiscontinuity = + (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0; + if (hasDataDiscontinuity) { dataDiscontinuityCount_.fetch_add(1); } if ((flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) != 0) { @@ -385,15 +444,22 @@ void WasapiCapture::captureThread() { ) { const int64_t elapsedHns = static_cast(qpcPosition) - firstPacketQpcHns; - const int64_t adjustedElapsedHns = std::max( - 0, - elapsedHns - accumulatedPausedQpcHns_.load()); + const int64_t adjustedElapsedHns = + elapsedHns > accumulatedPausedQpcHns_.load() + ? elapsedHns - accumulatedPausedQpcHns_.load() + : 0; const uint64_t expectedStartFrame = (static_cast(adjustedElapsedHns) * mixFormat_->nSamplesPerSec + kHundredNanosecondsPerSecond / 2) / kHundredNanosecondsPerSecond; const uint64_t writtenFrames = framesWritten_.load(); - const uint64_t gapThresholdFrames = mixFormat_->nSamplesPerSec / 100; + const uint32_t gapThresholdMs = hasDataDiscontinuity + ? kDiscontinuityGapFillThresholdMs + : kMinimumGapFillThresholdMs; + const uint32_t gapThresholdFrameCount = + (mixFormat_->nSamplesPerSec * gapThresholdMs) / 1000; + const uint64_t gapThresholdFrames = + gapThresholdFrameCount > 0 ? gapThresholdFrameCount : 1; if (expectedStartFrame > writtenFrames + gapThresholdFrames) { writeSilenceFrames(expectedStartFrame - writtenFrames, channels); diff --git a/electron/native/wgc-capture/src/wasapi_loopback.h b/electron/native/wgc-capture/src/wasapi_loopback.h index 1c5555e9..1a513446 100644 --- a/electron/native/wgc-capture/src/wasapi_loopback.h +++ b/electron/native/wgc-capture/src/wasapi_loopback.h @@ -22,8 +22,12 @@ public: int64_t firstPacketQpcHns() const { return firstPacketQpcHns_.load(); } uint64_t capturedDurationMs() const; uint64_t totalDataBytes() const { return totalDataBytes_.load(); } + uint32_t sampleRate() const; + uint16_t channelCount() const; uint32_t dataDiscontinuityCount() const { return dataDiscontinuityCount_.load(); } uint32_t timestampErrorCount() const { return timestampErrorCount_.load(); } + uint32_t gapFillCount() const { return gapFillCount_.load(); } + uint64_t insertedSilenceFrames() const { return insertedSilenceFrames_.load(); } private: bool initializeCommon(); @@ -31,6 +35,7 @@ private: bool writeWavHeader(HANDLE file, DWORD dataSize); void writePcmFrames(const int16_t* samples, UINT32 frameCount, WORD channels); void writeSilenceFrames(uint64_t frameCount, WORD channels); + uint32_t boundaryFadeInFrameCount() const; IMMDevice* findCaptureDeviceByName(const std::wstring& name); std::string outputPath_; @@ -54,4 +59,7 @@ private: std::atomic accumulatedPausedQpcHns_{0}; std::atomic dataDiscontinuityCount_{0}; std::atomic timestampErrorCount_{0}; + std::atomic gapFillCount_{0}; + std::atomic insertedSilenceFrames_{0}; + std::atomic fadeInFramesRemaining_{0}; };