Skip to content

Commit d863c38

Browse files
Merge pull request #40 from goatchurchprime/fixothersampling
Fixothersampling
2 parents d78012a + 32f7cde commit d863c38

9 files changed

+100
-79
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ scons apply_patches # optional
155155
scons build_opus # build opus using cmake
156156
scons build_rnnoise # build opus using cmake
157157
scons # build this library
158+
cp addons/twovoip/libs/*.so example/addons/twovoip/libs/
158159
```
159160

160161
To compile for another platform like web, the commands are
@@ -183,7 +184,7 @@ as your `GodotEngine.exe` so that it finds and links it.
183184
For the addon to work correctly, `twovoip_lipsync` and `twovoip` cannot be used in the same project.
184185

185186

186-
### Nixos automated
187+
### Nixos automated (not working)
187188

188189
The build system is defined by the flake.nix file
189190

example/default_bus_layout.tres

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
[gd_resource type="AudioBusLayout" load_steps=3 format=3 uid="uid://orx4iw038t0c"]
1+
[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://orx4iw038t0c"]
22

3-
[sub_resource type="AudioEffectPitchShift" id="AudioEffectPitchShift_jq6ag"]
3+
[sub_resource type="AudioEffectPitchShift" id="AudioEffectPitchShift_urnej"]
44
resource_name = "PitchShift"
5-
fft_size = 1
6-
7-
[sub_resource type="AudioEffectOpusChunked" id="AudioEffectOpusChunked_v1e16"]
8-
resource_name = "OpusChunked"
95

106
[resource]
117
bus/1/name = &"SpeechBus"
@@ -14,13 +10,11 @@ bus/1/mute = false
1410
bus/1/bypass_fx = false
1511
bus/1/volume_db = 0.0
1612
bus/1/send = &"Master"
17-
bus/1/effect/0/effect = SubResource("AudioEffectPitchShift_jq6ag")
13+
bus/1/effect/0/effect = SubResource("AudioEffectPitchShift_urnej")
1814
bus/1/effect/0/enabled = true
1915
bus/2/name = &"MicrophoneBus"
2016
bus/2/solo = false
2117
bus/2/mute = true
2218
bus/2/bypass_fx = false
2319
bus/2/volume_db = 0.0
2420
bus/2/send = &"Master"
25-
bus/2/effect/0/effect = SubResource("AudioEffectOpusChunked_v1e16")
26-
bus/2/effect/0/enabled = true

example/radiomqtt/member.gd

+6-4
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ func processheaderpacket(h):
8282
#$AudioStreamPlayer.play()
8383
setupaudioshader()
8484

85-
if opusframesize != 0 and audiostreamopuschunked == null:
85+
if opusframesize != 0 and audiostreamopuschunked == null and not h["noopuscompression"]:
8686
print("Compressed opus stream received that we cannot decompress")
87+
if audiostreamopuschunked != null:
88+
audiostreamopuschunked.resetdecoder()
8789
audioserveroutputlatency = AudioServer.get_output_latency()
8890
print("audioserveroutputlatency ", audioserveroutputlatency)
8991

@@ -130,8 +132,8 @@ func _process(delta):
130132
var chunkv1 = 0.0
131133
while audiostreamopuschunked.chunk_space_available():
132134
if resampledpacketsbuffer != null and len(resampledpacketsbuffer) != 0:
133-
#audiostreamopuschunked.push_audio_chunk(resampledpacketsbuffer.pop_front())
134-
var audiochunk = audiostreamopuschunked.resample_chunk(resampledpacketsbuffer.pop_front())
135+
var resampledaudiochunk = resampledpacketsbuffer.pop_front()
136+
var audiochunk = audiostreamopuschunked.resample_chunk(resampledaudiochunk)
135137
audiostreamopuschunked.push_audio_chunk(audiochunk)
136138
elif len(audiopacketsbuffer) != 0:
137139
audiostreamopuschunked.push_audio_chunk(audiopacketsbuffer.pop_front())
@@ -161,7 +163,7 @@ func _process(delta):
161163
elif bufferlengthtime < audiobufferregulationtime:
162164
$AudioStreamPlayer.pitch_scale = 1.0
163165
else:
164-
var w = inverse_lerp(audiobufferregulationtime, audioserveroutputlatency + audiobuffersize/audiosamplerate, bufferlengthtime)
166+
var w = inverse_lerp(audiobufferregulationtime, audioserveroutputlatency + audiobuffersize*1.0/audiosamplerate, bufferlengthtime)
165167
$AudioStreamPlayer.pitch_scale = lerp(1.0, audiobufferregulationpitch, w)
166168
#show some view of the speedup rate on here
167169

example/radiomqtt/radiomqtt.gd

+38-16
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ var opusframesize : int = 960
2222
var opuscomplexity : int = 5
2323
var opusoptimizeforvoice : bool = true
2424

25-
var prefixbytes = PackedByteArray([0,0,0,0,1])
25+
var prefixbytes = PackedByteArray([23])
2626
var mqttpacketencodebase64 : bool = false
27+
var noopuscompression = false
2728

2829
var recordedsamples = [ ]
2930
var recordedopuspackets = [ ]
@@ -87,12 +88,13 @@ func _ready():
8788
if ClassDB.can_instantiate("AudioEffectOpusChunked"):
8889
audioopuschunkedeffect = ClassDB.instantiate("AudioEffectOpusChunked")
8990

90-
for effect_idx in range(AudioServer.get_bus_effect_count(speechbusidx)):
91-
var laudioeffectonspeechbus : AudioEffect = AudioServer.get_bus_effect(speechbusidx, effect_idx)
92-
if laudioeffectonspeechbus.is_class("AudioEffectPitchShift"):
93-
audioeffectpitchshift = laudioeffectonspeechbus
94-
audioeffectpitchshiftidx = effect_idx
95-
break
91+
if speechbusidx != -1:
92+
for effect_idx in range(AudioServer.get_bus_effect_count(speechbusidx)):
93+
var laudioeffectonspeechbus : AudioEffect = AudioServer.get_bus_effect(speechbusidx, effect_idx)
94+
if laudioeffectonspeechbus.is_class("AudioEffectPitchShift"):
95+
audioeffectpitchshift = laudioeffectonspeechbus
96+
audioeffectpitchshiftidx = effect_idx
97+
break
9698

9799
updatesamplerates()
98100
for i in range(1, len(visemes)):
@@ -104,7 +106,7 @@ func _ready():
104106

105107
SelfMember.audiobufferregulationtime = 3600.0
106108

107-
func resamplerecordedsamples(orgsamples, newsamplesize):
109+
func rechunkrecordedchunks(orgsamples, newsamplesize):
108110
assert (newsamplesize > 0)
109111
var res = [ ]
110112
var currentsample = PackedVector2Array()
@@ -133,12 +135,13 @@ func updatesamplerates():
133135

134136
print("aaa audiosamplesize ", audiosamplesize, " audiosamplerate ", audiosamplerate)
135137

136-
var noopuscompression = false
138+
noopuscompression = false
137139
if opussamplerate == audioresamplerate:
138140
$VBoxFrameLength/HBoxOpusExtra/Compressed.disabled = false
139141
if not $VBoxFrameLength/HBoxOpusExtra/Compressed.button_pressed:
140142
noopuscompression = true
141143
else:
144+
$VBoxFrameLength/HBoxOpusExtra/Compressed.button_pressed = false
142145
$VBoxFrameLength/HBoxOpusExtra/Compressed.disabled = true
143146
noopuscompression = true
144147
opusbitrate = int($VBoxFrameLength/HBoxOpusBitRate/BitRate.value)
@@ -163,9 +166,10 @@ func updatesamplerates():
163166
recordedheader = { "opusframesize":audioresamplesize,
164167
"opussamplerate":audioresamplerate,
165168
"prefixbyteslength":len(prefixbytes),
169+
"noopuscompression":noopuscompression,
166170
"mqttpacketencoding":"base64" if mqttpacketencodebase64 else "binary" }
167171
if len(recordedsamples) != 0 and len(recordedsamples[0]) != audiosamplesize:
168-
recordedsamples = resamplerecordedsamples(recordedsamples, audiosamplesize)
172+
recordedsamples = rechunkrecordedchunks(recordedsamples, audiosamplesize)
169173
recordedopuspacketsMemSize = 0
170174
recordedopuspackets = null
171175
recordedresampledpackets = null
@@ -176,7 +180,7 @@ func updatesamplerates():
176180
recordedopuspackets.append(opuspacket)
177181
recordedopuspacketsMemSize += opuspacket.size()
178182
$VBoxPlayback/HBoxPlaycount/GridContainer/FrameCount.text = str(len(recordedopuspackets))
179-
else:
183+
elif audioopuschunkedeffect != null:
180184
recordedresampledpackets = [ ]
181185
var denoise = not $HBoxBigButtons/VBoxPTT/Denoise.disabled and $HBoxBigButtons/VBoxPTT/Denoise.button_pressed
182186
for s in recordedsamples:
@@ -187,6 +191,9 @@ func updatesamplerates():
187191
$VBoxPlayback/HBoxPlaycount/GridContainer/FrameCount.text = "1"
188192
if len(recordedresampledpackets):
189193
recordedopuspacketsMemSize = len(recordedresampledpackets)*len(recordedresampledpackets[0])*4
194+
else:
195+
recordedresampledpackets = null
196+
190197

191198
$VBoxPlayback/HBoxPlaycount/GridContainer/Totalbytes.text = str(recordedopuspacketsMemSize)
192199
var tm = len(recordedsamples)*frametimems*0.001
@@ -229,8 +236,16 @@ var talkingstarttime = 0
229236
func starttalking():
230237
currentlytalking = true
231238
recordedsamples = [ ]
232-
recordedopuspackets = [ ]
233-
recordedresampledpackets = null
239+
if not noopuscompression:
240+
recordedopuspackets = [ ]
241+
recordedresampledpackets = null
242+
else:
243+
recordedopuspackets = null
244+
if audioopuschunkedeffect != null:
245+
recordedresampledpackets = [ ]
246+
else:
247+
recordedresampledpackets = null
248+
234249
$VBoxPlayback/HBoxPlaycount/GridContainer/FrameCount.text = str(0)
235250
$VBoxPlayback/HBoxPlaycount/GridContainer/TimeSecs.text = str(0)
236251
recordedopuspacketsMemSize = 0
@@ -248,8 +263,8 @@ func starttalking():
248263
leadtimems -= frametimems
249264
Dundroppedchunks += 1
250265
print("Undropped ", Dundroppedchunks, " chunks")
251-
if opusframesize != 0:
252-
audioopuschunkedeffect.flush_opus_encoder(false)
266+
if opusframesize != 0 and $VBoxFrameLength/HBoxOpusExtra/Compressed.button_pressed:
267+
audioopuschunkedeffect.resetencoder()
253268

254269
func _on_mic_working_toggled(toggled_on):
255270
print("_on_mic_working_toggled ", $AudioStreamMicrophone.playing, " to ", toggled_on)
@@ -321,7 +336,9 @@ func _process(_delta):
321336
if currentlytalking:
322337
if len(recordedsamples) < maxrecordedsamples:
323338
recordedsamples.append(audiosamples)
324-
if opusframesize != 0:
339+
if noopuscompression:
340+
recordedresampledpackets.append(audioopuschunkedeffect.read_chunk(true))
341+
elif opusframesize != 0:
325342
var opuspacket = audioopuschunkedeffect.read_opus_packet(prefixbytes)
326343
$MQTTnetwork.transportaudiopacket(opuspacket, mqttpacketencodebase64)
327344
if len(recordedopuspackets) < maxrecordedsamples:
@@ -392,6 +409,11 @@ func _on_play_pressed():
392409
elif recordedresampledpackets != null:
393410
SelfMember.processheaderpacket(h)
394411
SelfMember.resampledpacketsbuffer = recordedresampledpackets.duplicate()
412+
var resampledaudiochunk_blank = PackedVector2Array()
413+
resampledaudiochunk_blank.resize(h["opusframesize"])
414+
for i in range(5):
415+
SelfMember.audiostreamopuschunked.resample_chunk(resampledaudiochunk_blank)
416+
395417
elif recordedsamples and SelfMember.audiostreamgeneratorplayback != null:
396418
SelfMember.audiosamplesize = audiosamplesize
397419
SelfMember.audiopacketsbuffer = recordedsamples.duplicate()

example/radiomqtt/radiomqtt.tscn

+1
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ text = "Resampled:"
356356
[node name="ResampleRate" type="SpinBox" parent="VBoxFrameLength/HBoxAudioFrame"]
357357
custom_minimum_size = Vector2(95, 0)
358358
layout_mode = 2
359+
tooltip_text = "Needs to match the Opus sample rate to be compressed"
359360
min_value = 20.0
360361
max_value = 96000.0
361362
value = 48000.0

src/audio_effect_opus_chunked.cpp

+25-30
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ void AudioEffectOpusChunked::_bind_methods() {
7575
ClassDB::bind_method(D_METHOD("drop_chunk"), &AudioEffectOpusChunked::drop_chunk);
7676
ClassDB::bind_method(D_METHOD("undrop_chunk"), &AudioEffectOpusChunked::undrop_chunk);
7777
ClassDB::bind_method(D_METHOD("read_opus_packet", "prefixbytes"), &AudioEffectOpusChunked::read_opus_packet);
78-
ClassDB::bind_method(D_METHOD("flush_opus_encoder", "denoise"), &AudioEffectOpusChunked::flush_opus_encoder);
78+
ClassDB::bind_method(D_METHOD("resetencoder"), &AudioEffectOpusChunked::resetencoder);
7979
ClassDB::bind_method(D_METHOD("chunk_to_opus_packet", "prefixbytes", "audiosamples", "denoise"), &AudioEffectOpusChunked::chunk_to_opus_packet);
8080
ClassDB::bind_method(D_METHOD("chunk_resample", "audiosamples", "denoise", "backresample"), &AudioEffectOpusChunked::chunk_resample);
8181
}
@@ -92,7 +92,7 @@ AudioEffectOpusChunked::AudioEffectOpusChunked() {
9292

9393
AudioEffectOpusChunked::~AudioEffectOpusChunked()
9494
{
95-
resetencoder(17);
95+
deleteencoder();
9696
};
9797

9898
Ref<AudioEffectInstance> AudioEffectOpusChunked::_instantiate() {
@@ -102,8 +102,22 @@ Ref<AudioEffectInstance> AudioEffectOpusChunked::_instantiate() {
102102
return ins;
103103
}
104104

105-
void AudioEffectOpusChunked::resetencoder(int Dreason) {
106-
godot::UtilityFunctions::prints("resetting AudioEffectOpusChunked", Dreason);
105+
void AudioEffectOpusChunked::resetencoder() {
106+
if ((opusframesize == 0) || (chunknumber == -1))
107+
return;
108+
if (speexresampler != NULL)
109+
speex_resampler_reset_mem(speexresampler);
110+
if (speexbackresampler != NULL)
111+
speex_resampler_reset_mem(speexbackresampler);
112+
if (st != NULL)
113+
rnnoise_init(st, NULL);
114+
if (opusencoder != NULL)
115+
opus_encoder_ctl(opusencoder, OPUS_RESET_STATE);
116+
lastopuschunk = chunknumber - 1;
117+
}
118+
119+
120+
void AudioEffectOpusChunked::deleteencoder() {
107121
if (speexresampler != NULL) {
108122
speex_resampler_destroy(speexresampler);
109123
speexresampler = NULL;
@@ -133,7 +147,7 @@ void AudioEffectOpusChunked::resetencoder(int Dreason) {
133147
}
134148

135149
void AudioEffectOpusChunked::createencoder() {
136-
resetencoder(4); // In case called from GDScript
150+
deleteencoder();
137151
audiosamplebuffer.resize(audiosamplesize*ringbufferchunks);
138152
chunknumber = 0;
139153
bufferend = 0;
@@ -191,22 +205,25 @@ void AudioEffectOpusChunked::createencoder() {
191205
}
192206
int opuserror2 = opus_encoder_ctl(opusencoder, OPUS_SET_BITRATE(opusbitrate));
193207
if (opuserror2 != 0) {
194-
godot::UtilityFunctions::printerr("opus_encoder_ctl bitrate error error ", opuserror2);
208+
godot::UtilityFunctions::printerr("opus_encoder_ctl bitrate error ", opuserror2);
195209
chunknumber = -2;
196210
return;
197211
}
198212
int opuserror3 = opus_encoder_ctl(opusencoder, OPUS_SET_COMPLEXITY(complexity));
199213
if (opuserror3 != 0) {
200-
godot::UtilityFunctions::printerr("opus_encoder_ctl complexity error error ", opuserror3);
214+
godot::UtilityFunctions::printerr("opus_encoder_ctl complexity error ", opuserror3);
201215
chunknumber = -2;
202216
return;
203217
}
204218
int opuserror4 = opus_encoder_ctl(opusencoder, OPUS_SET_SIGNAL(signal_type));
205219
if (opuserror4 != 0) {
206-
godot::UtilityFunctions::printerr("opus_encoder_ctl signal_type error error ", opuserror4);
220+
godot::UtilityFunctions::printerr("opus_encoder_ctl signal_type error ", opuserror4);
207221
chunknumber = -2;
208222
return;
209223
}
224+
// we don't set DTX because it's for letting opus decide internally when it is quiet https://github.com/xiph/opus/issues/381
225+
//int opuserror5 = opus_encoder_ctl(opusencoder, OPUS_SET_DTX(1));
226+
210227
opusbytebuffer.resize(sizeof(float)*channels*opusframesize + MAXPREFIXBYTES);
211228
lastopuschunk = -1;
212229
}
@@ -343,28 +360,6 @@ PackedByteArray AudioEffectOpusChunked::read_opus_packet(const PackedByteArray&
343360
return opus_frame_to_opus_packet(prefixbytes, paudioresamples);
344361
}
345362

346-
void AudioEffectOpusChunked::flush_opus_encoder(bool denoise) {
347-
if ((opusframesize == 0) || (chunknumber == -1))
348-
return;
349-
// this just sends 5 empty chunks into the encoder. doesn't necessarily work
350-
float* paudioresamples = (float*)singleresamplebuffer.ptrw();
351-
for (int j = 0; j < opusframesize*2; j++)
352-
paudioresamples[j] = 0.0F;
353-
for (int i = 0; i < 5; i++)
354-
opus_frame_to_opus_packet(PackedByteArray(), paudioresamples);
355-
356-
if ((st != NULL) && denoise) {
357-
// we could use rnnoise_init (if it doesn't involve a heavy reload of the model)
358-
float* rin = (float*)rnnoise_in.ptr();
359-
for (int j = 0; j < rnnoiseframesize; j++)
360-
rin[j] = 0.0F;
361-
float* rout = (float*)rnnoise_out.ptr();
362-
for (int i = 0; i < 5; i++)
363-
rnnoise_process_frame(st, rout, rin);
364-
}
365-
366-
lastopuschunk = chunknumber - 1;
367-
}
368363

369364
int AudioEffectOpusChunked::chunk_to_lipsync(bool resampled) {
370365
#ifdef OVR_LIP_SYNC

src/audio_effect_opus_chunked.h

+3-10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "opus.h"
4444
#include "speex_resampler/speex_resampler.h"
4545

46+
4647
#ifdef OVR_LIP_SYNC
4748
#include "OVRLipSync.h"
4849
#else
@@ -92,15 +93,6 @@ typedef enum {
9293

9394
// chunk_to_opus_packet() is for encoding a series of chunks not in the ring buffer.
9495

95-
// TODO
96-
// fix any crashes
97-
// plot the float value of the noise detector in the screen as a threshold
98-
// plot the resampled denoised view in the same texture too. (aligned)
99-
// hack the main scons module so it builds on the actions
100-
// make a stub so it can run without the rnnoise library if necessary
101-
// get help with this compiling
102-
103-
// finish folding and delivering GP leaflets
10496

10597

10698
class AudioEffectOpusChunked : public AudioEffect {
@@ -159,7 +151,8 @@ class AudioEffectOpusChunked : public AudioEffect {
159151
virtual Ref<AudioEffectInstance> _instantiate() override;
160152

161153
void createencoder();
162-
void resetencoder(int Dreason=3);
154+
void deleteencoder();
155+
void resetencoder();
163156

164157
bool chunk_available();
165158
void drop_chunk();

0 commit comments

Comments
 (0)