ASCII video chat.
Probably the first command line video chat program.
It just prints ASCII, so it works on your rxvt-unicode in OpenBox, a Putty SSH session, and even iTerm or Kitty.app on macOS.
It even works in an initial UNIX login shell, i.e. the login shell that runs 'startx'.
π Now 3+ simultaneous people can connect and the server will render the clients to each other as a grid, like Google Hangouts and Zoom calls do.
π Audio streaming is now supported via PortAudio, with a custom mixer with features like compression, ducking, crowd scaling, noise gating, hi/lo-pass filtering, and soft clipping.
Update: OpenCV is no longer required! The project now uses β¨ native platform APIs πͺ:
- Linux: V4L2 (Video4Linux2)
- macOS: AVFoundation
- Windows: Media Foundation
- Ubuntu/Debian:
apt-get install build-essential clang cmake ninja-build musl-tools musl-dev libmimalloc-dev libv4l-dev zlib1g-dev portaudio19-dev libsodium-dev libcriterion-dev
- Arch:
pacman -S pkg-config clang cmake ninja musl mimalloc v4l-utils zlib portaudio libsodium criterion
brew install cmake ninja zlib portaudio libsodium criterion
-
Install Scoop (if not already installed):
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser irm get.scoop.sh | iex
-
Install build tools via Scoop:
scoop install cmake ninja llvm
-
Install Windows SDK:
- Download and install Visual Studio Build Tools
- Or install via Scoop:
scoop install windows-sdk-10-version-2004
-
Install dependencies via vcpkg:
# Install vcpkg (if not already installed) git clone https://github.com/Microsoft/vcpkg.git cd vcpkg .\bootstrap-vcpkg.bat # Install required packages .\vcpkg install zlib:x64-windows portaudio:x64-windows libsodium:x64-windows
./tests/scripts/run-docker-tests.ps1
.
- Clone this repo onto a computer with a webcam and
cd
to its directory. - Install the dependencies for your OS (instructions listed above).
- Run
cmake --preset default && cmake --build --preset default
. - Run
./build/bin/ascii-chat-server
. - Open a second terminal window, tab, split, or pane. Or go to another computer.
- Run
./build/bin/ascii-chat-client
. - π― Optional: open more terminals and run more clients! ascii-chat is multiplayer π’. They'll all connect and show in a grid. On macOS you can just open multiple terminals and run
ascii-chat-client
in each one. On Windows and Linux computers only one program can use a webcam at a time, so use multiple computers to test connecting multiple clients to the server (call a friend).
Check the CMakeLists.txt
to see how it works.
default
/debug
- Debug build with AddressSanitizer (slowest, catches most bugs)dev
- Debug symbols without sanitizers (faster iteration)coverage
- Build with coverage instrumentation
-
release
- Optimized static release build with musl + mimalloc (Linux deployment)- Produces stripped static binaries (~700KB) using musl libc
- Best for Linux production deployment - single binary, no dependencies
- Uses mimalloc for optimal memory performance
-
release-clang
- Optimized dynamic release build with clang + mimalloc (Windows/macOS)- Produces stripped dynamic binaries (~200KB) using system libc
- Best for Windows/macOS where musl isn't available
- Uses mimalloc for optimal memory performance
-
release-musl
- Alias forrelease
(static musl build)
relwithdebinfo
- Optimized build with debug symbols (for profiling and debugging production issues)- Optimized with
-O2
but keeps debug symbols (not stripped, ~1.3MB) - Use with
gdb
,lldb
,perf
,valgrind
for profiling - Uses clang + glibc for best debugging experience
- Includes mimalloc for realistic performance profiling
- Optimized with
# Development (default)
cmake --preset default && cmake --build build
# Production release (Linux static binary)
cmake --preset release && cmake --build build
# Production release (Windows/macOS dynamic binary)
cmake --preset release-clang && cmake --build build
# Profiling/debugging production issues
cmake --preset relwithdebinfo && cmake --build build
# Clean rebuild
rm -rf build
cmake --preset release && cmake --build build
musl libc: A lightweight, fast, and simple C standard library alternative to glibc. The release
preset uses musl to create statically linked binaries that have no external dependencies - perfect for deployment as they work on any Linux system without requiring specific libraries to be installed.
mimalloc: Microsoft's high-performance memory allocator. All release and profiling builds use mimalloc instead of the system allocator for better performance. It provides:
- Up to 2x faster allocation/deallocation
- Better memory locality and cache performance
- Lower memory fragmentation
- Optimized for multi-threaded workloads
cmake --build --preset debug --target format
- Format source code using clang-formatcmake --build --preset debug --target format-check
- Check code formattingcmake --build --preset debug --target clang-tidy
- Run clang-tidy on sources
CMake supports several configuration options:
-DCMAKE_C_COMPILER=clang
- Set compiler (default: auto-detected)-DSIMD_MODE=auto
- SIMD mode: auto, sse2, ssse3, avx2, avx512, neon, sve (default: auto)-DCRC32_HW=auto
- CRC32 hardware acceleration: auto, on, off (default: auto)- Ninja automatically uses all available CPU cores for parallel builds
The project uses a unified test runner script at tests/scripts/run_tests.sh
that consolidates all test execution logic. It accepts all sorts of arguments and auto-builds the test executables it's gonna run beforehand with ninja, which is convenient because it allows you to simply iterate on code and then run this script, going between those two things.
- Have the dependencies installed.
- Choose:
- Linux or macOS: run test runner script:
./tests/scripts/run_tests.sh
- Windows: use Docker:
./tests/scripts/run-docker-tests.ps1
(just callsrun_tests.sh
in a container)
- Linux or macOS: run test runner script:
- Unit Tests: Test individual components in isolation
- Integration Tests: Test component interactions and full workflows
- Performance Tests: Benchmark stuff like SIMD vs scalar implementations
# Run all tests in debug mode
./tests/scripts/run_tests.sh
# Run specific test types
./tests/scripts/run_tests.sh -t unit
./tests/scripts/run_tests.sh -t integration
./tests/scripts/run_tests.sh -t performance
# Run with different build configurations
./tests/scripts/run_tests.sh -b debug
./tests/scripts/run_tests.sh -b release
./tests/scripts/run_tests.sh -b debug-coverage
./tests/scripts/run_tests.sh -b release-coverage
# Generate JUnit XML for CI
./tests/scripts/run_tests.sh -J
# Run in parallel (default: number of CPU cores)
./tests/scripts/run_tests.sh -j 4
# Verbose output
./tests/scripts/run_tests.sh -v
On Windows, since Criterion is POSIX-based, tests must be run in a Docker container. Use the PowerShell wrapper script:
# Run all tests
./tests/scripts/run-docker-tests.ps1
# Run specific test types
./tests/scripts/run-docker-tests.ps1 unit
./tests/scripts/run-docker-tests.ps1 integration
./tests/scripts/run-docker-tests.ps1 performance
# Run specific tests
./tests/scripts/run-docker-tests.ps1 unit options
./tests/scripts/run-docker-tests.ps1 unit buffer_pool packet_queue
# Run with verbose output
./tests/scripts/run-docker-tests.ps1 unit options -VerboseOutput
# Run with different build types
./tests/scripts/run-docker-tests.ps1 unit -BuildType release
# Run clang-tidy static analysis
./tests/scripts/run-docker-tests.ps1 clang-tidy
./tests/scripts/run-docker-tests.ps1 clang-tidy lib/common.c
# Interactive shell for debugging
./tests/scripts/run-docker-tests.ps1 -Interactive
The Docker script automatically:
- Builds the test container if needed
- Mounts your source code for live testing
- Handles incremental builds
- Provides the same test interface as the native script
You can also run individual test executables directly:
# Build the project first
cmake --preset debug && cmake --build --preset debug
# Run individual tests
build/bin/test_unit_mixer --verbose
build/bin/test_performance_ascii_simd --filter "*monochrome*"
- Framework: libcriterion
- Coverage: Code coverage reports generated in CI
- Performance: SIMD performance tests with aggressive speedup expectations (1-4x)
- Memory Checking: Comprehensive sanitizer support via
-b debug
for detecting memory issues, undefined behavior, and more
π΄
Good news though: we have libsodium installed and some code written for it.
π TODO: Implement crypto.
Run ./bin/ascii-chat-client -h
to see all client options:
-a --address ADDRESS
: IPv4 address to connect to (default: 0.0.0.0)-p --port PORT
: TCP port (default: 27224)-x --width WIDTH
: Render width (auto-detected by default)-y --height HEIGHT
: Render height (auto-detected by default)-c --webcam-index INDEX
: Webcam device index (default: 0)-f --webcam-flip
: Horizontally flip webcam (default: enabled)--color-mode MODE
: Color modes: auto, mono, 16, 256, truecolor (default: auto)--show-capabilities
: Display terminal color capabilities and exit--utf8
: Force enable UTF-8/Unicode support-M --background-mode MODE
: Render colors for glyphs or cells: foreground, background (default: foreground)-A --audio
: Enable audio capture and playback-s --stretch
: Stretch video to fit without preserving aspect ratio-q --quiet
: Disable console logging (logs only to file)-S --snapshot
: Capture one frame and exit (useful for testing)-D --snapshot-delay SECONDS
: Delay before snapshot in seconds (default: 3.0/5.0)-L --log-file FILE
: Redirect logs to file-E --encrypt
: Enable AES encryption-K --key PASSWORD
: Encryption password-F --keyfile FILE
: Read encryption key from file-h --help
: Show help message
Run ./bin/ascii-chat-server -h
to see all server options:
-a --address ADDRESS
: IPv4 address to bind to (default: 0.0.0.0)-p --port PORT
: TCP port to listen on (default: 27224)-A --audio
: Enable audio mixing and streaming-L --log-file FILE
: Redirect logs to file-E --encrypt
: Enable AES encryption-K --key PASSWORD
: Encryption password-F --keyfile FILE
: Read encryption key from file-h --help
: Show help message
Start the server and wait for client connections:
./bin/ascii-chat-server [options]
Start the client and connect to a running server:
./bin/ascii-chat-client [options]
- Audio.
- Client should continuously attempt to reconnect
- switch Client "-a/--address" option to "host" and make it accept domains as well as ipv4
- Colorize ASCII output
- Refactor image processing algorithms
- client reconnect logic
- terminal resize events
- A nice protocol for the thing (packets and headers).
- client requests a frame size
- Client should gracefully handle
frame width > term width
- Client should gracefully handle
term resize
event - Compile to WASM/WASI and run in the browser
- Socket multiplexing.
- Edge detection and other things like that to make the image nicer.
- Multiple clients. Grid to display them.
- Snapshot mode for clients with --snapshot to "take a photo" of a call and print it to the terminal or a file, rather than rendering video for a long time.
- Audio mixing for multiple clients with compression and ducking.
- Color filters so you can pick a color for all the ascii so it can look like the matrix when you pick green (Gurpreet suggested).
- Lock-free packet send queues.
- Hardware-accelerated ASCII-conversion via SIMD.
-
Note: Colored frames are many times larger than monochrome frames due to the ANSI color codes.
-
We don't really save bandwidth by sending color ascii video. I did the math with Claude Code.