System to anonymize audio and video in real-time and locally. With ReadyPlayerMe's avatar and Mimi-VC, our own real-time voice converter based on Kyutai's speech tokenizer, it runs with a latency of 0.13 seconds in my Macbook pro M1. RAVAS can also be used to anonymize videos, emulating the real-time scenario, to perform experiments. We built this at DFKI for the AnonymPrevent and VERANDA research projects.
You can read more technical details in this blog post.
ravas_demo.mp4
Here are the most important contributions by the three colleagues that have helped me develop RAVAS, in chronological order. They have all done much more than what is stated here, these are just the highlights. Huge thanks to them for their effort.
- @phipi-a: implemented the first version of the avatar the multi-threaded and sync-preserving architecture.
- @russaa: implemented the Windows packaging, the GUI and a faster and more robust version of the avatar.
- @HuangJ98: implemented chunking strategies for kNN-VC and private kNN-VC.
@inproceedings{franzreb24_spsc,
title = {Towards Audiovisual Anonymization for Remote Psychotherapy: a Subjective Evaluation},
author = {Carlos Franzreb and Arnab Das and Hannes Gieseler and Eva Charlotte Jahn and Tim Polzehl and Sebastian Möller},
year = {2024},
booktitle = {4th Symposium on Security and Privacy in Speech Communication},
pages = {102--110},
doi = {10.21437/SPSC.2024-17},
}TOC
- How to use the implementation
- Project Structure
- Installation guide
- Build Executable
- Usage
- Avatar Anonymizer
- Development
An example is provided in the ./example folder. The general workflow is as follows:
- Define the
init_callbackfunction for audio or video, where you can initialize all objects required for thecallbackfunction. Return these objects to use them in the callback. - Define the final
callbackfunction, which receives the data, corresponding timestamps, and the list of objects returned from theinit_callbackfunction. - Create an
AudioVideoStreamerobject and set all parameters like the sampling rate, audio/video device, etc., along with theinit_callbackand thecallbackfunction. - Start the streamer with the
start()function. - Wait for a KeyboardInterrupt or some other input.
- Stop the streamer with the
stop()function.
The project is structured as follows:
- Processor.py
Processor: Abstract class providing basic functionality to process data, such as synchronization (sync) and callback function handling (process). Theread_input_streamandwrite_output_streamfunctions are abstract and must be implemented in the child class. Since each function will be started in a new process, the class is only allowed to have attributes that are pickable.ProcessingQueues: Provides all necessary multiprocessing queues for theProcessorclass.ProcessingSyncState: Provides all necessary multiprocessing values to synchronize different processes.ProcessorProcessHandler: Has only two functions used to start and stop the functions of theProcessorclass in a new process.
- AudioProcessor.py
AudioProcessor: Extends theProcessorclass and provides all necessary functions to process audio data. Because each function will be started in a new process, the class is only allowed to have attributes that are pickable.
- VideoProcessor.py
VideoProcessor: Extends theProcessorclass and provides all necessary functions to process video data. Because each function will be started in a new process, the class is only allowed to have attributes that are pickable.
- AudioVideoStreamer.py
AudioVideoStreamer: Provides an API to combine theAudioProcessorandVideoProcessorclasses and share their sync state.
In the AudioVideoStreamer class, an AudioProcessor, VideoProcessor, ProcessingSyncState, and ProcessingQueues object
is created for each processor. The queues are used in the corresponding processor to exchange data between the processes.
The ProcessingSyncState object is used to synchronize the different processors (AudioProcessor and VideoProcessor).
A ProcessorProcessHandler object is used to start and stop the processes of the AudioProcessor and VideoProcessor class.
When calling the start function of the handler, each function of the Processor gets called in its own process
(read, process, sync, write).
The different processes communicate through the multiprocessing queues and synchronize through the multiprocessing values
provided in the sync state. Due to multiprocessing, the Processor class is only allowed to have attributes that are pickable.
Therefore, the callback and init_callback functions must be defined outside the main function (see example).
To reduce latency between the streams, the data is converted into torch tensors. Torch tensors are stored in shared memory and can be accessed by all processes through the queues without copying the data. For numpy arrays, the data has to be pickled and unpickled to be sent through the queues, which is very slow.
MacOS
Before installing RAVAS, you need to install some system packages:
brew is needed to install system packages. According to their website, you can install it by running
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"in your terminal.
Afterwards, you need to restart the terminal.
You can check if it was correctly installed by running brew --version.
If it wasn't, please check their list of issues.
Once brew is installed, you can install the two system packages required by RAVAS to process audio and create virtual microphones:
brew install portaudio
brew install blackhole-2chAfterwards, blackhole-2ch should appear in your list of microphones, under "Sound" in the settings.
You may need to restart your laptop for it to appear.
conda is a package and environment manager for Python. We need it to create an environment in which to install RAVAS and its dependencies.
You can install it following the instructions on their website.
Once it is installed, check that it works by running conda --version in a terminal.
Chrome is needed to render the avatar. It should be visible under your laptop's Applications, for the script below to install the Chrome extension that renders the avatar.
Once everything above is installed, the three lines below download the code from here, and install it with the script macos_install.sh:
git clone https://github.com/carlosfranzreb/ravas.git
cd ravas
bash ./macos_install.shIf you encounter any issues while installing RAVAS, you can write me an e-mail to carlos.franzreb@dfki.de or open an issue.
If you have trouble installing the other dependencies, please contact their maintainers or use Google or an AI to solve your issues. These are popular tools; you are likely not the first to encounter any particular problem. In my experience, restarting the terminal or the laptop solves many issues, so that is worth a try.
Once you have correctly installed everything and got it to run, the next time you want to use RAVAS you only need to execute three commands on the terminal, at the root directory where RAVAS is installed:
conda activate ./venv
cd ravas
python -m run_guiI've written these three commands in the script macos_run.sh, so you can also just run that script as shown below. If you are encountering a conda error, asking you to run conda init, run the three commands from above instead.
bash ./macos_run.shFor building / packaging the python application as a standalone executable see ravas/BUILD.md.
for starting the main app as a console program, change to directory ravas/ and run
python -m run_mainYou can specify a configration file via --config <file>, see ravas/configs for configuration examples.
for starting the main app with a GUI, change to directory ravas/ and run
python -m run_guiSee USAGE.md for more usage information for the GUI.
The avatar anonymizer is composed of the mediapipe's face-landmarker solution for detecting face expressions and head movement
from the camera / video images, and then using the result to render an avatar model (in *.glb format: binary glTF 2.0 format).
The avatar is now rendered with the new OpenGL renderer, using the moderngl and moderngl-window wrappers for OpenGL in python, see ravas/stream_processing/models/avatar/opengl_runner.py and ravas/stream_processing/models/avatar/opengl/*.
Note that to avoid unnecessary duplication, the renderer currently uses the web app's avatar model files in rpm/dist/chrome-extension/*.glb or rpm/public/*.glb
Previously, we used a React rendered based on this repository. The new one is faster and we have since removed the old renderer; check the commits if you want to try it out.
The basic configuration for the avatar renderer via the YAML configuration files (as video converter) are
video:
# ...
converter:
cls: stream_processing.models.Avatar
# select renderer for avatar: opengl (DEFAULT) | browser
avatar_renderer: opengl
# if TRUE, will show the rendering app window (e.g. for DEBUG purposes); for browser renderer, will show the browser window
show_renderer_window: false
# the avatar model to be used for rendering (see `rpm/public/*.glb`
# * the value `default_avatar.glb` will be mapped to the first available / default avatar
# * for the browser renderer, the values should be file names or relative to the immediate parent directory
# (i.e. no absolute file paths); the opengl renderer can also handle absolute file paths
avatar_uri: ./default_avatar.glb
# OPTIONAL store mediapipe's detection results for facial expression / head movement to an array in a JSON file in the log-dir:
# NOTE that the last entry in the array will be an empty object, i.e. without "blendshapes" field!
detection_log: detectionLog.json
# for browser renderer: if TRUE, start Chrome Browser automatically via Selenium webdriver
start_chrome_renderer: true
# for browser renderer: if TRUE, use Chrome Web Extension, if FALSE start a web server for serving web app as a website
use_chrome_extension: trueThe python based avatar renderer is implemented using the Python moderngl wrapper for OpenGL and running the
renderer in a moderngl-window.
TODO: currently the implementation uses the default
pygletintegration of moderngl-window. There is also aPyQT5integration in moderngl-window: whenPyQT6integration becomes available, we should switch to that, so that we use the same window-system as the main app's GUI.
For testing and debugging, the python-based avatar renderer can be started in standalone mode with
python -m stream_processing.models.avatar.opengl_runnerThere are several commend line options available (use --help), e.g. for selecting a specific avatar model (-a / --avatar).
It is also possible to "play back" previously recorded face expression & head movements ("blendshapes"):
-
configure the main app, to record
mediapipe's face-landmarker results in a JSON filevideo: # ... converter: cls: stream_processing.models.Avatar # ... # store mediapipe's detection results for facial expression / head movement to an array in a JSON file in the log-dir: # NOTE that the last entry in the array will be an empty object, i.e. without "blendshapes" field! detection_log: detectionLog.json
-
run the main app and store the detection results
-
find the log-dir where the detection results were stored and run the
pythonavatar renderer with the argument-b/--blendshapes:python -m stream_processing.models.avatar.opengl_runner -b <path to recorded detection results in log-directory>
The avatars are stored in a GLB files in the rpm/public/ folder, and are used by the default renderer as well as the
legacy web-based renderer (i.e. in the React app (rpm/src/App.tsx)).
The naming scheme for the avatar files is avatar_<number>_<gender: f | m>.glb
If you want to change or add avatars:
- create an avatar on the Ready Player Me website (currently this is free; you do not need an account either)
- get the download ID / link for created avatar, e.g. something like https://models.readyplayer.me/6460d95f9ae10f45bffb2864.glb
- IMPORTANT the avatar model files must include morph target definitions for
ARKit(i.e.morphTargets=ARKit, see Ready Player Me REST API docs), and the texture quality should be set to high (i.e.quality=highortextureAtlas=1024): add these query-parameters to the download link for the avatar, e.g. https://models.readyplayer.me/6460d95f9ae10f45bffb2864.glb?morphTargets=ARKit&textureAtlas=1024 - rename the
*.glbavatar file (see naming scheme above) and place it in the project folderrpm/public/ - Make sure to also change the variable
use_chrome_extensioninravas/configs/onnx_models_ui.yamlfromTrueto the path e.g./rpm/public/ - If you want to use chrome extension keep it as
Trueand repack the chrome_extension the guide is mentioned in step 7 - (Optional) you should also rebuild the web app for the avatar rendering (see rpm/README.md)
We currently support 2 Anonymizers: KnnVC and MimiVC.
Since the input and output are constrained by ONNX, the additional context must be included in the total processing size. The currently available total processing sizes are 3200, 4800, and 9600. There are two ways to enable this option and adjust the previous context size:
-
inside the GUI under the Advanced Settings
-
in the config files manually
audio: # ... converter: # ... prev_ctx: use_previous_ctx: false max_samples: 0
In the pre-release v0.6, the checkpoints include an additional 320-sample input, which is only required for the default computation to compensate for feature loss during WavLM. If you are already using a larger previous context, you can ignore the WavLM loss.
A short guide on how to add a new setting (i.e. a configuration item from the *.yaml based config) in the GUI
in ravas/stream_processing/gui/.
The current implementation uses ConfigurationItems to represent configuration properties in the GUI. They define
- the "path" to the configuration property within the YAML configuration (e.g. something like
["video", "use_video"]) - specify the allowed configuration values:
these (plus, if necessary, additional helper functions) allow to validate the current configuration, and thus allow feedback to users in case there is a misconfiguration.
To add a new setting in the GUI:
-
New Item: create a new
ConfigurationItemin gui/config_items.py: add this to the module constantCONFIG_ITEMS. -
Validation: if the new setting's valid value validation depends on other settings:
- e.g. a setting that is only relevant in case the avatar video-converter is selected, or if audio processing is enabled,
then add a
is_ignore_validation(current_config: Dict) -> boolhelper function to the configuration item. - As example, see
_do_set_ignore_validation_helpers()where this is done for several item defined inCONFIG_ITEMS(you may modify this function to add your own adjustments).
- e.g. a setting that is only relevant in case the avatar video-converter is selected, or if audio processing is enabled,
then add a
-
Add to GUI: then add a GUI widget for the new item to
ConfigDialogin gui/config_dlg.py- currently, this is done in the class' construction
__init__() - the class provides several helper functions to create some default widgets
_createCheckBoxFor(..): for creating a check-box widget (usually used for boolean settings)_createComboBoxFor(..): for creating a combo-box widget (usually used for settings with a list of valid values)- ... as well as some more intricate widgets like
_createSliderFor(..)for creating slider control for number settings
- NOTE that the GUI somewhat duplicates the validation logic in order to enable/disable configuration item widgets:
see for example local functions
_updateAvatarEnabled(),_updateAvatarRendererSelected(),_set_audio_widgets_enabled(), and_set_video_widgets_enabled()
- currently, this is done in the class' construction