diff --git a/examples/web/README.md b/examples/web/README.md new file mode 100644 index 0000000..2f9c21b --- /dev/null +++ b/examples/web/README.md @@ -0,0 +1,15 @@ +# Examples + +This folder contains examples of using openWakeWord with web applications. + +## Websocket Streaming + +As openWakeWord does not have a native Javascript port, using it within a web browswer is best accomplished with websocket streaming of the audio data from the browser to a simple Python application. To install the requirements for this example: + +``` +pip install aiohttp +``` + +The `streaming_client.html` page shows a simple implementation of audio capture and streamimng from a microphone and streaming in a browser, and the `streaming_server.py` file is the corresponding websocket server that passes the audio into openWakeWord. + +To run the example, execute `python streaming_server.py` (add the `--help` argument to see options) and navigate to `localhost:9000` in your browser. \ No newline at end of file diff --git a/examples/web/streaming_client.html b/examples/web/streaming_client.html new file mode 100644 index 0000000..3bf5005 --- /dev/null +++ b/examples/web/streaming_client.html @@ -0,0 +1,112 @@ + + + + + + + Websocket Microphone Streaming + + +

Streaming Audio to openWakeWord Using Websockets

+ + + + + \ No newline at end of file diff --git a/examples/web/streaming_server.py b/examples/web/streaming_server.py new file mode 100644 index 0000000..c386bbe --- /dev/null +++ b/examples/web/streaming_server.py @@ -0,0 +1,108 @@ +# Copyright 2023 David Scripka. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +####################################################################################### + +# This example scripts runs openWakeWord in a simple web server receiving audio +# from a web page using websockets. + +####################################################################################### + +# Imports +import aiohttp +from aiohttp import web +import numpy as np +from openwakeword import Model +import resampy +import argparse + +# Define websocket handler +async def websocket_handler(request): + ws = web.WebSocketResponse() + await ws.prepare(request) + + # Start listening for websocket messages + async for msg in ws: + # Get the sample rate of the microphone from the browser + if msg.type == aiohttp.WSMsgType.TEXT: + sample_rate = int(msg.data) + elif msg.type == aiohttp.WSMsgType.ERROR: + print(f"WebSocket error: {ws.exception()}") + else: + # Get audio data from websocket + audio_bytes = msg.data + + # Add extra bytes of silence if needed + if len(msg.data) % 2 == 1: + audio_bytes += (b'\x00') + + # Convert audio to correct format and sample rate + data = np.frombuffer(audio_bytes, dtype=np.int16) + if sample_rate != 16000: + data = resampy.resample(data, sample_rate, 16000) + + # Get openWakeWord predictions and set to browser client + predictions = owwModel.predict(data) + + activations = [] + for key in predictions: + if predictions[key] >= 0.5: + activations.append(key) + + if activations != []: + await ws.send_str(str(activations)) + + return ws + +# Define static file handler +async def static_file_handler(request): + return web.FileResponse('./streaming_client.html') + +app = web.Application() +app.add_routes([web.get('/ws', websocket_handler), web.get('/', static_file_handler)]) + +if __name__ == '__main__': + # Parse CLI arguments + parser=argparse.ArgumentParser() + parser.add_argument( + "--chunk_size", + help="How much audio (in number of samples) to predict on at once", + type=int, + default=1280, + required=False + ) + parser.add_argument( + "--model_path", + help="The path of a specific model to load", + type=str, + default="", + required=False + ) + parser.add_argument( + "--inference_framework", + help="The inference framework to use (either 'onnx' or 'tflite'", + type=str, + default='tflite', + required=False + ) + args=parser.parse_args() + + # Load openWakeWord models + if args.model_path != "": + owwModel = Model(wakeword_models=[args.model_path], inference_framework=args.inference_framework) + else: + owwModel = Model(inference_framework=args.inference_framework) + + # Start webapp + web.run_app(app, host='localhost', port=9000) \ No newline at end of file