mirror of
https://github.com/damp11113/xHE-Opus.git
synced 2025-04-27 06:28:08 +00:00
new xHE-Opus v2
new xHE-Opus v2 with parametric stereo
This commit is contained in:
parent
f3fef7d5da
commit
45f27fabfd
Binary file not shown.
38
gui.py
38
gui.py
@ -47,6 +47,14 @@ class App:
|
||||
if int(dpg.get_value("opusframesize")) > 60:
|
||||
dpg.configure_item("opusframesize", default_value="60")
|
||||
|
||||
def changeprofileopus(self, sender, data):
|
||||
if data == "xHE-Opus v2":
|
||||
dpg.configure_item("opusstereomode", show=False)
|
||||
dpg.configure_item("opusbitrate", min_value=2.5, max_value=510, min_clamped=True, max_clamped=True, step_fast=1, default_value=64)
|
||||
else:
|
||||
dpg.configure_item("opusstereomode", show=True)
|
||||
dpg.configure_item("opusbitrate", min_value=5, max_value=1020, min_clamped=True, max_clamped=True, step_fast=1, default_value=64)
|
||||
|
||||
def selectdeoutputpath(self, sender, data):
|
||||
file_path = easygui.diropenbox()
|
||||
dpg.set_value("deoutpathshow", f"output: {file_path}")
|
||||
@ -80,6 +88,8 @@ class App:
|
||||
dpg.configure_item("deplayconvert", show=False)
|
||||
|
||||
def convert(self):
|
||||
signaltype = str(dpg.get_value("opussignaltype")).lower()
|
||||
profile = str(dpg.get_value("opusprofile")).strip().lower()
|
||||
stereomode = str(dpg.get_value("opusstereomode")).lower()
|
||||
if stereomode == "stereo l/r":
|
||||
stereomode = 1
|
||||
@ -88,20 +98,40 @@ class App:
|
||||
else:
|
||||
stereomode = 2
|
||||
|
||||
if signaltype == "music":
|
||||
signalauto = False
|
||||
signalvoice = False
|
||||
elif signaltype == "voice":
|
||||
signalauto = False
|
||||
signalvoice = True
|
||||
else:
|
||||
signalauto = True
|
||||
signalvoice = False
|
||||
|
||||
try:
|
||||
total = 0
|
||||
current = 0
|
||||
filename = os.path.splitext(os.path.basename(self.inputfilepath))[0]
|
||||
|
||||
dpg.set_value("convertstatus", "init encoder...")
|
||||
print(profile)
|
||||
if profile == "xhe-opus v1":
|
||||
encoder = libxheopus.DualOpusEncoder(dpg.get_value("opusapp"), 48000, dpg.get_value("opusversion"))
|
||||
else:
|
||||
encoder = libxheopus.PSOpusEncoder(dpg.get_value("opusapp"), 48000, dpg.get_value("opusversion"))
|
||||
|
||||
encoder.set_bitrate_mode(dpg.get_value("opusbitmode"))
|
||||
encoder.set_bandwidth(dpg.get_value("opusbandwidth"))
|
||||
encoder.set_bitrates(int(dpg.get_value("opusbitrate")*1000))
|
||||
encoder.set_compression(dpg.get_value("opuscompression"))
|
||||
encoder.set_packet_loss(dpg.get_value("opuspacketloss"))
|
||||
|
||||
if profile != "xhe-opus v2":
|
||||
encoder.set_stereo_mode(stereomode, dpg.get_value("opusenajoint"))
|
||||
|
||||
encoder.set_feature(dpg.get_value("opusenapred"), False, dpg.get_value("opusenadtx"))
|
||||
encoder.enable_voice_mode(signalvoice, signalauto)
|
||||
|
||||
desired_frame_size = encoder.set_frame_size(int(dpg.get_value("opusframesize")))
|
||||
|
||||
dpg.set_value("convertstatus", "init writer...")
|
||||
@ -168,7 +198,7 @@ class App:
|
||||
self.delen = metadata["footer"]["length"]
|
||||
|
||||
def playaudiothread(self):
|
||||
self.decoder = libxheopus.DualOpusDecoder()
|
||||
self.decoder = libxheopus.xOpusDecoder()
|
||||
for data in self.derender.decode(self.decoder, True, self.depausepos):
|
||||
if self.deplay:
|
||||
|
||||
@ -229,7 +259,7 @@ class App:
|
||||
outwav.setsampwidth(2) # 2 bytes (16 bits) per sample
|
||||
outwav.setframerate(48000)
|
||||
|
||||
self.decoder = libxheopus.DualOpusDecoder()
|
||||
self.decoder = libxheopus.xOpusDecoder()
|
||||
for data in self.derender.decode(self.decoder, True, self.depausepos):
|
||||
self.decurrentplay += 1
|
||||
|
||||
@ -260,18 +290,20 @@ class App:
|
||||
dpg.add_text("output:", tag="outpathshow")
|
||||
dpg.add_button(label="Select Input File", callback=self.selectinputfile)
|
||||
dpg.add_button(label="Select Output Path", callback=self.selectoutputpath)
|
||||
dpg.add_combo(["xHE-Opus v1", "xHE-Opus v2"], label="Profile", default_value="xHE-Opus v1", tag="opusprofile", callback=self.changeprofileopus)
|
||||
dpg.add_combo(["hev2", "exper", "stable", "old"], label="Version", default_value="hev2", tag="opusversion", callback=self.changeversionopus)
|
||||
dpg.add_combo(["120", "100", "80", "60", "40", "20", "10", "5"], label="Frame Size (ms)", tag="opusframesize", default_value="120")
|
||||
dpg.add_combo(["voip", "audio", "restricted_lowdelay"], label="Application", default_value="restricted_lowdelay", tag="opusapp")
|
||||
dpg.add_combo(["VBR", "CVBR", "CBR"], label="Bitrate Mode", default_value="CVBR", tag="opusbitmode")
|
||||
dpg.add_combo(["auto", "fullband", "superwideband", "wideband", "mediumband", "narrowband"], label="Bandwidth", tag="opusbandwidth", default_value="fullband")
|
||||
dpg.add_combo(["Stereo L/R", "Stereo Mid/Side"], label="Stereo Mode", tag="opusstereomode", default_value="Stereo L/R")
|
||||
dpg.add_combo(["Auto", "Voice", "Music"], label="Signal Type", tag="opussignaltype", default_value="Auto")
|
||||
dpg.add_input_float(label="Bitrates", min_value=5, max_value=1020, min_clamped=True, max_clamped=True, step_fast=1, default_value=64, tag="opusbitrate")
|
||||
dpg.add_input_int(label="Compression Level", max_clamped=True, min_clamped=True, min_value=0, max_value=10, default_value=10, tag="opuscompression")
|
||||
dpg.add_input_int(label="Packet Loss", max_clamped=True, min_clamped=True, min_value=0, max_value=100, default_value=0, tag="opuspacketloss")
|
||||
dpg.add_checkbox(label="Prediction", tag="opusenapred")
|
||||
dpg.add_checkbox(label="DTX", tag="opusenadtx")
|
||||
dpg.add_checkbox(label="Joint", tag="opusenajoint")
|
||||
dpg.add_checkbox(label="Auto Mono (Mid/Side Encoding)", tag="opusenajoint")
|
||||
dpg.add_button(label="Convert", callback=self.startconvert)
|
||||
|
||||
with dpg.window(label="converting", show=False, tag="convertingwindow", modal=True, no_resize=True, no_move=True, no_title_bar=True, width=320):
|
||||
|
280
icon.svg
Normal file
280
icon.svg
Normal file
@ -0,0 +1,280 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="48px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="48px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="icon.svg"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
inkscape:export-filename="icon.png"
|
||||
inkscape:export-xdpi="819.20001"
|
||||
inkscape:export-ydpi="819.20001"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1"><linearGradient
|
||||
id="linearGradient33"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#434243;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop33" /><stop
|
||||
style="stop-color:#060506;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop34" /></linearGradient><linearGradient
|
||||
id="linearGradient31"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#444243;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop31" /><stop
|
||||
style="stop-color:#050505;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop32" /></linearGradient><linearGradient
|
||||
id="linearGradient29"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#454344;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop29" /><stop
|
||||
style="stop-color:#090909;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop30" /></linearGradient><linearGradient
|
||||
id="linearGradient27"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#464445;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop27" /><stop
|
||||
style="stop-color:#0a0a0a;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop28" /></linearGradient><linearGradient
|
||||
id="linearGradient24"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#454344;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop24" /><stop
|
||||
style="stop-color:#111111;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop25" /></linearGradient><linearGradient
|
||||
id="linearGradient21"
|
||||
inkscape:collect="always"><stop
|
||||
style="stop-color:#474546;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop21" /><stop
|
||||
style="stop-color:#080808;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop22" /></linearGradient><linearGradient
|
||||
id="_Linear2"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,48.2549,-48.2549,0,231.618,118.208)"><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop8" /><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop9" /><stop
|
||||
offset="100%"
|
||||
style="stop-color:black;stop-opacity:1"
|
||||
id="stop10" /></linearGradient><linearGradient
|
||||
id="_Linear3"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,49.7919,-49.7919,0,45.9897,117.439)"><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop11" /><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop12" /><stop
|
||||
offset="100%"
|
||||
style="stop-color:black;stop-opacity:1"
|
||||
id="stop13" /></linearGradient><linearGradient
|
||||
id="_Linear4"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,49.5634,-49.5634,0,313.285,117.719)"><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop14" /><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop15" /><stop
|
||||
offset="100%"
|
||||
style="stop-color:black;stop-opacity:1"
|
||||
id="stop16" /></linearGradient><linearGradient
|
||||
id="_Linear5"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,88.9072,-88.9072,0,131.199,117.579)"><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop17" /><stop
|
||||
offset="0%"
|
||||
style="stop-color:rgb(73,71,72);stop-opacity:1"
|
||||
id="stop18" /><stop
|
||||
offset="100%"
|
||||
style="stop-color:black;stop-opacity:1"
|
||||
id="stop19" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient21"
|
||||
id="linearGradient20"
|
||||
x1="85.493027"
|
||||
y1="107.23618"
|
||||
x2="47.430016"
|
||||
y2="159.73979"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient21"
|
||||
id="linearGradient22"
|
||||
x1="152.74756"
|
||||
y1="90.268875"
|
||||
x2="104.83708"
|
||||
y2="161.27939"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient24"
|
||||
id="linearGradient25"
|
||||
x1="208.53099"
|
||||
y1="91.856384"
|
||||
x2="170.58093"
|
||||
y2="158.41898"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient27"
|
||||
id="linearGradient28"
|
||||
x1="247.16783"
|
||||
y1="125.09132"
|
||||
x2="233.85489"
|
||||
y2="202.95941"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient29"
|
||||
id="linearGradient30"
|
||||
x1="283.84119"
|
||||
y1="125.34251"
|
||||
x2="271.03064"
|
||||
y2="184.37154"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient31"
|
||||
id="linearGradient32"
|
||||
x1="320.77448"
|
||||
y1="126.64043"
|
||||
x2="309.05182"
|
||||
y2="161.1868"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient33"
|
||||
id="linearGradient34"
|
||||
x1="357.6275"
|
||||
y1="126.97522"
|
||||
x2="346.19846"
|
||||
y2="141.29291"
|
||||
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="18.09375"
|
||||
inkscape:cy="23.25"
|
||||
inkscape:window-width="1672"
|
||||
inkscape:window-height="950"
|
||||
inkscape:window-x="50"
|
||||
inkscape:window-y="33"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1" /><path
|
||||
d="m 237.69,-100 c -15.68667,0 -29.22333,-5.69333 -40.61,-17.08 C 185.69333,-128.46667 180,-142.00333 180,-157.69 v -644.62 c 0,-15.68667 5.69333,-29.22333 17.08,-40.61 11.38667,-11.38667 24.92333,-17.08 40.61,-17.08 H 585.23 L 780,-665.23 v 507.54 c 0,15.68667 -5.69333,29.22333 -17.08,40.61 -11.38667,11.38667 -24.92333,17.08 -40.61,17.08 z M 562.54,-644.77 V -814.61 H 237.69 c -3.07333,0 -5.89333,1.28 -8.46,3.84 -2.56,2.56667 -3.84,5.38667 -3.84,8.46 v 644.62 c 0,3.07333 1.28,5.89333 3.84,8.46 2.56667,2.56 5.38667,3.84 8.46,3.84 h 484.62 c 3.07333,0 5.89333,-1.28 8.46,-3.84 2.56,-2.56667 3.84,-5.38667 3.84,-8.46 v -487.08 c -65.24678,5.26008 -120.07899,3.57711 -172.07,0 z M 225.39,-814.61 v 169.84 -169.84 669.22 z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ssssssccssssccscsscsscsccccccc" /><path
|
||||
style="fill:#454344;stroke-width:1.18081"
|
||||
d="m 327.21902,-454.75826 c -8.67942,-1.386 -20.82226,-5.87842 -29.14992,-10.7842 -9.41336,-5.54536 -23.71188,-20.07002 -29.72482,-30.19494 -16.51428,-27.8076 -16.95202,-64.04278 -1.1004,-91.08834 5.9727,-10.19044 23.5213,-27.13586 33.44182,-32.29232 27.39902,-14.24146 61.31846,-12.82076 86.19926,3.61042 4.54612,3.00224 8.6642,5.63622 9.15128,5.8533 0.487,0.217 0.8856,-40.56474 0.8856,-90.62624 v -91.02092 h 73.21032 73.21034 v 27.74908 27.74908 h -56.05202 -56.052 l -0.3548,104.79704 c -0.3982,117.59698 0.184,109.62208 -9.41992,129.0037 -4.05164,8.17652 -7.06064,12.12558 -16.2337,21.3055 -12.63448,12.64392 -23.479,19.17334 -39.04158,23.5067 -10.07486,2.8053 -29.17016,3.99708 -38.96942,2.43214 z"
|
||||
id="path2" /><path
|
||||
style="fill:#454344;stroke-width:0.0590406"
|
||||
d="M 11.306273,42.917109 C 10.232134,42.708883 9.2423232,41.667944 9.0657808,40.560886 8.980653,40.027068 8.980653,7.9729322 9.0657808,7.4391144 9.1538473,6.886869 9.4020781,6.4007019 9.8290076,5.9443129 10.24328,5.5014543 10.812947,5.1785993 11.358612,5.0774193 c 0.202637,-0.037574 3.501528,-0.058969 9.092384,-0.058969 h 8.774363 l 4.885476,4.8857414 4.885475,4.8857413 -4.46e-4,12.693595 c -2.98e-4,8.472493 -0.0208,12.82121 -0.06164,13.077358 -0.08807,0.552245 -0.336297,1.038412 -0.763227,1.494801 -0.414272,0.442859 -0.983939,0.765713 -1.529604,0.866894 -0.399658,0.07411 -24.951963,0.0688 -25.335115,-0.0055 z m 25.224161,-2.351378 0.192813,-0.172276 0.01517,-12.314809 0.01517,-12.314808 H 32.443218 28.132841 V 11.512915 7.2619926 H 19.897611 11.66238 L 11.469566,7.4342714 11.276753,7.6065501 V 24 40.39345 l 0.192813,0.172279 0.192814,0.172278 H 24 36.337621 Z"
|
||||
id="path3"
|
||||
transform="matrix(20,0,0,20,0,-960)" /><g
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
transform="matrix(1.421733,0,0,1.421733,175.04404,-471.21546)"><path
|
||||
style="fill:url(#linearGradient20);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 42.772863,109.31583 15.215648,22.93703 -16.709705,25.41672 13.677827,0.16149 11.189814,-17.86307 10.6397,17.92037 15.625433,-0.0845 -16.563129,-25.44156 14.446739,-22.98913 -13.509043,-0.224 -9.314427,15.65949 -9.595734,-15.65949 z"
|
||||
id="path1-4"
|
||||
inkscape:label="x"
|
||||
sodipodi:nodetypes="ccccccccccccc" /><path
|
||||
style="fill:url(#linearGradient22);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 100.99431,91.977063 v 65.802947 l 13.22154,0.0838 0.0823,-28.79451 26.28765,-0.24383 0.32002,28.79451 13.1484,0.0762 0.15544,-65.802942 H 140.5858 l 0.24687,26.295272 -26.36994,0.0762 0.10264,-26.311838 z"
|
||||
id="path2-7"
|
||||
sodipodi:nodetypes="ccccccccccccc"
|
||||
inkscape:label="H" /><path
|
||||
style="fill:url(#linearGradient25);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 169.86479,92.036653 0.0249,65.595117 39.28245,-0.0503 V 147.4961 h -26.17493 v -18.69955 h 23.78516 v -10.15926 h -23.79058 v -16.51593 h 25.86902 v -9.944951 z"
|
||||
id="path3-9"
|
||||
inkscape:label="E" /><path
|
||||
style="display:inline;fill:url(#linearGradient28);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 235.53041,127.67943 h 10.09779 v 64.19197 l -10.09598,5.82892 z"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="ccccc" /><path
|
||||
style="fill:url(#linearGradient30);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 272.51786,127.66402 10.18464,0.001 v 45.10013 l -10.18142,5.87824 z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="ccccc" /><path
|
||||
style="fill:url(#linearGradient32);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 309.59307,127.67404 h 10.41031 v 25.99613 l -10.4105,6.0105 z"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="ccccc" /><path
|
||||
style="display:inline;fill:url(#linearGradient34);fill-opacity:1;stroke-width:0.264584"
|
||||
d="m 346.85062,127.61804 h 10.27822 v 6.80536 l -10.27696,5.93339 z"
|
||||
id="path7"
|
||||
sodipodi:nodetypes="ccccc" /><g
|
||||
id="path3716"
|
||||
transform="matrix(0.49366852,0,0,0.49366852,212.40542,15.350723)"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421">
|
||||
<path
|
||||
d="m 271.531,141.499 c -2.511,7.718 -8.23,13.807 -17.156,18.27 -8.926,4.463 -20.223,6.694 -33.891,6.694 -13.484,0 -23.292,-2.208 -29.43,-6.626 -6.136,-4.415 -7.904,-10.528 -5.299,-18.338 l 7.53,-23.291 h 25.663 l -7.252,23.151 c -0.931,2.883 -1.232,5.278 -0.906,7.183 0.326,1.907 1.046,3.417 2.162,4.533 1.394,1.113 2.95,1.88 4.672,2.299 1.72,0.419 3.788,0.629 6.207,0.629 2.417,0 4.742,-0.255 6.974,-0.769 2.231,-0.51 4.275,-1.323 6.138,-2.438 1.858,-1.116 3.508,-2.602 4.951,-4.463 1.44,-1.859 2.626,-4.186 3.557,-6.974 l 7.532,-23.151 h 25.663 z"
|
||||
style="fill:url(#_Linear2);fill-rule:nonzero"
|
||||
id="path2-4" />
|
||||
</g><g
|
||||
id="path3723"
|
||||
transform="matrix(0.49366852,0,0,0.49366852,212.40542,15.350723)"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421">
|
||||
<path
|
||||
d="m 88.875,142.404 c 2.51,-7.717 0.743,-13.808 -5.301,-18.271 -6.044,-4.463 -15.899,-6.694 -29.567,-6.694 -13.483,0 -24.686,2.21 -33.611,6.625 -8.928,4.417 -14.693,10.53 -17.295,18.34 -2.51,7.72 -0.722,13.786 5.37,18.201 6.089,4.418 15.922,6.626 29.498,6.626 13.575,0 24.826,-2.208 33.753,-6.626 8.924,-4.415 14.642,-10.481 17.153,-18.201 z m -26.082,0.14 c -0.931,2.978 -2.069,5.3 -3.417,6.974 -1.349,1.675 -3.046,3.116 -5.09,4.323 -1.768,1.116 -3.765,1.883 -5.997,2.302 -2.232,0.419 -4.463,0.627 -6.696,0.627 -2.697,0 -4.999,-0.23 -6.903,-0.696 -1.907,-0.465 -3.417,-1.256 -4.533,-2.371 -1.21,-1.116 -1.906,-2.626 -2.092,-4.533 -0.188,-1.904 0.14,-4.114 0.977,-6.625 0.929,-2.88 2.161,-5.275 3.696,-7.183 1.534,-1.904 3.229,-3.417 5.09,-4.533 2.138,-1.115 4.206,-1.882 6.207,-2.301 1.999,-0.419 4.207,-0.627 6.625,-0.627 2.416,0 4.603,0.257 6.555,0.767 1.953,0.512 3.486,1.325 4.603,2.44 1.115,1.116 1.789,2.605 2.021,4.463 0.231,1.86 -0.117,4.185 -1.046,6.973 z"
|
||||
style="fill:url(#_Linear3);fill-rule:nonzero"
|
||||
id="path3-7" />
|
||||
</g><g
|
||||
id="path3730"
|
||||
transform="matrix(0.49366852,0,0,0.49366852,212.40542,15.350723)"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421">
|
||||
<path
|
||||
d="m 312.14,128.807 c 2.928,-0.698 9.041,-1.046 18.339,-1.046 4.833,0 9.506,0.487 14.018,1.465 4.508,0.976 9.042,2.209 13.598,3.696 L 362,121.066 c -2.698,-0.744 -6.625,-1.487 -11.787,-2.231 -5.159,-0.744 -10.669,-1.116 -16.526,-1.116 -17.574,0 -30.405,1.513 -38.493,4.533 -8.089,3.022 -12.879,6.812 -14.366,11.366 -1.115,3.44 -0.348,6.346 2.302,8.717 2.65,2.371 7.322,4.115 14.016,5.229 2.511,0.467 6.624,0.838 12.343,1.117 5.718,0.279 9.46,0.557 11.228,0.836 3.717,0.373 6.205,0.837 7.461,1.396 1.254,0.558 1.695,1.394 1.325,2.511 -0.467,1.303 -1.976,2.279 -4.533,2.928 -2.559,0.652 -6.3,0.977 -11.227,0.977 -0.77,0 -1.513,-0.003 -2.241,-0.007 -1.846,-0.101 -3.858,-0.272 -5.791,-0.476 -2.06,-0.22 -4.118,-0.485 -6.162,-0.795 -4.089,-0.62 -8.132,-1.419 -12.058,-2.439 -3.921,-1.022 -7.734,-2.267 -11.26,-3.813 -0.474,-0.208 -0.932,-0.433 -1.394,-0.654 -2.476,3.979 -5.905,7.451 -10.27,10.396 2.259,1.085 4.539,1.976 6.807,2.742 4.52,1.506 9.034,2.52 13.525,3.266 4.494,0.741 8.969,1.203 13.431,1.472 2.231,0.133 4.459,0.215 6.691,0.248 1.966,0.026 3.882,0.02 5.902,-0.045 12.216,-0.072 22.318,-1.53 30.294,-4.386 8.18,-2.929 13.062,-6.81 14.644,-11.645 1.116,-3.349 0.441,-6.138 -2.021,-8.369 -2.466,-2.231 -6.813,-3.857 -13.041,-4.882 -2.883,-0.371 -5.768,-0.719 -8.647,-1.046 -2.884,-0.324 -5.533,-0.628 -7.951,-0.906 -9.577,-0.65 -14.924,-1.255 -16.039,-1.813 -1.116,-0.558 -1.488,-1.394 -1.116,-2.511 0.467,-1.209 2.164,-2.163 5.094,-2.859 z"
|
||||
style="fill:url(#_Linear4);fill-rule:nonzero"
|
||||
id="path4-6" />
|
||||
</g><g
|
||||
id="path3737"
|
||||
transform="matrix(0.49366852,0,0,0.49366852,212.40542,15.350723)"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421">
|
||||
<path
|
||||
d="m 174.838,124.204 c -6.091,-4.415 -15.924,-6.625 -29.499,-6.625 -13.577,0 -24.826,2.21 -33.751,6.625 -8.926,4.417 -14.4,10.415 -16.911,18.131 l -0.105,0.349 -12.692,39.19 c -5.26,17.631 17.526,24.612 17.526,24.612 l 7.58,-24.612 0.656,-2.106 c 0.592,-1.982 1.192,-3.964 1.781,-5.948 l 1.686,-5.531 c 0.603,-1.832 1.207,-3.662 1.85,-5.481 l 3.979,-10.875 1.993,-5.436 1.429,-3.718 c 0.051,-0.172 0.099,-0.339 0.155,-0.514 0.836,-2.509 1.953,-4.718 3.347,-6.624 1.396,-1.904 3.069,-3.417 5.021,-4.533 1.859,-1.115 3.882,-1.904 6.067,-2.371 2.183,-0.464 4.625,-0.696 7.322,-0.696 2.231,0 4.325,0.208 6.277,0.627 1.952,0.419 3.438,1.186 4.463,2.301 1.301,1.209 2.068,2.65 2.3,4.323 0.231,1.675 -0.117,3.999 -1.046,6.974 -0.931,2.79 -2.116,5.116 -3.557,6.974 -1.442,1.862 -3.091,3.348 -4.951,4.464 -1.861,1.115 -3.905,1.931 -6.136,2.44 -2.231,0.512 -4.558,0.767 -6.973,0.767 -2.419,0 -4.487,-0.208 -6.207,-0.627 -1.721,-0.419 -3.326,-1.186 -4.812,-2.302 -0.112,-0.112 -0.201,-0.247 -0.305,-0.366 l -3.674,10.658 c -0.206,0.613 -0.403,1.228 -0.601,1.842 3.479,0.708 7.507,1.13 12.111,1.256 13.668,0 24.965,-2.231 33.893,-6.694 8.926,-4.464 14.643,-10.552 17.154,-18.271 2.511,-7.719 0.718,-13.786 -5.37,-18.203 z"
|
||||
style="fill:url(#_Linear5);fill-rule:nonzero"
|
||||
id="path5-0" />
|
||||
</g></g></svg>
|
After Width: | Height: | Size: 16 KiB |
425
libxheopus.py
425
libxheopus.py
@ -4,6 +4,7 @@ import struct
|
||||
import pyogg
|
||||
import os
|
||||
import numpy as np
|
||||
from scipy.signal import butter, filtfilt
|
||||
|
||||
def float32_to_int16(data_float32):
|
||||
data_int16 = (data_float32 * 32767).astype(np.int16)
|
||||
@ -51,7 +52,8 @@ class DualOpusEncoder:
|
||||
self.version = version
|
||||
self.samplerate = samplerate
|
||||
self.stereomode = 1 #0 = mono, 1 = stereo LR, 2 = stereo Mid/Side
|
||||
self.enablejoint = False
|
||||
self.audiomono = False
|
||||
|
||||
os.environ["pyogg_win_libopus_version"] = version
|
||||
importlib.reload(pyogg.opus)
|
||||
|
||||
@ -97,20 +99,25 @@ class DualOpusEncoder:
|
||||
"""
|
||||
narrowband:
|
||||
Narrowband typically refers to a limited range of frequencies suitable for voice communication.
|
||||
|
||||
mediumband (unsupported in libopus 1.3+):
|
||||
Mediumband extends the frequency range compared to narrowband, providing better audio quality.
|
||||
|
||||
wideband:
|
||||
Wideband offers an even broader frequency range, resulting in higher audio fidelity compared to narrowband and mediumband.
|
||||
|
||||
superwideband:
|
||||
Superwideband extends the frequency range beyond wideband, further enhancing audio quality.
|
||||
|
||||
fullband (default):
|
||||
Fullband provides the widest frequency range among the listed options, offering the highest audio quality.
|
||||
|
||||
auto: opus is working auto not force
|
||||
"""
|
||||
self.Lencoder.set_bandwidth(bandwidth)
|
||||
self.Rencoder.set_bandwidth(bandwidth)
|
||||
|
||||
def set_stereo_mode(self, mode=1, enablejoint=False):
|
||||
def set_stereo_mode(self, mode=1, audiomono=False):
|
||||
"""
|
||||
0 = mono
|
||||
1 = stereo LR
|
||||
@ -120,7 +127,7 @@ class DualOpusEncoder:
|
||||
mode = 1
|
||||
|
||||
self.stereomode = mode
|
||||
self.enablejoint = enablejoint
|
||||
self.audiomono = audiomono
|
||||
|
||||
def set_frame_size(self, size=60):
|
||||
""" Set the desired frame duration (in milliseconds).
|
||||
@ -162,6 +169,10 @@ class DualOpusEncoder:
|
||||
self.Rencoder.CTL(pyogg.opus.OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, int(phaseinvert))
|
||||
self.Rencoder.CTL(pyogg.opus.OPUS_SET_DTX_REQUEST, int(DTX))
|
||||
|
||||
def enable_voice_mode(self, enable=True, auto=False):
|
||||
self.Lencoder.enable_voice_enhance(enable, auto)
|
||||
self.Rencoder.enable_voice_enhance(enable, auto)
|
||||
|
||||
def encode(self, pcmbytes, directpcm=False):
|
||||
"""input: pcm bytes accept float32/int16 only
|
||||
x74 is mono
|
||||
@ -188,9 +199,9 @@ class DualOpusEncoder:
|
||||
|
||||
intmono = float32_to_int16(mono)
|
||||
|
||||
Mencoded_packet = self.Lencoder.buffered_encode(memoryview(bytearray(intmono)), flush=True)[0][0].tobytes()
|
||||
midencoded_packet = self.Lencoder.buffered_encode(memoryview(bytearray(intmono)), flush=True)[0][0].tobytes()
|
||||
|
||||
dual_encoded_packet = (Mencoded_packet + b'\\x64\\x74')
|
||||
dual_encoded_packet = (midencoded_packet + b'\\x64\\x74')
|
||||
elif self.stereomode == 2:
|
||||
# stereo mid/side (Joint encoding)
|
||||
# convert to float32
|
||||
@ -214,7 +225,7 @@ class DualOpusEncoder:
|
||||
except:
|
||||
loudnessside = 0
|
||||
|
||||
if (loudnessside) <= -50 and self.enablejoint:
|
||||
if (loudnessside) <= -50 and self.audiomono:
|
||||
sideencoded_packet = b"\\xnl"
|
||||
else:
|
||||
sideencoded_packet = self.Rencoder.buffered_encode(memoryview(bytearray(intside)), flush=True)[0][0].tobytes()
|
||||
@ -232,7 +243,194 @@ class DualOpusEncoder:
|
||||
|
||||
return dual_encoded_packet
|
||||
|
||||
class DualOpusDecoder:
|
||||
class PSOpusEncoder:
|
||||
def __init__(self, app="audio", samplerate=48000, version="stable"):
|
||||
"""
|
||||
This version is xHE-Opus v2 (Parametric Stereo)
|
||||
----------------------------- version--------------------------
|
||||
hev2: libopus 1.5.1 (fre:ac)
|
||||
exper: libopus 1.5.1
|
||||
stable: libopus 1.4
|
||||
old: libopus 1.3.1
|
||||
custom: custom opus path you can use "pyogg_win_libopus_custom_path" env to change opus version (windows only)
|
||||
------------------------- App----------------------------------
|
||||
|
||||
Set the encoding mode.
|
||||
|
||||
This must be one of 'voip', 'audio', or 'restricted_lowdelay'.
|
||||
|
||||
'voip': Gives best quality at a given bitrate for voice
|
||||
signals. It enhances the input signal by high-pass
|
||||
filtering and emphasizing formants and
|
||||
harmonics. Optionally it includes in-band forward error
|
||||
correction to protect against packet loss. Use this mode
|
||||
for typical VoIP applications. Because of the enhancement,
|
||||
even at high bitrates the output may sound different from
|
||||
the input.
|
||||
|
||||
'audio': Gives best quality at a given bitrate for most
|
||||
non-voice signals like music. Use this mode for music and
|
||||
mixed (music/voice) content, broadcast, and applications
|
||||
requiring less than 15 ms of coding delay.
|
||||
|
||||
'restricted_lowdelay': configures low-delay mode that
|
||||
disables the speech-optimized mode in exchange for
|
||||
slightly reduced delay. This mode can only be set on an
|
||||
newly initialized encoder because it changes the codec
|
||||
delay.
|
||||
"""
|
||||
self.version = version
|
||||
self.samplerate = samplerate
|
||||
|
||||
os.environ["pyogg_win_libopus_version"] = version
|
||||
importlib.reload(pyogg.opus)
|
||||
|
||||
self.encoder = pyogg.OpusBufferedEncoder()
|
||||
|
||||
self.encoder.set_application(app)
|
||||
|
||||
self.encoder.set_sampling_frequency(samplerate)
|
||||
|
||||
self.encoder.set_channels(1)
|
||||
|
||||
self.set_frame_size()
|
||||
self.set_compression()
|
||||
self.set_feature()
|
||||
self.set_bitrate_mode()
|
||||
self.set_bitrates()
|
||||
self.set_bandwidth()
|
||||
self.set_packet_loss()
|
||||
|
||||
def set_compression(self, level=10):
|
||||
"""complex 0-10 low-hires"""
|
||||
self.encoder.set_compresion_complex(level)
|
||||
|
||||
def set_bitrates(self, bitrates=64000):
|
||||
"""input birate unit: bps"""
|
||||
if bitrates <= 2500:
|
||||
bitrates = 2500
|
||||
|
||||
self.encoder.set_bitrates(bitrates)
|
||||
|
||||
def set_bandwidth(self, bandwidth="fullband"):
|
||||
"""
|
||||
narrowband:
|
||||
Narrowband typically refers to a limited range of frequencies suitable for voice communication.
|
||||
|
||||
mediumband (unsupported in libopus 1.3+):
|
||||
Mediumband extends the frequency range compared to narrowband, providing better audio quality.
|
||||
|
||||
wideband:
|
||||
Wideband offers an even broader frequency range, resulting in higher audio fidelity compared to narrowband and mediumband.
|
||||
|
||||
superwideband:
|
||||
Superwideband extends the frequency range beyond wideband, further enhancing audio quality.
|
||||
|
||||
fullband (default):
|
||||
Fullband provides the widest frequency range among the listed options, offering the highest audio quality.
|
||||
|
||||
auto: opus is working auto not force
|
||||
"""
|
||||
self.encoder.set_bandwidth(bandwidth)
|
||||
|
||||
def set_frame_size(self, size=60):
|
||||
""" Set the desired frame duration (in milliseconds).
|
||||
Valid options are 2.5, 5, 10, 20, 40, or 60ms.
|
||||
Exclusive for HE opus v2 (freac opus) 80, 100 or 120ms.
|
||||
|
||||
@return chunk size
|
||||
"""
|
||||
if self.version != "hev2" and size > 60:
|
||||
raise ValueError("non hev2 can't use framesize > 60")
|
||||
|
||||
self.encoder.set_frame_size(size)
|
||||
|
||||
return int((size / 1000) * self.samplerate)
|
||||
|
||||
def set_packet_loss(self, loss=0):
|
||||
"""input: % percent"""
|
||||
if loss > 100:
|
||||
raise ValueError("percent must <=100")
|
||||
|
||||
self.encoder.set_packets_loss(loss)
|
||||
|
||||
def set_bitrate_mode(self, mode="CVBR"):
|
||||
"""VBR, CVBR, CBR
|
||||
VBR in 1.5.x replace by CVBR
|
||||
"""
|
||||
|
||||
self.encoder.set_bitrate_mode(mode)
|
||||
|
||||
def set_feature(self, prediction=False, phaseinvert=False, DTX=False):
|
||||
self.encoder.CTL(pyogg.opus.OPUS_SET_PREDICTION_DISABLED_REQUEST, int(prediction))
|
||||
self.encoder.CTL(pyogg.opus.OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, int(phaseinvert))
|
||||
self.encoder.CTL(pyogg.opus.OPUS_SET_DTX_REQUEST, int(DTX))
|
||||
|
||||
def enable_voice_mode(self, enable=True, auto=False):
|
||||
self.encoder.enable_voice_enhance(enable, auto)
|
||||
|
||||
def __parameterization(self, stereo_signal):
|
||||
# Convert int16 to float32 for processing
|
||||
stereo_signal = stereo_signal.astype(np.float32) / 32768.0
|
||||
|
||||
# Reshape stereo_signal into a 2D array with two channels
|
||||
stereo_signal = stereo_signal.reshape((-1, 2))
|
||||
|
||||
# Calculate the magnitude spectrogram for each channel
|
||||
mag_left = np.abs(np.fft.fft(stereo_signal[:, 0]))
|
||||
mag_right = np.abs(np.fft.fft(stereo_signal[:, 1]))
|
||||
|
||||
# Calculate the phase difference between the left and right channels
|
||||
phase_diff = np.angle(stereo_signal[:, 0]) - np.angle(stereo_signal[:, 1])
|
||||
|
||||
# Compute other spatial features
|
||||
# Calculate stereo width
|
||||
stereo_width = np.mean(np.correlate(mag_left, mag_right, mode='full'))
|
||||
|
||||
# Calculate phase coherence
|
||||
phase_coherence = np.mean(np.cos(phase_diff))
|
||||
|
||||
# Calculate stereo panning
|
||||
stereo_panning_left = np.mean(mag_left / (mag_left + mag_right))
|
||||
stereo_panning_right = np.mean(mag_right / (mag_left + mag_right))
|
||||
|
||||
pan = stereo_panning_right - stereo_panning_left
|
||||
|
||||
# Return the derived parameters
|
||||
return (int(stereo_width), phase_coherence, pan)
|
||||
|
||||
def encode(self, pcmbytes, directpcm=False):
|
||||
"""input: pcm bytes accept float32/int16 only
|
||||
x74 is mono
|
||||
x75 is stereo LR
|
||||
x76 is stereo mid/side
|
||||
|
||||
xnl is no side audio
|
||||
"""
|
||||
if directpcm:
|
||||
if pcmbytes.dtype == np.float32:
|
||||
pcm = (pcmbytes * 32767).astype(np.int16)
|
||||
elif pcmbytes.dtype == np.int16:
|
||||
pcm = pcmbytes.astype(np.int16)
|
||||
else:
|
||||
raise TypeError("accept only int16/float32")
|
||||
else:
|
||||
pcm = np.frombuffer(pcmbytes, dtype=np.int16)
|
||||
|
||||
pcmreshaped = pcm.reshape(-1, 2)
|
||||
|
||||
mono_data = np.mean(pcmreshaped * 0.5, axis=1, dtype=np.int16)
|
||||
|
||||
stereodata = self.__parameterization(pcmreshaped)
|
||||
packedstereodata = struct.pack('iff', *stereodata)
|
||||
|
||||
encoded_packet = self.encoder.buffered_encode(memoryview(bytearray(mono_data)), flush=True)[0][0].tobytes()
|
||||
|
||||
encoded_packet = (encoded_packet + b'\\x21\\x75' + packedstereodata)
|
||||
|
||||
return encoded_packet
|
||||
|
||||
class xOpusDecoder:
|
||||
def __init__(self, sample_rate=48000):
|
||||
self.Ldecoder = pyogg.OpusDecoder()
|
||||
self.Rdecoder = pyogg.OpusDecoder()
|
||||
@ -243,22 +441,189 @@ class DualOpusDecoder:
|
||||
self.Ldecoder.set_sampling_frequency(sample_rate)
|
||||
self.Rdecoder.set_sampling_frequency(sample_rate)
|
||||
|
||||
self.__prev_pan = 0.0
|
||||
|
||||
def __smooth(self, value, prev_value, alpha=0.1):
|
||||
return alpha * value + (1 - alpha) * prev_value
|
||||
|
||||
def __expand_and_pan(self, input_signal, pan_value, expansion_factor, gain):
|
||||
"""
|
||||
Apply stereo expansion and panning to an input audio signal.
|
||||
|
||||
Parameters:
|
||||
- input_signal: Input audio signal (numpy array of int16).
|
||||
- expansion_factor: Factor to expand the stereo width (0 to 1).
|
||||
- pan_value: Pan value (-1 to 1, where -1 is full left, 1 is full right).
|
||||
- gain: Gain factor to adjust the volume.
|
||||
|
||||
Returns:
|
||||
- output_signal: Processed audio signal (stereo, numpy array of int16).
|
||||
"""
|
||||
|
||||
# Convert int16 to float32 for processing
|
||||
input_signal_float = input_signal.astype(np.float32) / 32768.0
|
||||
|
||||
# Separate the channels
|
||||
left_channel = input_signal_float[:, 0]
|
||||
right_channel = input_signal_float[:, 1]
|
||||
|
||||
# Apply panning
|
||||
pan_left = (1 - pan_value) / 2
|
||||
pan_right = (1 + pan_value) / 2
|
||||
left_channel *= pan_left
|
||||
right_channel *= pan_right
|
||||
|
||||
# Apply stereo expansion
|
||||
center = (left_channel + right_channel) / 2
|
||||
left_channel = center + (left_channel - center) * expansion_factor
|
||||
right_channel = center + (right_channel - center) * expansion_factor
|
||||
|
||||
# Apply gain
|
||||
left_channel *= gain
|
||||
right_channel *= gain
|
||||
|
||||
# Ensure no clipping by normalizing if necessary
|
||||
max_val = max(np.max(np.abs(left_channel)), np.max(np.abs(right_channel)))
|
||||
if max_val > 1.0:
|
||||
left_channel /= max_val
|
||||
right_channel /= max_val
|
||||
|
||||
# Merge the channels
|
||||
output_signal = np.stack((left_channel, right_channel), axis=-1)
|
||||
|
||||
return (output_signal * 32767).astype(np.int16)
|
||||
|
||||
def __mix_stereo_signals(self, signal1, signal2, volume1=1.0, volume2=1.0):
|
||||
# Ensure both signals have the same length
|
||||
length = max(len(signal1), len(signal2))
|
||||
signal1 = np.pad(signal1, ((0, length - len(signal1)), (0, 0)), mode='constant')
|
||||
signal2 = np.pad(signal2, ((0, length - len(signal2)), (0, 0)), mode='constant')
|
||||
|
||||
# Convert signals to float
|
||||
signal1 = signal1.astype(np.float32)
|
||||
signal2 = signal2.astype(np.float32)
|
||||
|
||||
# Adjust volume
|
||||
signal1 *= volume1
|
||||
signal2 *= volume2
|
||||
|
||||
# Mix the signals
|
||||
mixed_signal = signal1 + signal2
|
||||
|
||||
# Normalize the mixed signal to prevent clipping
|
||||
max_amplitude = np.max(np.abs(mixed_signal))
|
||||
if max_amplitude > 32767:
|
||||
mixed_signal = (mixed_signal / max_amplitude) * 32767
|
||||
|
||||
return mixed_signal.astype(np.int16)
|
||||
|
||||
def __apply_smoothing_window(self, audio_data, window_size):
|
||||
"""
|
||||
Apply a smoothing window to the beginning and end of the audio data.
|
||||
|
||||
Parameters:
|
||||
- audio_data: 2D numpy array with shape (num_samples, 2)
|
||||
- window_size: Size of the smoothing window in samples
|
||||
|
||||
Returns:
|
||||
- smoothed_audio_data: 2D numpy array with the smoothing window applied
|
||||
"""
|
||||
window = np.hanning(window_size * 2)
|
||||
fade_in = window[:window_size]
|
||||
fade_out = window[-window_size:]
|
||||
|
||||
audio_data[:window_size, :] *= fade_in[:, np.newaxis]
|
||||
audio_data[-window_size:, :] *= fade_out[:, np.newaxis]
|
||||
|
||||
return audio_data
|
||||
|
||||
def __stereo_widening_effect(self, data, delay_samples=10, gain=0.8, window_size=100):
|
||||
audio_data = data.reshape(-1, 2)
|
||||
|
||||
# Convert int16 to float32 for processing
|
||||
audio_data = audio_data.astype(np.float32)
|
||||
|
||||
# Apply delay to the right channel
|
||||
right_channel = np.roll(audio_data[:, 1], delay_samples)
|
||||
|
||||
# Apply gain to both channels
|
||||
audio_data[:, 0] *= gain
|
||||
right_channel *= gain
|
||||
|
||||
# Combine channels back into stereo
|
||||
widened_audio_data = np.stack((audio_data[:, 0], right_channel), axis=1)
|
||||
|
||||
# Apply smoothing window to reduce clicks
|
||||
widened_audio_data = self.__apply_smoothing_window(widened_audio_data, window_size)
|
||||
|
||||
# Clip to avoid overflow
|
||||
widened_audio_data = np.clip(widened_audio_data, -32768, 32767)
|
||||
|
||||
# Convert float32 back to int16
|
||||
widened_audio_data = widened_audio_data.astype(np.int16)
|
||||
|
||||
return widened_audio_data
|
||||
|
||||
def __apply_phase_coherence_to_stereo(self, signal, phase_coherence):
|
||||
# Convert phase coherence to phase shift in radians
|
||||
phase_shift = np.arccos(phase_coherence)
|
||||
# Apply phase shift to both channels
|
||||
return self.__apply_phase_shift(signal, phase_shift)
|
||||
|
||||
# Function to apply phase shift to one channel
|
||||
def __apply_phase_shift(self, signal, phase_shift):
|
||||
# Convert to complex
|
||||
signal_complex = signal.astype(np.complex64)
|
||||
# Apply phase shift
|
||||
shifted_signal = signal_complex * np.exp(1j * phase_shift)
|
||||
return shifted_signal.astype(np.int16)
|
||||
|
||||
def __butter_lowpass_filter_stereo(self, data, cutoff, fs, order=5):
|
||||
nyq = 0.5 * fs
|
||||
normal_cutoff = cutoff / nyq
|
||||
b, a = butter(order, normal_cutoff, btype='low', analog=False)
|
||||
filtered_data = np.apply_along_axis(lambda x: filtfilt(b, a, x), axis=0, arr=data)
|
||||
return filtered_data.astype(np.int16)
|
||||
|
||||
def __synthstereo(self, mono_signal, stereodata):
|
||||
pan = stereodata[2]
|
||||
|
||||
# Smooth the pan value
|
||||
pan = self.__smooth(pan, self.__prev_pan, alpha=0.25)
|
||||
self.__prev_pan = pan
|
||||
|
||||
stereo_exp = stereodata[0] / 10000
|
||||
|
||||
try:
|
||||
delayed = self.__stereo_widening_effect(mono_signal, int(stereo_exp), 1, int(stereo_exp) * 2)
|
||||
except:
|
||||
delayed = mono_signal
|
||||
|
||||
l1 = self.__expand_and_pan(mono_signal, pan, 1, 2)
|
||||
|
||||
stereo_signal_shifted = self.__apply_phase_coherence_to_stereo(delayed, stereodata[1])
|
||||
|
||||
return self.__mix_stereo_signals(l1, stereo_signal_shifted, volume1=1, volume2=0.5).astype(np.int16)
|
||||
|
||||
def decode(self, dualopusbytes: bytes, outputformat=np.int16):
|
||||
# mode check
|
||||
if b"\\x64\\x74" in dualopusbytes:
|
||||
mode = 0
|
||||
dualopusbytespilted = dualopusbytes.split(b'\\x64\\x74')
|
||||
xopusbytespilted = dualopusbytes.split(b'\\x64\\x74')
|
||||
elif b"\\x64\\x76" in dualopusbytes:
|
||||
mode = 2
|
||||
dualopusbytespilted = dualopusbytes.split(b'\\x64\\x76')
|
||||
xopusbytespilted = dualopusbytes.split(b'\\x64\\x76')
|
||||
elif b"\\x64\\x75" in dualopusbytes:
|
||||
mode = 1
|
||||
dualopusbytespilted = dualopusbytes.split(b'\\x64\\x75')
|
||||
xopusbytespilted = dualopusbytes.split(b'\\x64\\x75')
|
||||
elif b"\\x21\\x75" in dualopusbytes:
|
||||
mode = 3 # v2
|
||||
xopusbytespilted = dualopusbytes.split(b'\\x21\\x75')
|
||||
else:
|
||||
raise TypeError("this is not dual opus")
|
||||
raise TypeError("this is not xopus bytes")
|
||||
|
||||
if mode == 0: # mono
|
||||
Mencoded_packet = dualopusbytespilted[0]
|
||||
Mencoded_packet = xopusbytespilted[0]
|
||||
decoded_left_channel_pcm = self.Ldecoder.decode(memoryview(bytearray(Mencoded_packet)))
|
||||
Mpcm = np.frombuffer(decoded_left_channel_pcm, dtype=np.int16)
|
||||
|
||||
@ -266,8 +631,8 @@ class DualOpusDecoder:
|
||||
|
||||
elif mode == 2:
|
||||
# stereo mid/side (Joint encoding)
|
||||
Mencoded_packet = dualopusbytespilted[0]
|
||||
Sencoded_packet = dualopusbytespilted[1]
|
||||
Mencoded_packet = xopusbytespilted[0]
|
||||
Sencoded_packet = xopusbytespilted[1]
|
||||
|
||||
decoded_mid_channel_pcm = self.Ldecoder.decode(memoryview(bytearray(Mencoded_packet)))
|
||||
Mpcm = np.frombuffer(decoded_mid_channel_pcm, dtype=np.int16)
|
||||
@ -279,18 +644,34 @@ class DualOpusDecoder:
|
||||
Mpcm = int16_to_float32(Mpcm)
|
||||
Spcm = int16_to_float32(Spcm)
|
||||
|
||||
L = (Mpcm + Spcm) / 1.5
|
||||
R = (Mpcm - Spcm) / 1.5
|
||||
L = Mpcm + Spcm
|
||||
R = Mpcm - Spcm
|
||||
|
||||
stereo_signal = np.column_stack((L, R))
|
||||
|
||||
max_amplitude = np.max(np.abs(stereo_signal))
|
||||
if max_amplitude > 1.0:
|
||||
stereo_signal /= max_amplitude
|
||||
|
||||
stereo_signal = float32_to_int16(stereo_signal)
|
||||
else:
|
||||
stereo_signal = np.column_stack((Mpcm, Mpcm))
|
||||
elif mode == 3:
|
||||
Mencoded_packet = xopusbytespilted[0]
|
||||
stereodatapacked = xopusbytespilted[1]
|
||||
|
||||
stereodata = struct.unpack('iff', stereodatapacked)
|
||||
|
||||
mono_channel_pcm = self.Ldecoder.decode(memoryview(bytearray(Mencoded_packet)))
|
||||
Mpcm = np.frombuffer(mono_channel_pcm, dtype=np.int16)
|
||||
|
||||
stereo_audio = np.stack((Mpcm, Mpcm)).T.reshape(-1, 2)
|
||||
|
||||
stereo_signal = self.__synthstereo(stereo_audio, stereodata)
|
||||
else:
|
||||
# stereo LR
|
||||
Lencoded_packet = dualopusbytespilted[0]
|
||||
Rencoded_packet = dualopusbytespilted[1]
|
||||
Lencoded_packet = xopusbytespilted[0]
|
||||
Rencoded_packet = xopusbytespilted[1]
|
||||
|
||||
decoded_left_channel_pcm = self.Ldecoder.decode(memoryview(bytearray(Lencoded_packet)))
|
||||
decoded_right_channel_pcm = self.Rdecoder.decode(memoryview(bytearray(Rencoded_packet)))
|
||||
@ -368,10 +749,13 @@ class FooterContainer:
|
||||
return loudness_avg, length
|
||||
|
||||
class XopusWriter:
|
||||
def __init__(self, file, encoder: DualOpusEncoder, metadata={}):
|
||||
def __init__(self, file, encoder: DualOpusEncoder, metadata=None):
|
||||
self.file = file
|
||||
self.encoder = encoder
|
||||
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
|
||||
systemmetadata = {
|
||||
"format": "Xopus",
|
||||
"audio": {
|
||||
@ -445,7 +829,8 @@ class XopusReader:
|
||||
else:
|
||||
try:
|
||||
yield decoder.decode(data)
|
||||
except:
|
||||
except Exception as e:
|
||||
#print(e)
|
||||
yield b""
|
||||
else:
|
||||
decodedlist = []
|
||||
|
@ -1,5 +1,5 @@
|
||||
import pyaudio
|
||||
from libxheopus import DualOpusDecoder, XopusReader
|
||||
from libxheopus import xOpusDecoder, XopusReader
|
||||
import argparse
|
||||
from tqdm import tqdm
|
||||
import wave
|
||||
@ -16,7 +16,7 @@ progress = tqdm()
|
||||
# Initialize PyAudio
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
decoder = DualOpusDecoder()
|
||||
decoder = xOpusDecoder()
|
||||
|
||||
xopusdecoder = XopusReader(args.input)
|
||||
|
||||
|
79
realtime.py
Normal file
79
realtime.py
Normal file
@ -0,0 +1,79 @@
|
||||
import numpy as np
|
||||
import pyaudio
|
||||
import os
|
||||
from libxheopus import DualOpusEncoder, xOpusDecoder
|
||||
|
||||
encoder = DualOpusEncoder("restricted_lowdelay", 48000, "hev2")
|
||||
encoder.set_bitrates(24000)
|
||||
encoder.set_bitrate_mode("CVBR")
|
||||
encoder.set_bandwidth("fullband")
|
||||
encoder.set_compression(10)
|
||||
desired_frame_size = encoder.set_frame_size(120)
|
||||
|
||||
decoder = xOpusDecoder(48000)
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
device_name_input = "Line 5 (Virtual Audio Cable)"
|
||||
device_index_input = 0
|
||||
for i in range(p.get_device_count()):
|
||||
dev = p.get_device_info_by_index(i)
|
||||
if dev['name'] == device_name_input:
|
||||
device_index_input = dev['index']
|
||||
break
|
||||
|
||||
device_name_output = "Speakers (2- USB Audio DAC )"
|
||||
device_index_output = 0
|
||||
for i in range(p.get_device_count()):
|
||||
dev = p.get_device_info_by_index(i)
|
||||
if dev['name'] == device_name_output:
|
||||
device_index_output = dev['index']
|
||||
break
|
||||
|
||||
streaminput = p.open(format=pyaudio.paInt16, channels=2, rate=48000, input=True, input_device_index=device_index_input)
|
||||
streamoutput = p.open(format=pyaudio.paInt16, channels=2, rate=48000, output=True, output_device_index=device_index_output)
|
||||
|
||||
print(desired_frame_size)
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
pcm = np.frombuffer(streaminput.read(desired_frame_size, exception_on_overflow=False), dtype=np.int16)
|
||||
|
||||
if len(pcm) == 0:
|
||||
# If PCM is empty, break the loop
|
||||
break
|
||||
|
||||
encoded_packets = encoder.encode(pcm)
|
||||
|
||||
print(len(pcm), "-encoded->", len(encoded_packets))
|
||||
|
||||
|
||||
# print(encoded_packet)
|
||||
try:
|
||||
decoded_pcm = decoder.decode(encoded_packets)
|
||||
except Exception as e:
|
||||
decoded_pcm = b""
|
||||
|
||||
|
||||
# Check if the decoded PCM is empty or not
|
||||
if len(decoded_pcm) > 0:
|
||||
pcm_to_write = np.frombuffer(decoded_pcm, dtype=np.int16)
|
||||
|
||||
streamoutput.write(pcm_to_write.astype(np.int16).tobytes())
|
||||
else:
|
||||
print("Decoded PCM is empty")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("Interrupted by user")
|
||||
finally:
|
||||
# Clean up PyAudio streams and terminate PyAudio
|
||||
streaminput.stop_stream()
|
||||
streaminput.close()
|
||||
streamoutput.stop_stream()
|
||||
streamoutput.close()
|
||||
p.terminate()
|
Loading…
x
Reference in New Issue
Block a user