Skip to content

Commit

Permalink
WIP: Add WebAssembly + NodeJS example.
Browse files Browse the repository at this point in the history
  • Loading branch information
csukuangfj committed Feb 23, 2024
1 parent 185f5c7 commit 1e4d5e0
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 72 deletions.
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ option(SHERPA_NCNN_ENABLE_BINARY "Whether to build the binary sherpa-ncnn" ON)
option(SHERPA_NCNN_ENABLE_TEST "Whether to build tests" OFF)
option(SHERPA_NCNN_ENABLE_C_API "Whether to build C API" ON)
option(SHERPA_NCNN_ENABLE_WASM "Whether to enable WASM" OFF)
option(SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS "Whether to enable WASM for NodeJS" OFF)
option(SHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE "Whether to generate-int8-scale-table" ON)
option(SHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES "Whether to enable ffmpeg-examples" OFF)

Expand Down Expand Up @@ -77,6 +78,14 @@ message(STATUS "SHERPA_NCNN_ENABLE_TEST ${SHERPA_NCNN_ENABLE_TEST}")
message(STATUS "SHERPA_NCNN_ENABLE_C_API ${SHERPA_NCNN_ENABLE_C_API}")
message(STATUS "SHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE ${SHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE}")
message(STATUS "SHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES ${SHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES}")
message(STATUS "SHERPA_NCNN_ENABLE_WASM ${SHERPA_NCNN_ENABLE_WASM}")
message(STATUS "SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS ${SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS}")

if(SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS)
if(NOT SHERPA_NCNN_ENABLE_WASM)
message(FATAL_ERROR "Please set SHERPA_NCNN_ENABLE_WASM to ON if you enable WASM for NodeJS")
endif()
endif()

if(NOT CMAKE_BUILD_TYPE)
message(STATUS "No CMAKE_BUILD_TYPE given, default to Release")
Expand Down
76 changes: 76 additions & 0 deletions build-wasm-simd-for-nodejs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Xiaomi Corporation
#
# This script is to build sherpa-ncnn for WebAssembly (NodeJS)
#
# See also
# https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-webassembly
#
# Please refer to
# https://k2-fsa.github.io/sherpa/ncnn/wasm/index.html
# for more details.

set -ex

if [ x"$EMSCRIPTEN" == x"" ]; then
if ! command -v emcc &> /dev/null; then
echo "Please install emscripten first"
echo ""
echo "You can use the following commands to install it:"
echo ""
echo "git clone https://github.com/emscripten-core/emsdk.git"
echo "cd emsdk"
echo "git pull"
echo "./emsdk install latest"
echo "./emsdk activate latest"
echo "source ./emsdk_env.sh"
exit 1
else
EMSCRIPTEN=$(dirname $(realpath $(which emcc)))
fi
fi

export EMSCRIPTEN=$EMSCRIPTEN
echo "EMSCRIPTEN: $EMSCRIPTEN"
if [ ! -f $EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake ]; then
echo "Cannot find $EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake"
echo "Please make sure you have installed emsdk correctly"
exit 1
fi

mkdir -p build-wasm-simd-for-nodejs
pushd build-wasm-simd-for-nodejs

export SHERPA_NCNN_IS_USING_BUILD_WASM_SH=ON

cmake \
-DCMAKE_INSTALL_PREFIX=./install \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake \
-DNCNN_THREADS=OFF \
-DNCNN_OPENMP=OFF \
-DNCNN_SIMPLEOMP=OFF \
-DNCNN_RUNTIME_CPU=OFF \
-DNCNN_SSE2=ON \
-DNCNN_AVX2=OFF \
-DNCNN_AVX=OFF \
-DNCNN_BUILD_TOOLS=OFF \
-DNCNN_BUILD_EXAMPLES=OFF \
-DNCNN_BUILD_BENCHMARK=OFF \
\
-DSHERPA_NCNN_ENABLE_WASM=ON \
-DSHERPA_NCNN_ENABLE_WASM_FOR_NODEJS=ON \
-DBUILD_SHARED_LIBS=OFF \
-DSHERPA_NCNN_ENABLE_PYTHON=OFF \
-DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF \
-DSHERPA_NCNN_ENABLE_JNI=OFF \
-DSHERPA_NCNN_ENABLE_BINARY=OFF \
-DSHERPA_NCNN_ENABLE_TEST=OFF \
-DSHERPA_NCNN_ENABLE_C_API=ON \
-DSHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE=OFF \
-DSHERPA_NCNN_ENABLE_FFMPEG_EXAMPLES=OFF \
..

make -j2
make install
ls -lh install/bin/wasm
75 changes: 75 additions & 0 deletions nodejs-wasm-examples/decode-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const fs = require('fs');
const wav = require('wav');
const {Readable} = require('stream');

let Module = require('./sherpa-ncnn-wasm-main.js')()
let b = require('./sherpa-ncnn.js');

let recognizer = b.createRecognizer(Module);
let stream = recognizer.createStream();

console.log(recognizer.config);

const waveFilename = './0.wav';

const reader = new wav.Reader();
const readable = new Readable().wrap(reader);
const buf = [];

reader.on('format', ({audioFormat, bitDepth, channels, sampleRate}) => {
if (sampleRate != recognizer.config.featConfig.samplingRate) {
throw new Error(`Only support sampleRate ${
recognizer.config.featConfig.samplingRate}. Given ${sampleRate}`);
}

if (audioFormat != 1) {
throw new Error(`Only support PCM format. Given ${audioFormat}`);
}

if (channels != 1) {
throw new Error(`Only a single channel. Given ${channel}`);
}

if (bitDepth != 16) {
throw new Error(`Only support 16-bit samples. Given ${bitDepth}`);
}
});

fs.createReadStream(waveFilename, {'highWaterMark': 4096})
.pipe(reader)
.on('finish', function(err) {
// tail padding
const floatSamples =
new Float32Array(recognizer.config.featConfig.samplingRate * 0.5);

buf.push(floatSamples);
const flattened =
Float32Array.from(buf.reduce((a, b) => [...a, ...b], []));

stream.acceptWaveform(
recognizer.config.featConfig.samplingRate, flattened);
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
const r = recognizer.getResult(stream);
console.log('result', r);

stream.free();
recognizer.free();
});

readable.on('readable', function() {
let chunk;
while ((chunk = readable.read()) != null) {
const int16Samples = new Int16Array(
chunk.buffer, chunk.byteOffset,
chunk.length / Int16Array.BYTES_PER_ELEMENT);

const floatSamples = new Float32Array(int16Samples.length);
for (let i = 0; i < floatSamples.length; i++) {
floatSamples[i] = int16Samples[i] / 32768.0;
}

buf.push(floatSamples);
}
});
26 changes: 23 additions & 3 deletions wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,20 @@ list(JOIN mangled_exported_functions "," all_exported_functions)
include_directories(${CMAKE_SOURCE_DIR})
set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB ")
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue'] ")

if(SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS)
string(APPEND MY_FLAGS " -sNODERAWFS=1 ")
else()
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
endif()

string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")

if(SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS)
string(APPEND MY_FLAGS " -sMODULARIZE=1 -sWASM_ASYNC_COMPILATION=0 ")
endif()


message(STATUS "MY_FLAGS: ${MY_FLAGS}")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MY_FLAGS}")
Expand All @@ -50,7 +62,15 @@ install(
"index.html"
"$<TARGET_FILE_DIR:sherpa-ncnn-wasm-main>/sherpa-ncnn-wasm-main.js"
"$<TARGET_FILE_DIR:sherpa-ncnn-wasm-main>/sherpa-ncnn-wasm-main.wasm"
"$<TARGET_FILE_DIR:sherpa-ncnn-wasm-main>/sherpa-ncnn-wasm-main.data"
DESTINATION
bin/wasm
)

if(NOT SHERPA_NCNN_ENABLE_WASM_FOR_NODEJS)
install(
FILES
"$<TARGET_FILE_DIR:sherpa-ncnn-wasm-main>/sherpa-ncnn-wasm-main.data"
DESTINATION
bin/wasm
)
endif()
2 changes: 1 addition & 1 deletion wasm/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Module.onRuntimeInitialized = function() {

startBtn.disabled = false;

recognizer = createRecognizer();
recognizer = createRecognizer(Module);
console.log('recognizer is created!', recognizer);
};

Expand Down
Loading

0 comments on commit 1e4d5e0

Please sign in to comment.