TalkingHeadAvatar is a research implementation for a real-time, audio-driven 3D Gaussian Splatting talking head avatar. The project combines FLAME-bound Gaussian avatar rendering, VHAP-based face tracking, HuBERT audio features, a speaker-specific audio-to-motion model, streaming TTS, optional Gemma orchestration, and Linux virtual camera output.
The repository is organized as an end-to-end avatar pipeline, not as a Python package. The main integration target is run_demo.py; the training, preprocessing, and runtime pieces are kept as separate modules so they can be tested and replaced independently.
flowchart LR
video[Subject video] --> vhap[VHAP tracking]
vhap --> transforms[FLAME params + transforms]
transforms --> ga_train[GaussianAvatars training]
ga_train --> avatar_ckpt[Avatar checkpoint]
mic[Meeting audio] --> stt[Whisper STT]
typed[Typed fallback] --> orchestrator[Persona + transcript orchestrator]
stt --> orchestrator
orchestrator --> tts[Streaming TTS]
tts --> bridge[TTS audio bridge]
bridge --> hubert[HuBERT features]
hubert --> motion[MotionTranslator]
motion --> flame[FLAME expression + jaw pose]
avatar_ckpt --> renderer[Gaussian avatar renderer]
flame --> renderer
renderer --> camera[v4l2loopback virtual camera]
MeetingAudioListenercaptures 16 kHz meeting audio into rolling chunks.MeetingSpeechRecognizertranscribes chunks with a Whisper-compatible model.MeetingTranscriptstores recent participant and avatar turns.load_persona()andbuild_system_prompt()inject persona context.load_gemma()optionally loads a GGUF Gemma model throughllama-cpp-python.process_gemma_output()extractsavatar_speaktool payloads or falls back to sentence splitting.ChatterboxEnginestreams TTS audio, resampled from 24 kHz to 16 kHz.TTSAudioBridgesends 16 kHz chunks intoStreamingAudioDriver.StreamingAudioDriverruns HuBERT andMotionTranslatorto predict FLAME expression and jaw pose.AvatarRendererrenders the current FLAME-driven Gaussian avatar frame.VirtualCameraOutputsends RGB frames to/dev/video10or another v4l2loopback node.
| Path | Technical role |
|---|---|
run_demo.py |
End-to-end runtime. Parses CLI flags, initializes persona/orchestrator, audio, TTS, renderer, and virtual camera loops. |
avatar_renderer.py |
Loads a GaussianAvatars checkpoint, selects a FLAME mesh timestep, builds camera matrices, and renders idle or driven frames. |
audio_driver/audio_encoder.py |
Frozen HuBERT feature extractor. Runtime input is 16 kHz mono audio. |
audio_driver/motion_translator.py |
Transformer model mapping HuBERT features (B, T, 1024) to expression (B, T, n_expr) and jaw pose (B, T, 3). |
audio_driver/inference.py |
Streaming audio buffer, HuBERT inference, MotionTranslator inference, EMA smoothing, and latency reporting. |
orchestrator/ |
Persona loading, transcript windowing, audio listener, STT wrapper, Gemma GGUF loader, and avatar tool-call parsing. |
tts_engine/ |
Streaming TTS abstraction plus Chatterbox wrapper and TTS-to-HuBERT audio bridge. |
virtual_camera/ |
Fixed-FPS RGB frame writer backed by pyvirtualcam. Includes idle frame generation for debugging black camera feeds. |
GaussianAvatars/ |
Vendored GaussianAvatars renderer/training code and CUDA rasterizer sources. |
VHAP/ |
Vendored face tracking and FLAME parameter export tooling. |
scripts/ |
Setup, download, preprocessing, audio-driver training, monitoring, and virtual-camera helper scripts. |
tests/ |
Unit and integration tests for parsing, rendering, orchestration, audio/TTS bridge, VHAP export, and camera behavior. |
data/ |
Local subject data root. Only data/README.md is tracked. |
output/ |
Local training and rendered-output root. Only output/README.md is tracked. |
models/ |
Local model-weight root. Only models/README.md is tracked. |
audio_driver/checkpoints/ |
Local MotionTranslator checkpoint root. Only the README is tracked. |
implementation_plan.md |
Project build plan and deeper research notes. |
ARTIFACTS.md |
Local artifact inventory for this workspace. |
AvatarRenderer expects an extracted GaussianAvatars-style checkpoint directory:
output/{run_name}/
point_cloud/
iteration_{N}/
point_cloud.ply
cameras.json
cfg_args
At initialization it:
- Finds the highest
point_cloud/iteration_*directory. - Parses
sh_degreefromcfg_argsif present. - Loads
FlameGaussianModel. - Uses
cameras.jsonwhen available to preserve the trained coordinate system. - Falls back to a fixed portrait camera if
cameras.jsonis missing. - Chooses a base timestep from the saved camera entry.
- Exposes
render_idle()and audio-driven rendering throughrender(expr, jaw).
StreamingAudioDriver is the real-time audio-to-FLAME inference path.
Input contract:
audio_chunk: float32 mono audio at 16 kHz
shape: (N,) or (1, N)
Internal processing:
- Sliding window buffer: 1 second by default.
- Hop size: 320 samples, equal to 20 ms at 16 kHz.
- Feature extractor: HuBERT, output dimension
1024. - Motion model:
MotionTranslator(causal=True)for streaming use. - Output: FLAME expression vector and jaw axis-angle vector.
- Smoothing: exponential moving average with default
ema_alpha=0.3.
Output contract:
expression: (n_expr,) float32
jaw_pose: (3,) float32
The MotionTranslator is a speaker-specific Transformer regression model.
Default architecture:
input dim: 1024 HuBERT features
encoder: 4 Transformer encoder layers
attention heads: 8
feed-forward: 2048
expression head: LayerNorm + Linear -> n_expr
jaw head: LayerNorm + Linear + Tanh -> 3
loss: L1 expression + weighted L1 jaw + expression velocity loss
The model is trained from VHAP FLAME parameter sequences paired with subject audio.
ChatterboxEngine wraps Chatterbox TTS behind BaseTTSEngine.
Runtime behavior:
- Loads the model lazily on first synthesis call.
- Supports optional voice reference audio.
- Uses
generate_stream()when available. - Falls back to sentence-by-sentence generation.
- Resamples generated audio from 24 kHz to 16 kHz for HuBERT.
- Maps emotion modes to Chatterbox exaggeration:
| Emotion mode | Exaggeration |
|---|---|
neutral |
0.45 |
engaged |
0.65 |
emphatic |
0.90 |
concerned |
0.60 |
The orchestrator can run with or without Gemma.
With Gemma enabled:
load_gemma()loads a GGUF model usingllama-cpp-python.GEMMA_GGUF_PATHcan point to a local.gguffile.GEMMA_N_GPU_LAYERScontrols GPU offload.-1means all layers.GEMMA_N_CTXcontrols context length.- The model is prompted with persona + rolling transcript context.
- Tool payloads are parsed as
avatar_speak.
Without Gemma:
--skip_gemmabypasses LLM loading.--enable_stdin_fallbackcan still feed text directly to the avatar response queue.
VirtualCameraOutput writes RGB frames to a v4l2loopback device.
Default output:
device: /dev/video10
format: RGB
size: 512 x 512
fps: 30
The writer runs on a background thread. It emits an animated idle frame if no rendered frame is available, which makes camera setup problems easier to debug than a solid black output.
Recommended baseline:
| Component | Recommendation |
|---|---|
| OS | Linux with v4l2loopback support |
| Python | 3.10 or 3.11 for upstream research dependency compatibility |
| GPU | NVIDIA CUDA GPU for rendering and training |
| PyTorch | CUDA build matching the installed driver/toolkit |
| Camera | v4l2loopback and pyvirtualcam |
| Audio | sounddevice, torchaudio, librosa or soundfile |
The workspace has been tested during development on Fedora. Some research dependencies may compile more reliably on Python 3.10 than on newer Python versions.
Create an environment:
python -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pipInstall GaussianAvatars dependencies:
pip install -r GaussianAvatars/requirements.txtInstall CUDA extension modules:
pip install GaussianAvatars/submodules/diff-gaussian-rasterization
pip install GaussianAvatars/submodules/simple-knnInstall VHAP in editable mode when using its tracking/export tools:
pip install -e VHAPInstall optional LLM support:
./scripts/install_llama_cpp_python.sh
./scripts/download_gemma_gguf.sh
export GEMMA_GGUF_PATH="$PWD/models/gemma-4-e4b-it-Q4_K_M.gguf"Install optional audio/data helpers as needed:
pip install transformers soundfile librosa pyvirtualcam sounddeviceTTS requires the Chatterbox package used by tts_engine/synthesizer.py. Install the version compatible with your CUDA/PyTorch environment.
The repository does not include private subject data, generated checkpoints, downloaded LLM weights, or FLAME assets. The runtime and training scripts expect those files to be present locally.
Required by GaussianAvatars and VHAP:
GaussianAvatars/flame_model/assets/flame/
flame2023.pkl
FLAME_masks.pkl
landmark_embedding_with_eyes.npy
head_template_mesh.obj
tex_mean_painted.png
VHAP/asset/flame/
flame2023.pkl
FLAME_masks.pkl
landmark_embedding_with_eyes.npy
head_template_mesh.obj
tex_mean_painted.png
Obtain FLAME assets from the official FLAME distribution and place them in the expected directories.
Expected subject layout:
data/{subject}/
transformsVHAP/
canonical_flame_param.npz
flame_param/
000000.npz
000001.npz
...
transforms_train.json
transforms_val.json
transforms_test.json
view_000/
images/
masks/
audio_full.wav
voice_reference.wav
persona.json
audio_full.wav should be 16 kHz mono for audio-driver training. voice_reference.wav should be a clean 30 to 60 second segment for voice cloning.
run_demo.py requires --persona_file.
Minimal shape:
{
"name": "Subject Name",
"role": "Role or title",
"communication_style": "Concise, direct, technical",
"vocabulary_patterns": ["phrase one", "phrase two"],
"domain_expertise": ["topic one", "topic two"],
"known_opinions": {
"topic": "position"
},
"biographical_context": "Short background used by the orchestrator.",
"tone_guardrails": "How responses should sound."
}--avatar_ckpt must point at an extracted directory, not a .tar archive:
output/{subject_or_run}/
point_cloud/
iteration_300000/
point_cloud.ply
cameras.json
cfg_args
Expected checkpoint path for full runtime:
audio_driver/checkpoints/{subject_or_run}/best_model.pt
If this is omitted, StreamingAudioDriver creates an untrained MotionTranslator, which is useful for plumbing tests but will not produce meaningful facial motion.
Validate a subject directory and generate missing audio/persona helper files:
python scripts/preprocess_data.py \
--subject {subject} \
--video /path/to/source_video.mp4The script checks:
transformsVHAP/canonical_flame_param.npztransformsVHAP/flame_param/*.npztransforms_train.json,transforms_val.json,transforms_test.jsonview_000/imagesview_000/masksaudio_full.wavvoice_reference.wavpersona.json
Prepare a downloaded demo character:
./scripts/download_demo_character.sh
python scripts/prepare_demo_character.py \
--character biden_001 \
--forceTrain the subject-specific MotionTranslator from VHAP FLAME parameters and subject audio:
python scripts/train_audio_driver.py \
--flame_params data/{subject}/transformsVHAP/flame_param \
--audio_path data/{subject}/audio_full.wav \
--output_dir audio_driver/checkpoints/{subject} \
--epochs 100 \
--lr 1e-4 \
--batch_size 32 \
--context_frames 50Training details:
- Audio is loaded and resampled to 16 kHz.
- HuBERT features are precomputed to avoid repeated encoder passes.
- FLAME expression and jaw labels are aligned to the HuBERT feature timeline.
- The checkpoint stores model config and
state_dict.
Use this first. It validates avatar checkpoint loading and virtual camera output without importing TTS or audio-driver dependencies:
python run_demo.py \
--avatar_ckpt output/{subject_or_run} \
--persona_file data/{subject}/persona.json \
--camera_device /dev/video10 \
--resolution 512 \
--fps 30 \
--device cuda \
--video_onlyUse --dry_run to check initialization without opening the camera device:
python run_demo.py \
--avatar_ckpt output/{subject_or_run} \
--persona_file data/{subject}/persona.json \
--video_only \
--dry_runpython run_demo.py \
--avatar_ckpt output/{subject_or_run} \
--audio_driver_ckpt audio_driver/checkpoints/{subject}/best_model.pt \
--persona_file data/{subject}/persona.json \
--voice_ref data/{subject}/voice_reference.wav \
--camera_device /dev/video10 \
--resolution 512 \
--fps 30 \
--device cuda \
--enable_stdin_fallback \
--play_audio| Flag | Behavior |
|---|---|
--avatar_ckpt |
Required. Extracted avatar checkpoint directory containing point_cloud/. |
--audio_driver_ckpt |
Optional MotionTranslator checkpoint. Without it, the driver is untrained. |
--persona_file |
Required persona JSON path. |
--voice_ref |
Optional voice reference WAV for TTS voice conditioning. |
--camera_device |
v4l2loopback output node. Default: /dev/video10. |
--resolution |
Square render output resolution. Default: 512. |
--fps |
Fixed render/camera loop rate. Default: 30. |
--device |
cuda or cpu. Default: cuda. |
--video_only |
Skip TTS and audio driver imports/init. |
--dry_run |
Do not open the virtual camera device. |
--skip_gemma |
Do not load Gemma. |
--skip_audio_listener |
Do not start meeting audio capture. |
--disable_stt |
Do not run STT over captured audio. |
--stt_model |
Hugging Face ASR model id. Default: openai/whisper-tiny.en. |
--stt_language |
STT language hint. Default: en. |
--enable_stdin_fallback |
Read participant text from stdin. |
--play_audio |
Play generated TTS audio locally. |
--audio_output_device |
Optional sounddevice output device. |
--emotion_mode |
One of neutral, engaged, emphatic, concerned. |
Create or reload the v4l2loopback devices:
sudo ./scripts/setup_v4l2loopback.shRecommended for OBS reader compatibility:
sudo OBS_EXCLUSIVE_CAPS=1 AVATAR_EXCLUSIVE_CAPS=0 ./scripts/setup_v4l2loopback.shDebug the output node:
ls -l /dev/video10
fuser -v /dev/video10If another process is publishing to the same device, stop it before starting run_demo.py.
Download a GGUF model:
./scripts/download_gemma_gguf.sh Q4_K_M
export GEMMA_GGUF_PATH="$PWD/models/gemma-4-e4b-it-Q4_K_M.gguf"Optional environment variables:
export GEMMA_N_GPU_LAYERS=-1
export GEMMA_N_CTX=4096load_gemma() returns a llama_cpp.Llama instance and None for the processor. run_demo.py adapts token counting through _TranscriptTokenizerAdapter.
Run all tests:
pytestRun targeted tests:
pytest tests/test_orchestrator_transcript.py
pytest tests/test_tts_audio_bridge.py
pytest tests/test_virtual_camera_output.py
pytest tests/test_gaussian_render.pySome tests require CUDA, FLAME assets, trained checkpoints, or optional runtime packages. Use --video_only and focused tests when validating a partial environment.
- Confirm v4l2loopback is loaded.
- Confirm
/dev/video10exists. - Run
sudo ./scripts/setup_v4l2loopback.sh. - Check whether OBS or another producer is using the device with
fuser -v /dev/video10.
- Run
run_demo.py --video_only. - Confirm the log prints
[VirtualCameraOutput] Opened /dev/video10. - Reload loopback with
AVATAR_EXCLUSIVE_CAPS=0. - Make sure OBS is reading
/dev/video10, not OBS's own virtual camera output.
- Check that
torchandtorchaudioversions match. - Use
--video_onlyto isolate renderer/camera issues. - Install audio packages only after the CUDA/PyTorch environment is stable.
- Confirm
--avatar_ckptpoints to an extracted directory. - Confirm
point_cloud/iteration_*/point_cloud.plyexists. - Confirm required FLAME assets are present.
- Confirm CUDA rasterizer extensions were installed successfully.
- Check
GEMMA_GGUF_PATH. - Check that
llama-cpp-pythonwas built with CUDA if GPU offload is expected. - Reduce
GEMMA_N_GPU_LAYERSif VRAM is tight.
The repository tracks source code, tests, scripts, and small documentation files. Runtime assets remain local:
data/
output/
models/
audio_driver/checkpoints/
GaussianAvatars/flame_model/assets/flame/
VHAP/asset/flame/
Do not commit subject videos, voice references, trained identity checkpoints, downloaded model weights, generated TensorBoard logs, extracted frame datasets, or private biometric assets. Use a separate artifact store for those files.
This repository vendors third-party research code. Keep their license files intact:
GaussianAvatars/LICENSE.mdGaussianAvatars/LICENSE_GS.mdVHAP/LICENSE
FLAME assets are not included and must be obtained under the FLAME license terms. Before publishing original code under a specific license, add a root LICENSE file and verify compatibility with the vendored components.
Implemented:
- End-to-end runtime scaffold in
run_demo.py. - GaussianAvatars checkpoint loading and frame rendering wrapper.
- Streaming audio driver and MotionTranslator model.
- Chatterbox TTS wrapper and audio bridge.
- Gemma GGUF loader and avatar tool-call parser.
- v4l2loopback virtual camera output.
- Dataset/artifact directory contracts.
- Regression tests for core glue modules.
Still project-specific:
- Actual avatar quality depends on locally trained checkpoints.
- Audio-driven motion quality depends on subject-specific MotionTranslator training.
- Full meeting behavior depends on local STT/TTS/Gemma dependencies and available VRAM.
- FLAME assets and subject datasets must be supplied by the user.