2020-03-30 13:44:18 +02:00
|
|
|
import * as log from "tc-shared/log";
|
|
|
|
import * as aplayer from "../audio/player";
|
|
|
|
import {LogCategory} from "tc-shared/log";
|
|
|
|
import {BufferChunk, Codec, CodecClientCache} from "./Codec";
|
|
|
|
import {AudioResampler} from "../voice/AudioResampler";
|
2018-04-11 17:56:09 +02:00
|
|
|
|
2018-05-07 11:51:50 +02:00
|
|
|
class AVGCalculator {
|
|
|
|
history_size: number = 100;
|
|
|
|
history: number[] = [];
|
|
|
|
|
|
|
|
push(entry: number) {
|
|
|
|
while(this.history.length > this.history_size)
|
|
|
|
this.history.pop();
|
|
|
|
this.history.unshift(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
avg() : number {
|
|
|
|
let count = 0;
|
|
|
|
for(let entry of this.history)
|
|
|
|
count += entry;
|
|
|
|
return count / this.history.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 13:44:18 +02:00
|
|
|
export abstract class BasicCodec implements Codec {
|
2018-04-11 17:56:09 +02:00
|
|
|
protected _audioContext: OfflineAudioContext;
|
|
|
|
protected _decodeResampler: AudioResampler;
|
|
|
|
protected _encodeResampler: AudioResampler;
|
|
|
|
protected _codecSampleRate: number;
|
2018-05-07 11:51:50 +02:00
|
|
|
protected _latenz: AVGCalculator = new AVGCalculator();
|
2018-04-11 17:56:09 +02:00
|
|
|
|
|
|
|
on_encoded_data: (Uint8Array) => void = $ => {};
|
|
|
|
channelCount: number = 1;
|
|
|
|
samplesPerUnit: number = 960;
|
|
|
|
|
2018-10-28 18:25:43 +01:00
|
|
|
protected constructor(codecSampleRate: number) {
|
2018-04-11 17:56:09 +02:00
|
|
|
this.channelCount = 1;
|
|
|
|
this.samplesPerUnit = 960;
|
2020-03-30 13:44:18 +02:00
|
|
|
this._audioContext = new (window.webkitOfflineAudioContext || window.OfflineAudioContext)(aplayer.destination().channelCount, 1024, aplayer.context().sampleRate);
|
2018-04-11 17:56:09 +02:00
|
|
|
this._codecSampleRate = codecSampleRate;
|
2020-03-30 13:44:18 +02:00
|
|
|
this._decodeResampler = new AudioResampler(aplayer.context().sampleRate);
|
2018-04-11 17:56:09 +02:00
|
|
|
this._encodeResampler = new AudioResampler(codecSampleRate);
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract name() : string;
|
2018-04-18 20:12:10 +02:00
|
|
|
abstract initialise() : Promise<Boolean>;
|
|
|
|
abstract initialized() : boolean;
|
2018-04-11 17:56:09 +02:00
|
|
|
abstract deinitialise();
|
|
|
|
abstract reset() : boolean;
|
|
|
|
|
|
|
|
protected abstract decode(data: Uint8Array) : Promise<AudioBuffer>;
|
|
|
|
protected abstract encode(data: AudioBuffer) : Promise<Uint8Array | string>;
|
|
|
|
|
|
|
|
|
|
|
|
encodeSamples(cache: CodecClientCache, pcm: AudioBuffer) {
|
2019-08-30 23:06:39 +02:00
|
|
|
this._encodeResampler.resample(pcm).catch(error => log.error(LogCategory.VOICE, tr("Could not resample PCM data for codec. Error: %o"), error))
|
2018-12-05 20:46:33 +01:00
|
|
|
.then(buffer => this.encodeSamples0(cache, buffer as any)).catch(error => console.error(tr("Could not encode PCM data for codec. Error: %o"), error))
|
2018-04-11 17:56:09 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private encodeSamples0(cache: CodecClientCache, buffer: AudioBuffer) {
|
|
|
|
cache._chunks.push(new BufferChunk(buffer)); //TODO multi channel!
|
|
|
|
|
|
|
|
while(cache.bufferedSamples(this.samplesPerUnit) >= this.samplesPerUnit) {
|
|
|
|
let buffer = this._audioContext.createBuffer(this.channelCount, this.samplesPerUnit, this._codecSampleRate);
|
|
|
|
let index = 0;
|
|
|
|
while(index < this.samplesPerUnit) {
|
|
|
|
let buf = cache._chunks[0];
|
|
|
|
let cpyBytes = buf.copyRangeTo(buffer, this.samplesPerUnit - index, index);
|
|
|
|
index += cpyBytes;
|
|
|
|
buf.index += cpyBytes;
|
|
|
|
if(buf.index == buf.buffer.length)
|
|
|
|
cache._chunks.pop_front();
|
|
|
|
}
|
|
|
|
|
2018-05-07 11:51:50 +02:00
|
|
|
let encodeBegin = Date.now();
|
2018-04-11 17:56:09 +02:00
|
|
|
this.encode(buffer).then(result => {
|
2018-04-18 16:25:10 +02:00
|
|
|
if(result instanceof Uint8Array) {
|
2018-05-07 11:51:50 +02:00
|
|
|
let time = Date.now() - encodeBegin;
|
|
|
|
if(time > 20)
|
2019-08-30 23:06:39 +02:00
|
|
|
log.warn(LogCategory.VOICE, tr("Voice buffer stalled in WorkerPipe longer then expected: %d"), time);
|
2018-06-19 20:31:05 +02:00
|
|
|
//if(time > 20)
|
|
|
|
// chat.serverChat().appendMessage("Required decode time: " + time);
|
2018-04-18 16:25:10 +02:00
|
|
|
this.on_encoded_data(result);
|
|
|
|
}
|
2019-08-30 23:06:39 +02:00
|
|
|
else log.error(LogCategory.VOICE, "[Codec][" + this.name() + "] Could not encode buffer. Result: " + result); //TODO tr
|
2018-04-11 17:56:09 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
decodeSamples(cache: CodecClientCache, data: Uint8Array) : Promise<AudioBuffer> {
|
|
|
|
return this.decode(data).then(buffer => this._decodeResampler.resample(buffer));
|
|
|
|
}
|
|
|
|
}
|