2018-03-07 18:06:52 +00:00
|
|
|
class BufferChunk {
|
|
|
|
buffer: AudioBuffer;
|
2018-03-02 19:39:46 +00:00
|
|
|
index: number;
|
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
constructor(buffer: AudioBuffer) {
|
2018-03-02 19:39:46 +00:00
|
|
|
this.buffer = buffer;
|
|
|
|
this.index = 0;
|
|
|
|
}
|
2018-03-07 18:06:52 +00:00
|
|
|
|
|
|
|
copyRangeTo(target: AudioBuffer, maxLength: number, offset: number) {
|
|
|
|
let copy = Math.min(this.buffer.length - this.index, maxLength);
|
|
|
|
for(let channel = 0; channel < this.buffer.numberOfChannels; channel++) {
|
|
|
|
target.getChannelData(channel).set(
|
|
|
|
this.buffer.getChannelData(channel).subarray(this.index, this.index + copy),
|
|
|
|
offset
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return copy;
|
|
|
|
}
|
2018-03-02 19:39:46 +00:00
|
|
|
}
|
|
|
|
|
2018-02-27 16:20:49 +00:00
|
|
|
abstract class Codec {
|
2018-03-02 19:39:46 +00:00
|
|
|
on_encoded_data: (Uint8Array) => void = ($) => {};
|
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
protected _decodeResampler: AudioResampler;
|
|
|
|
protected _encodeResampler: AudioResampler;
|
2018-03-02 22:39:12 +00:00
|
|
|
protected _codecSampleRate: number;
|
2018-03-07 18:06:52 +00:00
|
|
|
protected _chunks: BufferChunk[] = [];
|
2018-03-02 19:39:46 +00:00
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
channelCount: number = 1;
|
2018-03-02 22:39:12 +00:00
|
|
|
samplesPerUnit: number = 960;
|
|
|
|
|
|
|
|
protected constructor(codecSampleRate: number){
|
|
|
|
this._codecSampleRate = codecSampleRate;
|
2018-03-07 18:06:52 +00:00
|
|
|
this._decodeResampler = new AudioResampler();
|
|
|
|
this._encodeResampler = new AudioResampler(codecSampleRate);
|
2018-03-02 22:39:12 +00:00
|
|
|
}
|
2018-02-27 16:20:49 +00:00
|
|
|
|
|
|
|
abstract name() : string;
|
|
|
|
abstract initialise();
|
|
|
|
abstract deinitialise();
|
|
|
|
|
2018-03-02 22:39:12 +00:00
|
|
|
protected abstract decode(data: Uint8Array) : Promise<AudioBuffer>;
|
2018-03-07 18:06:52 +00:00
|
|
|
protected abstract encode(data: AudioBuffer) : Uint8Array | string;
|
2018-03-02 19:39:46 +00:00
|
|
|
|
2018-03-02 22:39:12 +00:00
|
|
|
|
2018-03-02 19:39:46 +00:00
|
|
|
protected bufferedSamples(max: number = 0) : number {
|
|
|
|
let value = 0;
|
2018-03-07 18:06:52 +00:00
|
|
|
for(let i = 0; i < this._chunks.length && value < max; i++)
|
|
|
|
value += this._chunks[i].buffer.length - this._chunks[i].index;
|
2018-03-02 19:39:46 +00:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
async encodeSamples(pcm: AudioBuffer) {
|
2018-03-02 22:39:12 +00:00
|
|
|
this._encodeResampler.resample(pcm).then(buffer => this.encodeSamples0(buffer))
|
|
|
|
.catch(error => console.error("Could not resample PCM data for codec. Error:" + error));
|
2018-03-07 18:06:52 +00:00
|
|
|
|
2018-03-02 22:39:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private encodeSamples0(buffer: AudioBuffer) {
|
2018-03-07 18:06:52 +00:00
|
|
|
this._chunks.push(new BufferChunk(buffer)); //TODO multi channel!
|
2018-03-02 19:39:46 +00:00
|
|
|
|
2018-03-02 22:39:12 +00:00
|
|
|
while(this.bufferedSamples(this.samplesPerUnit) >= this.samplesPerUnit) {
|
2018-03-07 18:06:52 +00:00
|
|
|
let buffer = AudioController.globalContext.createBuffer(this.channelCount, this.samplesPerUnit, this._codecSampleRate);
|
2018-03-02 19:39:46 +00:00
|
|
|
let index = 0;
|
2018-03-02 22:39:12 +00:00
|
|
|
while(index < this.samplesPerUnit) {
|
2018-03-07 18:06:52 +00:00
|
|
|
let buf = this._chunks[0];
|
|
|
|
let cpyBytes = buf.copyRangeTo(buffer, this.samplesPerUnit - index, index);
|
|
|
|
index += cpyBytes;
|
|
|
|
buf.index += cpyBytes;
|
2018-03-02 19:39:46 +00:00
|
|
|
if(buf.index == buf.buffer.length)
|
2018-03-07 18:06:52 +00:00
|
|
|
this._chunks.pop_front();
|
2018-03-02 19:39:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let result = this.encode(buffer);
|
|
|
|
if(result instanceof Uint8Array) this.on_encoded_data(result);
|
2018-03-02 22:39:12 +00:00
|
|
|
else console.error("[Codec][" + this.name() + "] Could not encode buffer. Result: " + result);
|
2018-03-02 19:39:46 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2018-03-02 22:39:12 +00:00
|
|
|
|
|
|
|
decodeSamples(data: Uint8Array) : Promise<AudioBuffer> {
|
|
|
|
return this.decode(data).then(buffer => this._decodeResampler.resample(buffer));
|
|
|
|
}
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
class RawCodec extends Codec {
|
|
|
|
converterRaw: any;
|
|
|
|
converter: Uint8Array;
|
|
|
|
bufferSize: number = 4096 * 4;
|
|
|
|
|
|
|
|
constructor(codecSampleRate: number){
|
|
|
|
super(codecSampleRate);
|
|
|
|
}
|
|
|
|
|
|
|
|
name(): string {
|
|
|
|
return "raw";
|
|
|
|
}
|
|
|
|
|
|
|
|
initialise() {
|
|
|
|
this.converterRaw = Module._malloc(this.bufferSize);
|
|
|
|
this.converter = new Uint8Array(Module.HEAPU8.buffer, this.converterRaw, this.bufferSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
deinitialise() { }
|
|
|
|
|
|
|
|
protected decode(data: Uint8Array): Promise<AudioBuffer> {
|
|
|
|
return new Promise<AudioBuffer>((resolve, reject) => {
|
|
|
|
this.converter.set(data);
|
|
|
|
let buf = Module.HEAPF32.slice(this.converter.byteOffset / 4, (this.converter.byteOffset / 4) + data.length / 4);
|
|
|
|
let audioBuf = AudioController.globalContext.createBuffer(1, data.length / 4, this._codecSampleRate);
|
|
|
|
audioBuf.copyToChannel(buf, 0);
|
|
|
|
resolve(audioBuf);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
protected encode(data: AudioBuffer): Uint8Array | string {
|
|
|
|
return new Uint8Array(data.getChannelData(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
enum OpusType {
|
|
|
|
VOIP = 2048,
|
|
|
|
AUDIO = 2049,
|
|
|
|
RESTRICTED_LOWDELAY = 2051
|
|
|
|
}
|
|
|
|
|
2018-02-27 16:20:49 +00:00
|
|
|
class OpusCodec extends Codec {
|
|
|
|
private nativeHandle: any;
|
2018-03-07 18:06:52 +00:00
|
|
|
private type: OpusType;
|
2018-02-27 16:20:49 +00:00
|
|
|
|
|
|
|
private fn_newHandle: any;
|
|
|
|
private fn_decode: any;
|
2018-02-28 19:49:56 +00:00
|
|
|
private fn_encode: any;
|
2018-02-27 16:20:49 +00:00
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
private bufferSize = 4096 * 2;
|
|
|
|
private encodeBufferRaw: any;
|
|
|
|
private encodeBuffer: Float32Array;
|
|
|
|
private decodeBufferRaw: any;
|
|
|
|
private decodeBuffer: Uint8Array;
|
|
|
|
|
|
|
|
constructor(channelCount: number, type: OpusType) {
|
2018-03-02 22:39:12 +00:00
|
|
|
super(48000);
|
2018-03-07 18:06:52 +00:00
|
|
|
super.channelCount = channelCount;
|
|
|
|
this.type = type;
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
name(): string {
|
2018-03-07 18:06:52 +00:00
|
|
|
return "Opus (Type: " + OpusCodec[this.type] + " Channels: " + this.channelCount + ")";
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
initialise() {
|
2018-03-07 18:06:52 +00:00
|
|
|
this.fn_newHandle = Module.cwrap("codec_opus_createNativeHandle", "pointer", ["number", "number"]);
|
2018-02-27 16:20:49 +00:00
|
|
|
this.fn_decode = Module.cwrap("codec_opus_decode", "number", ["pointer", "pointer", "number", "number"]); /* codec_opus_decode(handle, buffer, length, maxlength) */
|
2018-02-28 19:49:56 +00:00
|
|
|
this.fn_encode = Module.cwrap("codec_opus_encode", "number", ["pointer", "pointer", "number", "number"]);
|
2018-02-27 16:20:49 +00:00
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
this.nativeHandle = this.fn_newHandle(this.channelCount, this.type);
|
|
|
|
|
|
|
|
this.encodeBufferRaw = Module._malloc(this.bufferSize);
|
|
|
|
this.encodeBuffer = new Float32Array(Module.HEAPF32.buffer, this.encodeBufferRaw, this.bufferSize / 4);
|
|
|
|
|
|
|
|
this.decodeBufferRaw = Module._malloc(this.bufferSize);
|
|
|
|
this.decodeBuffer = new Uint8Array(Module.HEAPU8.buffer, this.decodeBufferRaw, this.bufferSize);
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
2018-03-02 22:39:12 +00:00
|
|
|
deinitialise() { } //TODO
|
|
|
|
|
|
|
|
decode(data: Uint8Array): Promise<AudioBuffer> {
|
|
|
|
return new Promise<AudioBuffer>((resolve, reject) => {
|
2018-03-07 18:06:52 +00:00
|
|
|
if(data.byteLength > this.decodeBuffer.byteLength) throw "Data to long!";
|
|
|
|
this.decodeBuffer.set(data);
|
|
|
|
//console.log("decode(" + data.length + ")");
|
|
|
|
//console.log(data);
|
|
|
|
let result = this.fn_decode(this.nativeHandle, this.decodeBuffer.byteOffset, data.byteLength, this.decodeBuffer.byteLength);
|
2018-03-02 22:39:12 +00:00
|
|
|
if(result < 0) {
|
|
|
|
reject("invalid result on decode (" + result + ")");
|
|
|
|
return;
|
|
|
|
}
|
2018-03-07 18:06:52 +00:00
|
|
|
//console.log("decoded: " + result);
|
|
|
|
let buf = Module.HEAPF32.slice(this.decodeBuffer.byteOffset / 4, (this.decodeBuffer.byteOffset / 4) + (result * this.channelCount));
|
2018-03-02 22:39:12 +00:00
|
|
|
let audioBuf = AudioController.globalContext.createBuffer(this.channelCount, result, this._codecSampleRate);
|
2018-03-07 18:06:52 +00:00
|
|
|
|
|
|
|
for(let offset = 0; offset < result; offset++) {
|
|
|
|
for(let channel = 0; channel < this.channelCount; channel++)
|
|
|
|
audioBuf.getChannelData(channel)[offset] = buf[offset * this.channelCount + this.channelCount];
|
|
|
|
}
|
|
|
|
|
2018-03-02 22:39:12 +00:00
|
|
|
resolve(audioBuf);
|
|
|
|
});
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
2018-03-07 18:06:52 +00:00
|
|
|
encode(data: AudioBuffer): Uint8Array | string {
|
|
|
|
if(data.length * this.channelCount > this.encodeBuffer.length) throw "Data to long!";
|
|
|
|
|
|
|
|
for(let offset = 0; offset < data.length; offset++) {
|
|
|
|
for(let channel = 0; channel < this.channelCount; channel++)
|
|
|
|
this.encodeBuffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset];
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = this.fn_encode(this.nativeHandle, this.encodeBuffer.byteOffset, data.length, this.encodeBuffer.byteLength);
|
2018-02-28 19:49:56 +00:00
|
|
|
if(result < 0) {
|
|
|
|
return "invalid result on encode (" + result + ")";
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
2018-03-07 18:06:52 +00:00
|
|
|
let buf = Module.HEAP8.slice(this.encodeBuffer.byteOffset , this.encodeBuffer.byteOffset + result);
|
2018-02-28 19:49:56 +00:00
|
|
|
return Uint8Array.from(buf);
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|