Improved audio de/encoding

canary
WolverinDEV 2020-06-12 18:27:33 +02:00
parent cb616b55dc
commit 99b75f34d6
7 changed files with 496 additions and 197 deletions

View File

@ -1,6 +1,7 @@
# Changelog:
* **12.06.20**
- Added a copy/paste menu for all HTML input elements
- Heavily improved web client audio de/encoding
* **11.06.20**
- Fixed channel tree deletions

View File

@ -0,0 +1,92 @@
import {CodecType} from "tc-backend/web/codec/Codec";
export type CWMessageResponse = {
type: "success";
token: string;
response: any;
timestampReceived: number;
timestampSend: number;
};
export type CWMessageErrorResponse = {
type: "error";
token: string;
error: string;
timestampReceived: number;
timestampSend: number;
}
export type CWMessageCommand<T = CWCommand | CWCommandResponse> = {
type: "command";
token: string;
command: keyof T;
payload: any;
}
export type CWMessageNotify = {
type: "notify";
}
export type CWMessage = CWMessageCommand | CWMessageErrorResponse | CWMessageResponse | CWMessageNotify;
/* from handle to worker */
export interface CWCommand {
"global-initialize": {},
"initialise": {
type: CodecType,
channelCount: number
},
"reset": {}
"finalize": {},
"decode-payload": {
buffer: ArrayBuffer;
byteLength: number;
byteOffset: number;
maxByteLength: number;
},
"encode-payload": {
buffer: ArrayBuffer;
byteLength: number;
byteOffset: number;
maxByteLength: number;
},
}
/* from worker to handle */
export interface CWCommandResponse {
"decode-payload-result": {
buffer: ArrayBuffer;
byteLength: number;
byteOffset: number;
},
"encode-payload-result": {
buffer: ArrayBuffer;
byteLength: number;
byteOffset: number;
}
}
export interface CWMessageRelations {
"decode-payload": "decode-payload-result",
"decode-payload-result": never,
"encode-payload": "encode-payload-result",
"encode-payload-result": never,
"global-initialize": void,
"initialise": void,
"reset": void,
"finalize": void
}
export type CWCommandResponseType<T extends keyof CWCommand | keyof CWCommandResponse> = CWMessageRelations[T] extends string ? CWCommandResponse[CWMessageRelations[T]] : CWMessageRelations[T];

View File

@ -2,18 +2,52 @@ import {BasicCodec} from "./BasicCodec";
import {CodecType} from "./Codec";
import * as log from "tc-shared/log";
import {LogCategory} from "tc-shared/log";
import {
CWCommand,
CWCommandResponseType,
CWMessage, CWMessageCommand,
CWMessageErrorResponse,
CWMessageResponse
} from "tc-backend/web/codec/CodecWorkerMessages";
interface ExecuteResult {
result?: any;
error?: string;
type MessageTimings = {
upstream: number;
downstream: number;
handle: number;
};
interface ExecuteResultBase {
success: boolean;
timings: {
upstream: number;
downstream: number;
handle: number;
timings: MessageTimings
}
interface SuccessExecuteResult<T> extends ExecuteResultBase {
success: true;
result: T;
}
interface ErrorExecuteResult extends ExecuteResultBase {
success: false;
error: string;
}
type ExecuteResult<T = any> = SuccessExecuteResult<T> | ErrorExecuteResult;
const cachedBufferSize = 1024 * 8;
let cachedBuffers: ArrayBuffer[] = [];
function nextCachedBuffer() : ArrayBuffer {
if(cachedBuffers.length === 0) {
return new ArrayBuffer(cachedBufferSize);
}
return cachedBuffers.pop();
}
function freeCachedBuffer(buffer: ArrayBuffer) {
if(cachedBuffers.length > 32)
return;
else if(buffer.byteLength < cachedBufferSize)
return;
cachedBuffers.push(buffer);
}
export class CodecWrapperWorker extends BasicCodec {
@ -27,10 +61,8 @@ export class CodecWrapperWorker extends BasicCodec {
private pending_executes: {[key: string]: {
timeout?: any;
timestamp_send: number,
timestampSend: number,
resolve: (_: ExecuteResult) => void;
reject: (_: any) => void;
}} = {};
constructor(type: CodecType) {
@ -61,7 +93,7 @@ export class CodecWrapperWorker extends BasicCodec {
type: this.type,
channelCount: this.channelCount,
})).then(result => {
if(result.success) {
if(result.success === true) {
this._initialized = true;
return Promise.resolve(true);
}
@ -78,7 +110,7 @@ export class CodecWrapperWorker extends BasicCodec {
}
deinitialise() {
this.execute("deinitialise", {});
this.execute("finalize", {});
this._initialized = false;
this._initialize_promise = undefined;
}
@ -86,44 +118,52 @@ export class CodecWrapperWorker extends BasicCodec {
async decode(data: Uint8Array): Promise<AudioBuffer> {
if(!this.initialized()) throw "codec not initialized/initialize failed";
const result = await this.execute("decodeSamples", { data: data, length: data.length });
const cachedBuffer = nextCachedBuffer();
new Uint8Array(cachedBuffer).set(data);
const result = await this.execute("decode-payload", {
byteLength: data.byteLength,
buffer: cachedBuffer,
byteOffset: 0,
maxByteLength: cachedBuffer.byteLength
}, 5000, [ cachedBuffer ]);
if(result.timings.downstream > 5 || result.timings.upstream > 5 || result.timings.handle > 5)
log.warn(LogCategory.VOICE, tr("Worker message stock time: {downstream: %dms, handle: %dms, upstream: %dms}"), result.timings.downstream, result.timings.handle, result.timings.upstream);
if(!result.success) throw result.error || tr("unknown decode error");
if(result.success === false)
throw result.error;
let array = new Float32Array(result.result.length);
for(let index = 0; index < array.length; index++)
array[index] = result.result.data[index];
const chunkLength = result.result.byteLength / this.channelCount;
const audioBuffer = this._audioContext.createBuffer(this.channelCount, chunkLength / 4, this._codecSampleRate);
let audioBuf = this._audioContext.createBuffer(this.channelCount, array.length / this.channelCount, this._codecSampleRate);
for (let channel = 0; channel < this.channelCount; channel++) {
for (let offset = 0; offset < audioBuf.length; offset++) {
audioBuf.getChannelData(channel)[offset] = array[channel + offset * this.channelCount];
}
for(let channel = 0; channel < this.channelCount; channel++) {
const buffer = new Float32Array(result.result.buffer, result.result.byteOffset + chunkLength * channel, chunkLength / 4);
audioBuffer.copyToChannel(buffer, channel, 0);
}
return audioBuf;
freeCachedBuffer(result.result.buffer);
return audioBuffer;
}
async encode(data: AudioBuffer) : Promise<Uint8Array> {
if(!this.initialized()) throw "codec not initialized/initialize failed";
let buffer = new Float32Array(this.channelCount * data.length);
for (let offset = 0; offset < data.length; offset++) {
for (let channel = 0; channel < this.channelCount; channel++)
buffer[offset * this.channelCount + channel] = data.getChannelData(channel)[offset];
}
const buffer = nextCachedBuffer();
const f32Buffer = new Float32Array(buffer);
for(let channel = 0; channel < this.channelCount; channel++)
data.copyFromChannel(f32Buffer, channel, data.length * channel);
const result = await this.execute("encode-payload", { byteLength: data.length * this.channelCount * 4, buffer: buffer, byteOffset: 0, maxByteLength: buffer.byteLength });
const result = await this.execute("encodeSamples", { data: buffer, length: buffer.length });
if(result.timings.downstream > 5 || result.timings.upstream > 5)
log.warn(LogCategory.VOICE, tr("Worker message stock time: {downstream: %dms, handle: %dms, upstream: %dms}"), result.timings.downstream, result.timings.handle, result.timings.upstream);
if(!result.success) throw result.error || tr("unknown encode error");
let array = new Uint8Array(result.result.length);
for(let index = 0; index < array.length; index++)
array[index] = result.result.data[index];
return array;
if(result.success === false)
throw result.error;
const encodedResult = new Uint8Array(result.result.buffer, result.result.byteOffset, result.result.byteLength).slice(0);
freeCachedBuffer(result.result.buffer);
return encodedResult;
}
reset() : boolean {
@ -132,85 +172,118 @@ export class CodecWrapperWorker extends BasicCodec {
return true;
}
private handle_worker_message(message: any) {
if(!message["token"]) {
log.error(LogCategory.VOICE, tr("Invalid worker token!"));
private handleWorkerMessage(message: CWMessage) {
if(message.type === "notify") {
log.warn(LogCategory.VOICE, tr("Received unknown notify from worker."));
return;
}
if(message["token"] === "notify") {
/* currently not really used */
if(message["type"] == "chatmessage_server") {
//FIXME?
} else if(message.type === "error") {
const request = this.pending_executes[message.token];
if(typeof request !== "object") {
log.warn(LogCategory.VOICE, tr("Received worker execute error for unknown token (%s)"), message.token);
return;
}
log.debug(LogCategory.VOICE, tr("Costume callback! (%o)"), message);
return;
}
delete this.pending_executes[message.token];
clearTimeout(request.timeout);
const request = this.pending_executes[message["token"]];
if(typeof request !== "object") {
log.error(LogCategory.VOICE, tr("Received worker execute result for unknown token (%s)"), message["token"]);
return;
}
delete this.pending_executes[message["token"]];
const result: ExecuteResult = {
success: message["success"],
error: message["error"],
result: message["result"],
timings: {
downstream: message["timestamp_received"] - request.timestamp_send,
handle: message["timestamp_send"] - message["timestamp_received"],
upstream: Date.now() - message["timestamp_send"]
const eresponse = message as CWMessageErrorResponse;
request.resolve({
success: false,
timings: {
downstream: eresponse.timestampReceived - request.timestampSend,
handle: eresponse.timestampSend - eresponse.timestampReceived,
upstream: Date.now() - eresponse.timestampSend
},
error: eresponse.error
});
} else if(message.type === "success") {
const request = this.pending_executes[message.token];
if(typeof request !== "object") {
log.warn(LogCategory.VOICE, tr("Received worker execute result for unknown token (%s)"), message.token);
return;
}
};
clearTimeout(request.timeout);
request.resolve(result);
delete this.pending_executes[message.token];
clearTimeout(request.timeout);
const response = message as CWMessageResponse;
request.resolve({
success: true,
timings: {
downstream: response.timestampReceived - request.timestampSend,
handle: response.timestampSend - response.timestampReceived,
upstream: Date.now() - response.timestampSend
},
result: response.response
});
} else if(message.type === "command") {
log.warn(LogCategory.VOICE, tr("Received command %s from voice worker. This should never happen!"), (message as CWMessageCommand).command);
return;
} else {
log.warn(LogCategory.VOICE, tr("Received unknown message of type %s from voice worker. This should never happen!"), (message as any).type);
return;
}
}
private handle_worker_error(error: any) {
private handleWorkerError() {
log.error(LogCategory.VOICE, tr("Received error from codec worker. Closing worker."));
for(const token of Object.keys(this.pending_executes)) {
this.pending_executes[token].reject(error);
this.pending_executes[token].resolve({
success: false,
error: tr("worker terminated with an error"),
timings: { downstream: 0, handle: 0, upstream: 0}
});
delete this.pending_executes[token];
}
this._worker = undefined;
}
private execute(command: string, data: any, timeout?: number) : Promise<ExecuteResult> {
return new Promise<any>((resolve, reject) => {
private execute<T extends keyof CWCommand>(command: T, data: CWCommand[T], timeout?: number, transfer?: Transferable[]) : Promise<ExecuteResult<CWCommandResponseType<T>>> {
return new Promise<ExecuteResult>(resolve => {
if(!this._worker) {
reject(tr("worker does not exists"));
resolve({
success: false,
error: tr("worker does not exists"),
timings: {
downstream: 0,
handle: 0,
upstream: 0
}
});
return;
}
const token = this._token_index++ + "_token";
const payload = {
token: token,
command: command,
data: data,
};
this.pending_executes[token] = {
timeout: typeof timeout === "number" ? setTimeout(() => reject(tr("timeout for command ") + command), timeout) : undefined,
timeout: typeof timeout === "number" ? setTimeout(() => {
delete this.pending_executes[token];
resolve({
success: false,
error: tr("command timed out"),
timings: { upstream: 0, handle: 0, downstream: 0 }
})
}, timeout) : undefined,
resolve: resolve,
reject: reject,
timestamp_send: Date.now()
timestampSend: Date.now()
};
this._worker.postMessage(payload);
this._worker.postMessage({
command: command,
type: "command",
payload: data,
token: token
} as CWMessageCommand, transfer);
});
}
private async spawn_worker() : Promise<void> {
this._worker = new Worker("tc-backend/web/workers/codec", { type: "module" });
this._worker.onmessage = event => this.handle_worker_message(event.data);
this._worker.onerror = event => this.handle_worker_error(event.error);
this._worker.onmessage = event => this.handleWorkerMessage(event.data);
this._worker.onerror = () => this.handleWorkerError();
const result = await this.execute("global-initialize", {}, 15000);
if(!result.success) throw result.error;
if(result.success === false)
throw result.error;
}
}

View File

@ -1,4 +1,11 @@
import {CodecType} from "tc-backend/web/codec/Codec";
import {
CWMessageCommand,
CWCommand,
CWMessage,
CWMessageResponse,
CWMessageErrorResponse, CWCommandResponseType
} from "tc-backend/web/codec/CodecWorkerMessages";
const prefix = "[CodecWorker] ";
@ -6,8 +13,8 @@ export interface CodecWorker {
name();
initialise?() : string;
deinitialise();
decode(data: Uint8Array);
encode(data: Float32Array) : Uint8Array | string;
decode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array) : number | string;
encode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array) : number | string;
reset();
}
@ -25,110 +32,152 @@ export function set_initialize_callback(callback: () => Promise<true | string>)
export let codec_instance: CodecWorker;
let globally_initialized = false;
let global_initialize_result;
let commandTransferableResponse: Transferable[];
/**
* @param command
* @param data
* @return string on error or object on success
*/
async function handle_message(command: string, data: any) : Promise<string | object> {
switch (command) {
case "global-initialize":
try {
const init_result = globally_initialized ? global_initialize_result : await initialize_callback();
globally_initialized = true;
let messageHandlers: { [T in keyof CWCommand]: (message: CWCommand[T]) => Promise<CWCommandResponseType<T>> } = {} as any;
if(typeof init_result === "string")
throw init_result;
} catch (e) {
if(typeof e === "string")
return e;
throw e;
}
return {};
case "initialise":
console.log(prefix + "Initialize for codec %s", CodecType[data.type as CodecType]);
if(!supported_types[data.type])
return "type unsupported";
try {
codec_instance = await supported_types[data.type](data.options);
} catch(ex) {
console.error(prefix + "Failed to allocate codec: %o", ex);
return typeof ex === "string" ? ex : "failed to allocate codec";
}
const error = codec_instance.initialise();
if(error) return error;
return {};
case "encodeSamples":
if(!codec_instance)
return "codec not initialized/initialize failed";
let encodeArray = new Float32Array(data.length);
for(let index = 0; index < encodeArray.length; index++)
encodeArray[index] = data.data[index];
let encodeResult = codec_instance.encode(encodeArray);
if(typeof encodeResult === "string")
return encodeResult;
else
return { data: encodeResult, length: encodeResult.length };
case "decodeSamples":
if(!codec_instance)
return "codec not initialized/initialize failed";
let decodeArray = new Uint8Array(data.length);
for(let index = 0; index < decodeArray.length; index++)
decodeArray[index] = data.data[index];
let decodeResult = codec_instance.decode(decodeArray);
if(typeof decodeResult === "string")
return decodeResult;
else
return { data: decodeResult, length: decodeResult.length };
case "reset":
codec_instance.reset();
break;
default:
return "unknown command";
}
function registerCommandHandler<T extends keyof CWCommand>(command: T, callback: (message: CWCommand[T]) => Promise<CWCommandResponseType<T>>) {
messageHandlers[command as any] = callback;
}
const handleOwnerMessage = (e: MessageEvent) => {
const timestampReceived = Date.now();
const message = e.data as CWMessage;
const handle_message_event = (e: MessageEvent) => {
const token = e.data.token;
const received = Date.now();
if(message.type === "error" || message.type === "success") {
console.warn("%sReceived a command response within the worker. We're not sending any commands so this should not happen!", prefix);
return;
} else if(message.type === "notify") {
console.warn("%sReceived a notify within the worker. This should not happen!", prefix);
return;
} else if(message.type === "command") {
const command = message as CWMessageCommand;
const send_result = result => {
const data = {};
if(typeof result === "object") {
data["result"] = result;
data["success"] = true;
} else if(typeof result === "string") {
data["error"] = result;
data["success"] = false;
} else {
data["error"] = "invalid result";
data["success"] = false;
}
data["token"] = token;
data["timestamp_received"] = received;
data["timestamp_send"] = Date.now();
const sendExecuteError = error => {
let errorMessage;
if(typeof error === "string") {
errorMessage = error;
} else if(error instanceof Error) {
console.error("%sMessage handle error: %o", prefix, error);
errorMessage = error.message;
} else {
console.error("%sMessage handle error: %o", prefix, error);
errorMessage = "lookup the console";
}
postMessage(data, undefined);
};
handle_message(e.data.command, e.data.data).then(res => {
if(token) {
send_result(res);
postMessage({
type: "error",
error: errorMessage,
timestampReceived: timestampReceived,
timestampSend: Date.now(),
token: command.token
} as CWMessageErrorResponse, undefined, commandTransferableResponse);
};
const sendExecuteResult = result => {
postMessage({
type: "success",
response: result,
timestampReceived: timestampReceived,
timestampSend: Date.now(),
token: command.token
} as CWMessageResponse, undefined);
};
const handler = messageHandlers[message.command as any];
if(!handler) {
sendExecuteError("unknown command");
return;
}
}).catch(error => {
console.warn("An error has been thrown while handing command %s: %o", e.data.command, error);
if(token) {
send_result(typeof error === "string" ? error : "unexpected exception has been thrown");
}
});
handler(command.payload).then(sendExecuteResult).catch(sendExecuteError);
}
};
addEventListener("message", handle_message_event);
addEventListener("message", handleOwnerMessage);
/* command handlers */
registerCommandHandler("global-initialize", async () => {
const init_result = globally_initialized ? global_initialize_result : await initialize_callback();
globally_initialized = true;
if(typeof init_result === "string")
throw init_result;
});
registerCommandHandler("initialise", async data => {
console.log(prefix + "Initialize for codec %s", CodecType[data.type as CodecType]);
if(!supported_types[data.type])
throw "type unsupported";
try {
codec_instance = await supported_types[data.type](data);
} catch(ex) {
console.error("%sFailed to allocate codec: %o", prefix, ex);
throw typeof ex === "string" ? ex : "failed to allocate codec";
}
const error = codec_instance.initialise();
if(error)
throw error;
});
registerCommandHandler("reset", async () => {
codec_instance.reset();
});
registerCommandHandler("finalize", async () => {
/* memory will be cleaned up by its own */
});
let responseBuffer: Uint8Array;
const popResponseBuffer = () => { const temp = responseBuffer; responseBuffer = undefined; return temp; }
registerCommandHandler("decode-payload", async data => {
if(!codec_instance)
throw "codec not initialized/initialize failed";
const byteLength = codec_instance.decode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), length => {
if(length > data.maxByteLength)
throw "source buffer too small to hold the result";
//return responseBuffer = new Uint8Array(length);
return responseBuffer = new Uint8Array(data.buffer, 0, data.maxByteLength);
});
const buffer = popResponseBuffer();
if(typeof byteLength === "string") {
throw byteLength;
}
commandTransferableResponse = [buffer.buffer];
return {
buffer: buffer.buffer,
byteLength: byteLength,
byteOffset: 0,
};
});
registerCommandHandler("encode-payload", async data => {
if(!codec_instance)
throw "codec not initialized/initialize failed";
const byteLength = codec_instance.encode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), length => {
if(length > data.maxByteLength)
throw "source buffer too small to hold the result";
//return responseBuffer = new Uint8Array(length);
return responseBuffer = new Uint8Array(data.buffer, 0, data.maxByteLength);
});
const buffer = popResponseBuffer();
if(typeof byteLength === "string") {
throw byteLength;
}
commandTransferableResponse = [buffer.buffer];
return {
buffer: buffer.buffer,
byteLength: byteLength,
byteOffset: 0
};
});

View File

@ -1,7 +1,6 @@
import * as cworker from "./CodecWorker";
import {CodecType} from "tc-backend/web/codec/Codec";
import {CodecWorker} from "./CodecWorker";
import {type} from "os";
declare global {
interface Window {
@ -113,7 +112,7 @@ enum OpusType {
const OPUS_ERROR_CODES = [
"One or more invalid/out of range arguments", //-1 (OPUS_BAD_ARG)
"Not enough bytes allocated in the buffer", //-2 (OPUS_BUFFER_TOO_SMALL)
"Not enough bytes allocated in the target buffer", //-2 (OPUS_BUFFER_TOO_SMALL)
"An internal error was detected", //-3 (OPUS_INTERNAL_ERROR)
"The compressed data passed is corrupted", //-4 (OPUS_INVALID_PACKET)
"Invalid/unsupported request number", //-5 (OPUS_UNIMPLEMENTED)
@ -162,23 +161,33 @@ class OpusWorker implements CodecWorker {
deinitialise() { } //TODO
decode(data: Uint8Array): Float32Array | string {
if (data.byteLength > this.decode_buffer.byteLength) return "supplied data exceeds internal buffer";
this.decode_buffer.set(data);
decode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array): number | string {
if (buffer.byteLength > this.decode_buffer.byteLength)
return "supplied data exceeds internal buffer";
let result = this.fn_decode(this.nativeHandle, this.decode_buffer.byteOffset, data.byteLength, this.decode_buffer.byteLength);
this.decode_buffer.set(buffer);
let result = this.fn_decode(this.nativeHandle, this.decode_buffer.byteOffset, buffer.byteLength, this.decode_buffer.byteLength);
if (result < 0) return OPUS_ERROR_CODES[-result] || "unknown decode error " + result;
return Module.HEAPF32.slice(this.decode_buffer.byteOffset / 4, (this.decode_buffer.byteOffset / 4) + (result * this.channelCount));
const resultByteLength = result * this.channelCount * 4;
const resultBuffer = responseBuffer(resultByteLength);
resultBuffer.set(this.decode_buffer.subarray(0, resultByteLength), 0);
return resultByteLength;
}
encode(data: Float32Array): Uint8Array | string {
this.encode_buffer.set(data);
encode(buffer: Uint8Array, responseBuffer: (length: number) => Uint8Array): number | string {
if (buffer.byteLength > this.decode_buffer.byteLength)
return "supplied data exceeds internal buffer";
let result = this.fn_encode(this.nativeHandle, this.encode_buffer.byteOffset, data.length, this.encode_buffer.byteLength);
this.encode_buffer.set(buffer);
let result = this.fn_encode(this.nativeHandle, this.encode_buffer.byteOffset, buffer.byteLength, this.encode_buffer.byteLength);
if (result < 0) return OPUS_ERROR_CODES[-result] || "unknown encode error " + result;
return Module.HEAP8.slice(this.encode_buffer.byteOffset, this.encode_buffer.byteOffset + result);
const resultBuffer = responseBuffer(result);
resultBuffer.set(Module.HEAP8.subarray(this.encode_buffer.byteOffset, this.encode_buffer.byteOffset + result));
return result;
}
reset() {

View File

@ -0,0 +1,68 @@
//
// Created by WolverinDEV on 12/06/2020.
//
/* source and target should not be intersecting! */
template <size_t kChannelCount>
void sequenced2interleaved(float* source, float* target, size_t sample_count) {
#pragma unroll
for(size_t channel = 0; channel < kChannelCount; channel++) {
auto src_ptr = source + channel * sample_count;
auto dest_ptr = target + channel;
auto samples_left = sample_count;
while(samples_left--) {
*dest_ptr = *src_ptr;
src_ptr++;
dest_ptr += kChannelCount;
}
}
}
/* source and target should not be intersecting! */
template <size_t kChannelCount>
void interleaved2sequenced(float* source, float* target, size_t sample_count) {
#pragma unroll
for(size_t channel = 0; channel < kChannelCount; channel++) {
auto src_ptr = source + channel;
auto dest_ptr = target + channel * sample_count;
auto samples_left = sample_count;
while(samples_left--) {
*dest_ptr = *src_ptr;
src_ptr += kChannelCount;
dest_ptr++;
}
}
}
#define kTempBufferSize (1024 * 8)
/* since js is single threaded we need no lock here */
float temp_buffer[kTempBufferSize];
template <size_t kChannelCount>
void interleaved2sequenced_intersecting(float* buffer, size_t sample_count) {
auto temp = temp_buffer;
if(sample_count * kChannelCount > kTempBufferSize)
temp = (float*) malloc(sample_count * sizeof(float) * kChannelCount);
memcpy(temp, buffer, sample_count * sizeof(float) * kChannelCount);
interleaved2sequenced<kChannelCount>(temp, buffer, sample_count);
if(temp != temp_buffer)
free(temp);
}
template <size_t kChannelCount>
void sequenced2interleaved_intersecting(float* buffer, size_t sample_count) {
auto temp = temp_buffer;
if(sample_count * kChannelCount > kTempBufferSize)
temp = (float*) malloc(sample_count * sizeof(float) * kChannelCount);
memcpy(temp, buffer, sample_count * sizeof(float) * kChannelCount);
sequenced2interleaved<kChannelCount>(temp, buffer, sample_count);
if(temp != temp_buffer)
free(temp);
}

View File

@ -3,6 +3,7 @@
#include <string_view>
#include <emscripten.h>
#include <string>
#include "./ilarvecon.cpp"
typedef std::unique_ptr<OpusEncoder, decltype(opus_encoder_destroy)*> opus_encoder_t;
typedef std::unique_ptr<OpusDecoder, decltype(opus_decoder_destroy)*> opus_decoder_t;
@ -82,16 +83,22 @@ void codec_opus_deleteNativeHandle(OpusHandle *codec) {
}
EMSCRIPTEN_KEEPALIVE
int codec_opus_encode(OpusHandle *handle, uint8_t *buffer, size_t length, size_t maxLength) {
auto result = opus_encode_float(&*handle->encoder, (float *) buffer, length / handle->channelCount, buffer, maxLength);
int codec_opus_encode(OpusHandle *handle, uint8_t *buffer, size_t byte_length, size_t maxLength) {
if(handle->channelCount == 2)
sequenced2interleaved_intersecting<2>((float *) buffer, byte_length / (sizeof(float) * 2));
auto result = opus_encode_float(&*handle->encoder, (float *) buffer, byte_length / handle->channelCount, buffer, maxLength);
if (result < 0) return result;
return result;
}
EMSCRIPTEN_KEEPALIVE
int codec_opus_decode(OpusHandle *handle, uint8_t *buffer, size_t length, size_t maxLength) {
auto result = opus_decode_float(&*handle->decoder, buffer, length, (float *) buffer, maxLength / sizeof(float) / handle->channelCount, false);
int codec_opus_decode(OpusHandle *handle, uint8_t *buffer, size_t byte_length, size_t buffer_max_byte_length) {
auto result = opus_decode_float(&*handle->decoder, buffer, byte_length, (float *) buffer, buffer_max_byte_length / sizeof(float) / handle->channelCount, false);
if (result < 0) return result;
if(handle->channelCount == 2)
interleaved2sequenced_intersecting<2>((float *) buffer, result);
return result;
}