blog

オーディオ乗算(wasm)のフロントエンド実装

職歴 教育系企業でライブストリーミング事業に従事。現在、音声の新しい再生方法を模索中。今まではFMP4へのコーデックを使用し、MSEを通して音声をストリーミングしていましたが、一部のPCでは音が再生さ...

Oct 21, 2020 · 7 min. read
シェア

コンテキスト

職務経歴 教育関連企業でライブストリーミング事業に従事。現在、音声の新しい再生方法を探しています。これまでFMP4コーデックを使用し、MSEを通して音声をストリーミングしてきましたが、一部のパソコンでは音声が再生できないというフィードバックをユーザーから受けたため、AudioContextを通してpcmを再生するという新しい再生方式を採用しました。主な理由は、FMP4プログラムブラウザは乗算効果を達成するのに役立ちますが、乗算を達成するためのAudioContext再生pcmは、自分自身で達成する必要があるので、達成するためにsonicを使用します。

コンパイル・ソニック

一般的にWebassmeblyのデバッグプロセスでは、まずネイティブ層のデバッグを経て、wasmとjsのデバッグのコンパイルにあります。

まず、ソニックライブラリをダウンロードします:

git clone github.com/waywardgeek...

コンパイルします:

cd sonic/

make

インストール

今回は、sonic.a、sonic.oなどのファイルを生成し、ネイティブコードを記述して、ネイティブ層のデバッグを行います:

wrapper_sonic.cの作成:

/*
 * @Author: xiuquanxu
 * @Company: kaochong
 * @Date:  
 * @LastEditors: xiuquanxu
 * @LastEditTime:  
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "sonic.h"
#define TAG "wrapper_sonic"
typedef struct {
 sonicStream sonic_handle;
 uint8_t *in_data;
 uint8_t *out_data;
} as_sonic_t;
uint8_t* sonicInit(int sampleRate, int numChannels, int maxRate) {
 as_sonic_t *sonicer = (as_sonic_t *)malloc(sizeof(as_sonic_t));
 sonicer->sonic_handle = sonicCreateStream(sampleRate, numChannels);
 sonicer->in_data = (uint8_t *)malloc(sampleRate * numChannels * maxRate);
 sonicer->out_data = (uint8_t *)malloc(sampleRate * numChannels * maxRate);
 sonicSetSpeed(sonicer->sonic_handle, 0.5);
 return (uint8_t *)sonicer;
}
void setSpeed(uint8_t* dec, int speed) {
 as_sonic_t *sonicer = (as_sonic_t *)dec;
 sonicSetSpeed(sonicer->sonic_handle, speed);
}
int pcmHandleRateBySonic(uint8_t* dec, uint8_t* input, int input_len, uint8_t* output, int samples) {
 printf(" pcmHandleRateBySonic input_len
");
 as_sonic_t *sonicer = (as_sonic_t *)dec;
 printf(" before memcpy
");
 memcpy(sonicer->in_data, input, input_len);
 int write_res = sonicWriteShortToStream(sonicer->sonic_handle, (short *)sonicer->in_data, samples / 2);
 printf(" after memcpy write_res:%d
", write_res);
 if (write_res != 1) {
 printf("%s pcmHandleRateBySonic write_res:%d
", TAG, write_res);
 return write_res;
 }
 int read_res = sonicReadShortFromStream(sonicer->sonic_handle, (short *)sonicer->out_data, samples / 2);
 printf(" read res:%d
", read_res);
 memcpy(output, sonicer->out_data, read_res * 2);
 return read_res * 2;
}
int destory(uint8_t *dec) {
 as_sonic_t *sonicer = (as_sonic_t *)dec;
 if (sonicer) {
 if (sonicer->sonic_handle) {
 sonicDestroyStream(sonicer->sonic_handle);
 }
 if (sonicer->in_data) {
 free(sonicer->in_data);
 }
 if (sonicer->out_data) {
 free(sonicer->out_data);
 }
 free(sonicer);
 }
 return 0;
}
int test() {
 int sampleRate = 48000;
 int numChannels = 1;
 int maxRate = 10;
 FILE *fin = NULL;
 FILE *fout = NULL;
 fin = fopen("/Users/xuxiuquan/mygithub/like-player-lib/lib-sonic/sonic/webassembly-test/test-pcm-web.pcm", "rb");
 fout = fopen("/Users/xuxiuquan/mygithub/like-player-lib/lib-sonic/sonic/webassembly-test/after-sonic-pcm.pcm", "wb");
 // ソニックを初期化する
 uint8_t* content = (uint8_t*)malloc();
 uint8_t* res = (uint8_t *)malloc();
 fread(content, , fin);
 uint8_t* son = sonicInit(sampleRate, numChannels, maxRate);
 int read_res = pcmHandleRateBySonic(son, content, , res, );
 printf("test res:%d
", read_res);
 fwrite(res, read_res, 1, fout);
 return 0;
}
int main() {
 test();
 return 0;
}

test()メソッドは、APIをテストするメソッドです。ここでは、sonicインターフェースの2番目のカプセル化、初期化インターフェース:sonicInit、乗算インターフェース:setSpeed、pcmデータの処理インターフェース:pcmHandleRateBySonic.

finとfoutの入力と出力に注意してください。finはパスをローカルのpcmに書き出し、foutは出力をローカルに書き出します。

ここでは、wrapper_sonic.cをgccを使って直接コンパイルしています。

 gcc wrapper_sonic.c -o test.a -I/Users/xuxiuquan/mygithub/like-player-lib/lib-sonic/sonic/ -L/Users/xuxiuquan/mygithub/like-player-lib/lib-sonic/sonic/ -lsonic

コンパイルに成功すると、test.aが出力されます。/test.a

実行後、乗算されたpcmがfoutとして定義したパスに出力され、この時点でvlc経由でそのpcmを再生することができます:

/Applications/VLC.app/Contents/MacOS/VLC --demux=rawaud --rawaud-channels 1 --rawaud-samplerate 44800 /Users/xuxiuquan/Downloads/sonic-web-pcm.pcm

正常に再生されれば、ネイティブレイヤーでのデバッグが成功したことになります。

wasmのコンパイル

まず、emsdkがインストールされていることを確認してください:

http://../-/-de/

コンパイルスクリプトを書く: webassembly.sh

echo "----------------------------"
echo "start building"
# source /Users/xuxiuquan/github/webassembly/emsdk/emsdk_env.sh
rm -rf ../webassembly
mkdir ../webassembly
EMCC_DEBUG=1 \
EMMAKEN_CFLAGS="-I/usr/local/include -DNOPUS_HAVE_RTCD" \
emcc ./sonic.c ./wrapper_sonic.c\
 -g2 \
 -s EMULATE_FUNCTION_POINTER_CASTS=1 \
 -s ASSERTIONS=2 \
 -s WASM=1 \
 -s ALLOW_MEMORY_GROWTH=1 \
 -s "MODULARIZE=1" \
 -s "EXPORT_NAME='WebSonic'" \
 -s "BINARYEN_METHOD='native-wasm'" \
 -s "EXPORTED_FUNCTIONS=['_sonicInit', '_pcmHandleRateBySonic', '_setSpeed', '_destory']" \
 -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
 -o ../webassembly/sonic.js \
 -Wall \
https://.rg/

ここで注目すべき点は、パラメーター EXPORTED_FUNCTIONS で、 wrapper_sonic.c や sonic.c からエクスポートしたい関数を表します。ここで、3つのことに注意してください、 まず、emccはgccと同等であり、em++はg++と同等です。第二に、main関数はデフォルトでエクスポートされます。これはemsdkの要件です。

エンジニアリングでの使用

sonic.wasmによってロードされたバイナリを、初期化のためにglueコードにネットワークリクエストで渡す必要があり、初期化が成功するとonRuntimeInitializedこの関数がトリガーされます。

ローディング・コードは次のようになります:

 function loadWasm() {
 var req = new XMLHttpRst();
 req.responseType = 'arraybuffer';
 req.addEventListener('load', () => {
 var wasmBuffer = req.response;
 <!-- 共通メモリ領域を取得する -->
 WebSonic['wasmBinary'] = wasmBuffer;
 var wasmDsp = WebSonic({ wasmBinary: WebSonic.wasmBinary });
 <!-- jsが使えるようにネイティブメソッドをエクスポートする -->
 var sonicInit = wasmDsp.cwrap('sonicInit', ['number'], ['number', 'number', 'number']);
 var pcmHandleRate = wasmDsp.cwrap('pcmHandleRateBySonic', ['number'], ['number', 'number', 'number', 'number', 'number']);
 var setSpeed = wasmDsp.cwrap('setSpeed', null, ['number', 'number']);
 <!-- グローバルオブジェクトにメソッドをバインドする -->
 ModuleSonic.init = sonicInit;
 ModuleSonic.handleRate = pcmHandleRate;
 ModuleSonic.setSpeed = setSpeed;
 ModuleSonic.wasmDsp = wasmDsp;
 });
 req.open('GET', './webassembly/sonic.wasm');
 req.send();
 }
 function onRuntimeInitialized() {
 <!-- emsdkを呼び出して_malloc公開領域のメモリの一部をリクエストする関数 -->
 c_write_ptr = ModuleSonic.wasmDsp._malloc();
 c_reader_ptr = ModuleSonic.wasmDsp._malloc();
 handle = ModuleSonic.init(, 10);
 }

既存のpcm_playerライブラリを使用した再生。

 function start() {
 <!-- pcm_player库 -->
 var player = new PCMPlayer({
 encoding: '16bitInt',
 channels: 1,
 sampleRate: 44800,
 flushingTime: 1000
 });
 const needWriteBuffer = new Uint8Array(buffer.slice(0, ));
 <!-- pcmデータバッファを共有メモリに書き込む -->
 ModuleSonic.wasmDsp.HEAPU8.set(needWriteBuffer, c_write_ptr);
 <!-- 掛け算のネイティブインターフェースを呼び出す -->
 var writeRes = ModuleSonic.handleRate(handle, c_write_ptr, , c_reader_ptr, );
 console.log("writeRes:", writeRes, c_reader_ptr);
 <!-- 2倍した結果を短い型で取り出す、jsに対応するのはUint16Array -->
 const pcm = ModuleSonic.wasmDsp.HEAP16.slice(c_reader_ptr / 2, (c_reader_ptr + writeRes) / 2);
 <!-- プレイヤーにデータを投げる -->
 player.feed(pcm);
 }

上記のコードは、それぞれの文の意味を明確に理解できるようにコメントされています。

上記の操作を通じて、オーディオを再生するには、AudioContextを介してpcmデータの速度のWebページソニックライブラリ倍で実装することができます。

  1. 一般的なデバッグ用にネイティブコンパイルとwasmコンパイルを導入。
  2. 音波使用の紹介
  3. ソニックをワームにコンパイルするプロセスを紹介。
  4. ソニック統合ウェブ再生の実装

注:このようなCPU負荷の高い計算は、実際にはウェブワーカーで行うべきです。

github.com/this-spring...

背中の機能:

  1. ウェブワーカーの練習

Read next

暗号通貨の世界では、DeFiというキーワードがますます熱くなっている。

DeFiというキーワードは2019年に入ってから暗号通貨界隈でどんどん熱を帯びてきており、今年に入ってからは文句なしの爆発的な盛り上がりを見せています。DeFiサービスを提供するプラットフォームやプロダクトが続々と誕生。DeFiの急成長を前に、私たちはこう考えざるを得ません:DeFiは暗号通貨界で最も人気のあるサービスです。

Oct 21, 2020 · 2 min read