How scrcpy Implements the V4L2 Sink for Linux Webcam Mirroring
Scrcpy implements the V4L2 sink by streaming decoded Android screen frames to a Video4Linux loopback device using FFmpeg's raw video encoder, enabling any v4l2-compatible application to use the device as a virtual webcam.
The Genymobile/scrcpy project provides a sophisticated scrcpy V4L2 sink implementation that bridges Android screen capture with Linux webcam infrastructure. This feature allows users to present their Android device screen as a standard video device node (e.g., /dev/video2) that applications like OBS Studio, Chrome, or VLC can consume directly.
Architecture of the scrcpy V4L2 Sink
Core Data Structures (sc_v4l2_sink)
The implementation centers around the sc_v4l2_sink structure defined in app/src/v4l2_sink.h. This struct encapsulates all V4L2-specific state:
- Device path: Target loopback device (e.g.,
/dev/video0) - FFmpeg contexts:
AVFormatContextfor the v4l2 muxer,AVCodecContextfor the raw video encoder - Synchronization primitives:
sc_mutexandsc_condfor thread-safe frame buffering - Frame buffer: Lock-free queue (
sc_frame_buffer) between decoder and sink thread
Frame-Sink Trait Interface
Scrcpy abstracts video outputs through a generic frame-sink trait (sc_frame_sink). The V4L2 sink registers its operations via sc_v4l2_frame_sink_ops, implementing:
open: Initializes FFmpeg muxer and encoderclose: Signals thread shutdown and releases resourcespush: Enqueues decoded frames for the worker thread
This design isolates V4L2-specific logic in app/src/v4l2_sink.c while allowing the main video pipeline in app/src/video.c to remain agnostic of the output type.
FFmpeg Integration
The sink leverages FFmpeg's v4l2 muxer to treat the loopback device as an output format. Key technical details:
- Muxer identification: Uses
av_guess_format("v4l2", NULL, NULL)to locate the v4l2 output driver - Raw video encoding: Configures
AV_CODEC_ID_RAWVIDEOwithAV_PIX_FMT_YUV420Pto match scrcpy's decoded frame format - Header handling: Stores the first encoded packet as
extradatain the video stream to satisfy V4L2 driver header requirements
Step-by-Step Implementation Flow
1. Initialization and Device Setup
When scrcpy launches with --v4l2-sink=/dev/videoN, the following occurs:
- The CLI parser validates the device path
sc_v4l2_sink_init()allocates the sink structure and copies the device string- The sink is registered with the video pipeline via
sc_video_set_sink()
2. Opening the V4L2 Sink
The sc_v4l2_sink_open() function in app/src/v4l2_sink.c (lines 68-120) performs the heavy lifting:
// Locate the v4l2 muxer
const AVOutputFormat *format = av_guess_format("v4l2", NULL, NULL);
// Allocate output context
AVFormatContext *format_ctx;
avformat_alloc_output_context2(&format_ctx, format, NULL, device_path);
// Open the device for writing
avio_open(&format_ctx->pb, device_path, AVIO_FLAG_WRITE);
It then initializes the raw video encoder with parameters matching the source:
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_RAWVIDEO);
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_ctx->width = width;
codec_ctx->height = height;
avcodec_open2(codec_ctx, codec, NULL);
3. Feeding Frames to the Buffer
When the decoder produces a new frame, sc_v4l2_sink_push() enqueues it into the lock-free sc_frame_buffer. If the buffer transitions from empty to non-empty, it signals the worker thread via sc_cond_signal().
4. Worker Thread Processing
The run_v4l2_sink() thread (lines 14-46 in v4l2_sink.c) operates asynchronously:
- Wait for frames: Blocks on
sc_cond_wait()until signaled - Retrieve frame: Pops from
sc_frame_bufferusingsc_frame_buffer_take() - Encode: Calls
avcodec_send_frame()followed byavcodec_receive_packet() - Header handling (first packet only): In
write_header()(lines 34-57), copies packet data tostream->codecpar->extradataand writes the format header viaavformat_write_header() - Write packet: Sends encoded data to the v4l2 device with
av_write_frame() - Cleanup: Releases packet and frame resources
5. Shutdown and Cleanup
When the session ends:
sc_v4l2_sink_close()sets thestoppedatomic flag- Signals the condition variable to wake the worker
- Joins the thread with
sc_thread_join() - Flushes the encoder with
avcodec_send_frame(NULL)to retrieve remaining packets - Writes the trailer with
av_write_trailer() - Closes the AVIO context with
avio_close() - Frees all FFmpeg contexts and synchronization objects
Practical Usage Examples
Command-Line Setup with v4l2loopback
To use the scrcpy V4L2 sink, you must first install the kernel module:
# Install the v4l2loopback driver
sudo apt install v4l2loopback-dkms
sudo modprobe v4l2loopback
# Verify the device was created
ls /dev/video*
Then start scrcpy with the sink enabled:
# Stream to /dev/video2 without displaying locally
scrcpy --v4l2-sink=/dev/video2 --no-video-playback
# Or use a specific device name for clarity
scrcpy --v4l2-sink=/dev/video0
Configuring Buffer Latency
Scrcpy provides a configurable buffering delay to balance latency against frame drops:
# Set 300ms buffer (default may vary by version)
scrcpy --v4l2-sink=/dev/video0 --v4l2-buffer=300
The underlying implementation adjusts the sc_frame_buffer capacity in app/src/v4l2_sink.c based on this parameter.
C API Integration Example
For developers integrating scrcpy as a library, the V4L2 sink follows the standard frame-sink pattern:
#include "v4l2_sink.h"
// Initialize the sink structure
struct sc_v4l2_sink v4l2_sink;
if (!sc_v4l2_sink_init(&v4l2_sink, "/dev/video2")) {
fprintf(stderr, "Failed to initialize V4L2 sink\n");
return -1;
}
// Open with the decoder context (AVCodecContext*)
if (!sc_v4l2_sink_open(&v4l2_sink, decoder_ctx)) {
fprintf(stderr, "Failed to open V4L2 device\n");
return -1;
}
// Push decoded frames (AVFrame*)
sc_v4l2_sink_push(&v4l2_sink, frame);
// Cleanup when done
sc_v4l2_sink_close(&v4l2_sink);
sc_v4l2_sink_destroy(&v4l2_sink);
This mirrors the internal flow used by scrcpy's video pipeline in app/src/video.c.
Key Source Files
The scrcpy V4L2 sink implementation spans these critical files in the Genymobile/scrcpy repository:
-
app/src/v4l2_sink.h— Defines thesc_v4l2_sinkstructure and the public API includingsc_v4l2_sink_init(),sc_v4l2_sink_open(), andsc_v4l2_sink_push(). -
app/src/v4l2_sink.c— Contains the full implementation including therun_v4l2_sink()worker thread, FFmpeg muxer setup, raw video encoding, and header handling logic. -
app/src/frame_sink.handapp/src/frame_sink.c— Define the genericsc_frame_sinktrait that the V4L2 sink implements, providing abstraction for the video pipeline. -
app/src/video.c— Orchestrates the video pipeline and connects the decoder output to the selected frame sink viasc_video_set_sink(). -
doc/v4l2.md— User-facing documentation explaining how to enable and configure the V4L2 sink feature.
Summary
- Scrcpy's V4L2 sink turns an Android screen stream into a standard Linux video device using FFmpeg's v4l2 muxer and raw video encoding.
- The implementation resides primarily in
app/src/v4l2_sink.cand uses a dedicated worker thread (run_v4l2_sink) to asynchronously encode and write frames. - Frame-sink abstraction allows the V4L2 output to plug into scrcpy's generic video pipeline alongside other sinks like SDL or file recording.
- The system requires the v4l2loopback kernel module to create the virtual video device node that applications recognize as a webcam.
- Users control latency through the
--v4l2-bufferoption, which adjusts the internalsc_frame_buffercapacity.
Frequently Asked Questions
What kernel module is required for scrcpy V4L2 sink functionality?
The v4l2loopback kernel module is required to create virtual video device nodes. Install it via your distribution's package manager (e.g., sudo apt install v4l2loopback-dkms on Debian/Ubuntu), then load it with sudo modprobe v4l2loopback. This creates /dev/videoN devices that scrcpy can write to using the --v4l2-sink option.
Does the V4L2 sink support audio streaming?
No, the V4L2 sink implementation in app/src/v4l2_sink.c handles video frames only. It uses FFmpeg's raw video encoder (AV_CODEC_ID_RAWVIDEO) to stream YUV420P frames to the video device node. Audio is handled separately through scrcpy's audio forwarding system (via --audio-dup or --audio-output), which does not route through the V4L2 sink.
What video format does scrcpy use for the V4L2 output?
Scrcpy outputs raw YUV420P format to the V4L2 device. In sc_v4l2_sink_open() within app/src/v4l2_sink.c, the code configures the FFmpeg encoder with codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P and codec_ctx->codec_id = AV_CODEC_ID_RAWVIDEO. This uncompressed format ensures compatibility with most v4l2 applications while minimizing CPU overhead from format conversion.
Can I use the V4L2 sink on macOS or Windows?
No, the V4L2 sink is Linux-specific. It relies on the Video4Linux2 API and the v4l2loopback kernel module, which are exclusive to Linux. The implementation in app/src/v4l2_sink.c uses Linux-specific device paths (/dev/videoN) and FFmpeg's v4l2 muxer, which requires V4L2 kernel interfaces. macOS and Windows users seeking similar functionality should use scrcpy's standard SDL display or third-party screen capture tools instead.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →