Skip to content

Commit

Permalink
Merge branch 'release/2022.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
sile committed Dec 8, 2022
2 parents bf72674 + 1b97ab5 commit 4b0c0db
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 66 deletions.
17 changes: 17 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@

## develop

## 2022.2.0

- [CHANGE] サンプルレート・チャンネル数・ビットレートの取り得る値を型で明記する
- @sile
- [CHANGE] デフォルトサンプルレートを 16000 に変更
- @sile
- [CHANGE] 無駄な変換を最小限にするために encoder / decoder が float-32 ではなく int-16 で音声データをやりとりするようにする
- @sile
- [UPDATE] google/lyra のバージョンを v1.3.1 に更新
- @sile
- [ADD] LYRA_VERSION 定数を追加
- @sile
- [UPDATE] JavaScript と WebAssembly 間での音声データ転送を高速化
- @sile
- [CHANGE] LyraEncoder.encode() でエンコードに失敗した場合には undefined を返すのではなく例外を送出するようにする
- @sile

## 2022.1.0

**初リリース**
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ const decoded = lyraDecoder.decode(encoded);
[リリースページ](https://github.com/shiguredo/lyra-wasm/releases)からビルド済みの lyra.wasm および lyra.worker.js ファイルが取得できます。

```console
$ curl -OL https://github.com/shiguredo/lyra-wasm/releases/download/2022.1.0/lyra.wasm
$ curl -OL https://github.com/shiguredo/lyra-wasm/releases/download/2022.1.0/lyra.worker.js
$ curl -OL https://github.com/shiguredo/lyra-wasm/releases/download/2022.2.0/lyra.wasm
$ curl -OL https://github.com/shiguredo/lyra-wasm/releases/download/2022.2.0/lyra.worker.js
```

自前でビルドする場合には、以下のコマンドを実行してください。
Expand Down
14 changes: 12 additions & 2 deletions examples/recording.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ <h2>操作</h2>
const decoder = lyraModule.createDecoder();
const halfFrameSize = encoder.frameSize / 2;
const audioData = new Float32Array(encoder.frameSize);
const audioDataInt16 = new Int16Array(encoder.frameSize);

let totalEncodedSize = 0;
let startTime = performance.now();
Expand All @@ -128,11 +129,20 @@ <h2>操作</h2>
originalAudioDataList[i].copyTo(audioData.subarray(0, halfFrameSize), { planeIndex: 0 });
originalAudioDataList[i + 1].copyTo(audioData.subarray(halfFrameSize), { planeIndex: 0 });

const encoded = encoder.encode(audioData);
for (let [i, v] of audioData.entries()) {
audioDataInt16[i] = v * 0x7FFF;
}

const encoded = encoder.encode(audioDataInt16);
if (encoded !== undefined) {
totalEncodedSize += encoded.length;
}
const processedData = decoder.decode(encoded);
const processedDataInt16 = decoder.decode(encoded);
const processedData = new Float32Array(processedDataInt16.length);
for (let [i, v] of processedDataInt16.entries()) {
processedData[i] = v / 0x7FFF;
}

processedAudioDataList.push(
new AudioData({
format: originalAudioDataList[i].format,
Expand Down
3 changes: 3 additions & 0 deletions examples/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ self.addEventListener('fetch', (e) => {
headers.set("Cross-Origin-Embedder-Policy", "require-corp");
headers.set("Cross-Origin-Opener-Policy", "same-origin");

// Chrome でローカルの http で実行する場合に必要
headers.set("Content-Security-Policy", "treat-as-public-address");

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shiguredo/lyra-wasm",
"version": "2022.1.0",
"version": "2022.2.0",
"description": "Lyra V2 WebAssembly build",
"main": "dist/lyra.mjs",
"module": "dist/lyra.mjs",
Expand Down Expand Up @@ -32,7 +32,7 @@
"devDependencies": {
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^9.0.2",
"@rollup/plugin-typescript": "^10.0.0",
"@types/emscripten": "^1.39.6",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
Expand Down
92 changes: 52 additions & 40 deletions src/lyra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,39 @@ import * as lyra_wasm from "./lyra_wasm.js";

const MEMFS_MODEL_PATH = "/tmp/";

/**
* Lyra のエンコード形式のバージョン。
*
* エンコード形式に非互換な変更が入った時点での google/lyra のバージョンが格納されている。
*/
const LYRA_VERSION = "1.3.0";

/**
* {@link LyraModule.createEncoder} メソッドに指定可能なオプション
*/
interface LyraEncoderOptions {
/**
* 入力音声データのサンプルレート
*
* 指定可能な値: 8000, 16000, 32000, 48000
* デフォルト値: 48000
* なお 16000 以外のサンプルレートが指定された場合には、内部的にはリサンプルが行われる。
*
* デフォルト値: 16000
*/
sampleRate?: number;
sampleRate?: 8000 | 16000 | 32000 | 48000;

/**
* 入力音声データのチャンネル数
*
* 現在は 1 (モノラル、デフォルト値)のみが指定可能
* 現在は 1 (モノラル)のみが指定可能
*/
numberOfChannels?: number;
numberOfChannels?: 1;

/**
* エンコード後の音声データのビットレート
*
* 指定可能な値: 3200, 6000, 9200
* デフォルト値: 9200
*/
bitrate?: number;
bitrate?: 3200 | 6000 | 9200;

/**
* DTX(discontinuous transmission)を有効にするかどうか
Expand All @@ -45,20 +52,19 @@ interface LyraDecoderOptions {
/**
* 入力音声データのサンプルレート
*
* 指定可能な値: 8000, 16000, 32000, 48000
* デフォルト値: 48000
* デフォルト値: 16000
*/
sampleRate?: number;
sampleRate?: 8000 | 16000 | 32000 | 48000;

/**
* 入力音声データのチャンネル数
*
* 現在は 1 (モノラル、デフォルト値)のみが指定可能
* 現在は 1 (モノラル)のみが指定可能
*/
numberOfChannels?: number;
numberOfChannels?: 1;
}

const DEFAULT_SAMPLE_RATE = 48000;
const DEFAULT_SAMPLE_RATE = 16000;
const DEFAULT_BITRATE = 9200;
const DEFAULT_ENABLE_DTX = false;
const DEFAULT_CHANNELS = 1;
Expand Down Expand Up @@ -124,7 +130,7 @@ class LyraModule {
} else {
const frameSize = ((options.sampleRate || DEFAULT_SAMPLE_RATE) * FRAME_DURATION_MS) / 1000;
const buffer = this.wasmModule.newAudioData(frameSize);
return new LyraEncoder(encoder, buffer, options);
return new LyraEncoder(this.wasmModule, encoder, buffer, options);
}
}

Expand All @@ -149,7 +155,7 @@ class LyraModule {
throw new Error("failed to create lyra decoder");
} else {
const buffer = this.wasmModule.newBytes();
return new LyraDecoder(decoder, buffer, options);
return new LyraDecoder(this.wasmModule, decoder, buffer, options);
}
}
}
Expand All @@ -158,6 +164,7 @@ class LyraModule {
* Lyra のエンコーダ
*/
class LyraEncoder {
private wasmModule: lyra_wasm.LyraWasmModule;
private encoder: lyra_wasm.LyraWasmEncoder;
private buffer: lyra_wasm.AudioData;

Expand Down Expand Up @@ -189,15 +196,20 @@ class LyraEncoder {
/**
* @internal
*/
constructor(encoder: lyra_wasm.LyraWasmEncoder, buffer: lyra_wasm.AudioData, options: LyraEncoderOptions) {
constructor(
wasmModule: lyra_wasm.LyraWasmModule,
encoder: lyra_wasm.LyraWasmEncoder,
buffer: lyra_wasm.AudioData,
options: LyraEncoderOptions
) {
this.wasmModule = wasmModule;
this.encoder = encoder;
this.buffer = buffer;

this.sampleRate = options.sampleRate || DEFAULT_SAMPLE_RATE;
this.numberOfChannels = options.numberOfChannels || DEFAULT_CHANNELS;
this.bitrate = options.bitrate || DEFAULT_BITRATE;
this.enableDtx = options.enableDtx || DEFAULT_ENABLE_DTX;

this.frameSize = buffer.size();
}

Expand All @@ -208,31 +220,33 @@ class LyraEncoder {
* @returns エンコード後のバイト列。もし DTX が有効で音声データが無音な場合には undefined が代わりに返される。
*
* @throws
* 入力音声データが 20ms 単位(サンプル数としては {@link LyraEncoder.frameSize})ではない場合には例外が送出される
*
* 以下のいずれかに該当する場合には例外が送出される:
* - 入力音声データが 20ms 単位(サンプル数としては {@link LyraEncoder.frameSize})ではない
* - その他、何らかの理由でエンコードに失敗した場合
*/
encode(audioData: Float32Array): Uint8Array | undefined {
encode(audioData: Int16Array): Uint8Array | undefined {
if (audioData.length !== this.frameSize) {
throw new Error(
`expected an audio data with ${this.frameSize} samples, but got one with ${audioData.length} samples`
);
}

for (const [i, v] of audioData.entries()) {
this.buffer.set(i, convertFloat32ToInt16(v));
}
this.wasmModule.copyInt16ArrayToAudioData(this.buffer, audioData);

const result = this.encoder.encode(this.buffer);

if (result === undefined) {
return undefined;
throw new Error("failed to encode");
} else {
try {
const encodedAudioData = new Uint8Array(result.size());
for (let i = 0; i < encodedAudioData.length; i++) {
encodedAudioData[i] = result.get(i);
}

if (encodedAudioData.length === 0) {
/// google/lyra のドキュメント上は DTX が有効かつ入力が無音な場合には `result === undefined` になるべきだが、
/// 現在の実装上では空バイナリが返ってくるので、ここでハンドリングしている
// DTX が有効、かつ、 audioData が無音ないしノイズだけを含んでいる場合にはここに来る
return undefined;
}
return encodedAudioData;
Expand Down Expand Up @@ -268,6 +282,7 @@ class LyraEncoder {
* Lyra のデコーダ
*/
class LyraDecoder {
private wasmModule: lyra_wasm.LyraWasmModule;
private decoder: lyra_wasm.LyraWasmDecoder;
private buffer: lyra_wasm.Bytes;

Expand All @@ -289,7 +304,13 @@ class LyraDecoder {
/**
* @internal
*/
constructor(decoder: lyra_wasm.LyraWasmDecoder, buffer: lyra_wasm.Bytes, options: LyraDecoderOptions) {
constructor(
wasmModule: lyra_wasm.LyraWasmModule,
decoder: lyra_wasm.LyraWasmDecoder,
buffer: lyra_wasm.Bytes,
options: LyraDecoderOptions
) {
this.wasmModule = wasmModule;
this.decoder = decoder;
this.buffer = buffer;

Expand All @@ -305,7 +326,7 @@ class LyraDecoder {
* @params encodedAudioData デコード対象のバイナリ列ないし undefined
* @returns デコードされた 20ms 分の音声データ。undefined が渡された場合には代わりにコンフォートノイズが生成される。
*/
decode(encodedAudioData: Uint8Array | undefined): Float32Array {
decode(encodedAudioData: Uint8Array | undefined): Int16Array {
if (encodedAudioData !== undefined) {
this.buffer.resize(0, 0); // clear() を使うと「関数が存在しない」というエラーが出るので resize() で代用
for (const v of encodedAudioData) {
Expand All @@ -317,14 +338,13 @@ class LyraDecoder {
}

const result = this.decoder.decodeSamples(this.frameSize);

if (result === undefined) {
throw Error("failed to decode samples");
}
try {
const audioData = new Float32Array(this.frameSize);
for (let i = 0; i < this.frameSize; i++) {
audioData[i] = convertInt16ToFloat32(result.get(i));
}
const audioData = new Int16Array(this.frameSize);
this.wasmModule.copyAudioDataToInt16Array(audioData, result);
return audioData;
} finally {
result.delete();
Expand All @@ -340,14 +360,6 @@ class LyraDecoder {
}
}

function convertFloat32ToInt16(v: number): number {
return Math.max(-32768, Math.min(Math.round(v * 0x7fff), 32767));
}

function convertInt16ToFloat32(v: number): number {
return v / 0x7fff;
}

function trimLastSlash(s: string): string {
if (s.slice(-1) === "/") {
return s.slice(0, -1);
Expand Down Expand Up @@ -387,4 +399,4 @@ function checkBitrate(n: number | undefined): void {
throw new Error(`unsupported bitrate: expected one of 3200, 6000 or 9200, but got ${n}`);
}

export { LyraModule, LyraDecoder, LyraEncoder, LyraEncoderOptions, LyraDecoderOptions };
export { LyraModule, LyraDecoder, LyraEncoder, LyraEncoderOptions, LyraDecoderOptions, LYRA_VERSION };
4 changes: 3 additions & 1 deletion src/lyra_wasm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ export interface LyraWasmDecoder {
export interface LyraWasmModule extends EmscriptenModule {
LyraEncoder: LyraWasmEncoderClass;
LyraDecoder: LyraWasmDecoderClass;
newAudioData(numberOfSamples: number): AudioData;
newBytes(): Bytes;
newAudioData(numberOfSamples: number): AudioData;
copyAudioDataToInt16Array(to: Int16Array, from: AudioData): void;
copyInt16ArrayToAudioData(to: AudioData, from: Int16Array): void;
FS_createPreloadedFile(parent: string, name: string, url: string, canRead: boolean, canWrite: boolean): void;
}

Expand Down
4 changes: 2 additions & 2 deletions wasm/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ cc_binary(
name = "lyra",
srcs = ["lyra.cc"],
deps = [
"@lyra//:lyra_encoder",
"@lyra//:lyra_decoder",
"@lyra//lyra:lyra_encoder",
"@lyra//lyra:lyra_decoder",
],
linkopts = [
"--bind",
Expand Down

0 comments on commit 4b0c0db

Please sign in to comment.