Compare commits

...

3 Commits

Author SHA1 Message Date
Jakob Ketterl 58c7f8a132 reset the decoder if it errors 2022-06-18 23:52:08 +02:00
Jakob Ketterl a779d5d02a use webcodecs api (breaking other things) 2022-06-18 23:24:40 +02:00
Jakob Ketterl a6f2b6b31a add opus server-side integration 2022-06-18 20:23:50 +02:00
4 changed files with 72 additions and 17 deletions

View File

@ -1,5 +1,5 @@
from csdr.chain import Chain from csdr.chain import Chain
from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, OpusEncoder, Limit
from pycsdr.types import Format from pycsdr.types import Format
@ -27,10 +27,12 @@ class ClientAudioChain(Chain):
workers += [converter] workers += [converter]
if compression == "adpcm": if compression == "adpcm":
workers += [AdpcmEncoder(sync=True)] workers += [AdpcmEncoder(sync=True)]
elif compression == "opus":
workers += [OpusEncoder()]
super().__init__(workers) super().__init__(workers)
def _buildConverter(self): def _buildConverter(self):
return Converter(self.format, self.inputRate, self.clientRate) return Converter(self.format, self.inputRate, 12000)
def _updateConverter(self): def _updateConverter(self):
converter = self._buildConverter() converter = self._buildConverter()
@ -63,10 +65,18 @@ class ClientAudioChain(Chain):
self._updateConverter() self._updateConverter()
def setAudioCompression(self, compression: str) -> None: def setAudioCompression(self, compression: str) -> None:
index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder)) index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder) or isinstance(x, OpusEncoder))
newEncoder = None
if compression == "adpcm": if compression == "adpcm":
newEncoder = AdpcmEncoder(sync=True)
elif compression == "opus":
newEncoder = OpusEncoder()
if newEncoder:
if index < 0: if index < 0:
self.append(AdpcmEncoder(sync=True)) self.append(newEncoder)
else:
self.replace(index, newEncoder)
else: else:
if index >= 0: if index >= 0:
self.remove(index) self.remove(index)

View File

@ -21,7 +21,15 @@ function AudioEngine(maxBufferLength, audioReporter) {
me._start(); me._start();
} }
this.audioCodec = new ImaAdpcmCodec(); var onAudio = function(audioData) {
me.playbackAudio(audioData);
}
this.audioCodecs = {
"adpcm": new ImaAdpcmCodec(onAudio),
"opus": new OpusCodec(onAudio)
};
this.compression = 'none'; this.compression = 'none';
this.setupResampling(); this.setupResampling();
@ -279,24 +287,26 @@ AudioEngine.prototype.getSampleRate = function() {
AudioEngine.prototype.processAudio = function(data, resampler) { AudioEngine.prototype.processAudio = function(data, resampler) {
if (!this.audioNode) return; if (!this.audioNode) return;
this.audioBytes.add(data.byteLength); this.audioBytes.add(data.byteLength);
var buffer;
if (this.compression === "adpcm") { if (this.compression !== "none") {
//resampling & ADPCM this.audioCodecs[this.compression].decodeAsync(new Uint8Array(data));
buffer = this.audioCodec.decodeWithSync(new Uint8Array(data));
} else { } else {
buffer = new Int16Array(data); this.playbackAudio(new Int16Array(data));
} }
buffer = resampler.process(buffer); }
AudioEngine.prototype.playbackAudio = function(audioData) {
//var buffer = this.resampler.process(audioData);
if (this.audioNode.port) { if (this.audioNode.port) {
// AudioWorklets supported // AudioWorklets supported
this.audioNode.port.postMessage(buffer); this.audioNode.port.postMessage(audioData);
} else { } else {
// silently drop excess samples // silently drop excess samples
if (this.getBuffersize() + buffer.length <= this.maxBufferSize) { if (this.getBuffersize() + buffer.length <= this.maxBufferSize) {
this.audioBuffers.push(buffer); this.audioBuffers.push(buffer);
} }
} }
} };
AudioEngine.prototype.pushAudio = function(data) { AudioEngine.prototype.pushAudio = function(data) {
this.processAudio(data, this.resampler); this.processAudio(data, this.resampler);
@ -320,8 +330,9 @@ AudioEngine.prototype.getBuffersize = function() {
return this.audioBuffers.map(function(b){ return b.length; }).reduce(function(a, b){ return a + b; }, 0); return this.audioBuffers.map(function(b){ return b.length; }).reduce(function(a, b){ return a + b; }, 0);
}; };
function ImaAdpcmCodec() { function ImaAdpcmCodec(callback) {
this.reset(); this.reset();
this.callback = callback;
} }
ImaAdpcmCodec.prototype.reset = function() { ImaAdpcmCodec.prototype.reset = function() {
@ -357,7 +368,7 @@ ImaAdpcmCodec.prototype.decode = function(data) {
return output; return output;
}; };
ImaAdpcmCodec.prototype.decodeWithSync = function(data) { ImaAdpcmCodec.prototype.decodeAsync = function(data, callback) {
var output = new Int16Array(data.length * 2); var output = new Int16Array(data.length * 2);
var index = this.skip; var index = this.skip;
var oi = 0; var oi = 0;
@ -391,7 +402,7 @@ ImaAdpcmCodec.prototype.decodeWithSync = function(data) {
} }
} }
this.skip = index - data.length; this.skip = index - data.length;
return output.slice(0, oi); this.callback(output.slice(0, oi));
}; };
ImaAdpcmCodec.prototype.decodeNibble = function(nibble) { ImaAdpcmCodec.prototype.decodeNibble = function(nibble) {
@ -412,6 +423,39 @@ ImaAdpcmCodec.prototype.decodeNibble = function(nibble) {
return this.predictor; return this.predictor;
}; };
function OpusCodec(callback) {
this.callback = callback;
this.resetDecoder();
}
OpusCodec.prototype.resetDecoder = function() {
var me = this;
me.decoder = new AudioDecoder({
output: function(audioData) {
var buffer = new Float32Array(audioData.numberOfFrames * audioData.numberOfChannels);
audioData.copyTo(buffer, {planeIndex: 0});
me.callback(buffer);
},
error: function(e) {
console.error(e);
me.resetDecoder();
}
});
me.decoder.configure({
codec: "opus",
sampleRate: 12000,
numberOfChannels: 1
});
}
OpusCodec.prototype.decodeAsync = function(data) {
this.decoder.decode(new EncodedAudioChunk({
type: "key",
data: data,
timestamp: 0
}));
};
function Interpolator(factor) { function Interpolator(factor) {
this.factor = factor; this.factor = factor;
this.lowpass = new Lowpass(factor) this.lowpass = new Lowpass(factor)

View File

@ -754,7 +754,7 @@ function on_ws_recv(evt) {
if ('audio_compression' in config) { if ('audio_compression' in config) {
var audio_compression = config['audio_compression']; var audio_compression = config['audio_compression'];
audioEngine.setCompression(audio_compression); audioEngine.setCompression(audio_compression);
divlog("Audio stream is " + ((audio_compression === "adpcm") ? "compressed" : "uncompressed") + "."); divlog("Audio stream is " + ((audio_compression !== "none") ? "compressed" : "uncompressed") + ".");
} }
if ('fft_compression' in config) { if ('fft_compression' in config) {
fft_compression = config['fft_compression']; fft_compression = config['fft_compression'];

View File

@ -132,6 +132,7 @@ class GeneralSettingsController(SettingsFormController):
"Audio compression", "Audio compression",
options=[ options=[
Option("adpcm", "ADPCM"), Option("adpcm", "ADPCM"),
Option("opus", "OPUS"),
Option("none", "None"), Option("none", "None"),
], ],
), ),