Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <fcntl.h>

#include <optional>
#include <string>
#include <unordered_set>
#include <utility>
Expand Down Expand Up @@ -67,23 +68,44 @@ class VhostUserMediaDevices : public CommandSource {
for (int index = 0; index < instance_.media_configs().size(); index++) {
auto config = instance_.media_configs()[index];
std::string binary_path;
std::optional<Command> cmd;
if (config.type ==
CuttlefishConfig::MediaType::kV4l2EmulatedCameraMPlane) {
binary_path = VhostUserMediaEmulatedCameraMPlaneBinary();
cmd.emplace(NewCommand(binary_path, instance_.media_socket_path(index),
config.lens_facing));
} else if (config.type ==
CuttlefishConfig::MediaType::kV4l2EmulatedCameraSPlane) {
binary_path = VhostUserMediaEmulatedCameraSPlaneBinary();
cmd.emplace(NewCommand(binary_path, instance_.media_socket_path(index),
config.lens_facing));
} else if (config.type == CuttlefishConfig::MediaType::kV4l2StreamProxy) {
CF_EXPECT(config.v4l2_stream_proxy.has_value(),
"Missing v4l2_stream_proxy config");
binary_path = VhostUserMediaV4l2StreamProxyBinary();
cmd.emplace(
NewCommand(binary_path, instance_.media_socket_path(index), ""));
cmd->AddParameter("--input_path=",
config.v4l2_stream_proxy->input_path);
cmd->AddParameter(
"--input_width=",
std::to_string(config.v4l2_stream_proxy->input_width));
cmd->AddParameter(
"--input_height=",
std::to_string(config.v4l2_stream_proxy->input_height));
cmd->AddParameter("--input_fps=", config.v4l2_stream_proxy->input_fps);
} else if (config.type == CuttlefishConfig::MediaType::kV4l2Proxy) {
continue;
} else {
CF_EXPECT(false, "unknown media type");
}
Command cmd = NewCommand(binary_path, instance_.media_socket_path(index),
config.lens_facing);

CF_EXPECT(cmd.has_value(), "Command was not initialized");

Command cmd_log_tee = CF_EXPECT(
log_tee_.CreateLogTee(cmd, "vhu_media_simple_device", kStdErr),
log_tee_.CreateLogTee(*cmd, "vhu_media_simple_device", kStdErr),
"Failed to create log tee command for media device");
commands.emplace_back(std::move(cmd));
commands.emplace_back(std::move(*cmd));
commands.emplace_back(std::move(cmd_log_tee));
}
return commands;
Expand Down
2 changes: 2 additions & 0 deletions base/cvd/cuttlefish/host/commands/vhost_user_media/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"emulated_camera_mplane",
"emulated_camera_splane",
"vhu_media",
"v4l2_stream_proxy",
]

[workspace.dependencies]
Expand All @@ -11,6 +12,7 @@ env_logger = "0.11"
image = "0.24"
libc = "0.2"
log = "0.4"
nix = "0.29.0"
thiserror = "2.0"
v4l2r = { version = "0.0.6", features = ["arch64"] }
vhost = { version = "0.16.0", features = ["vhost-user-backend"] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("@vhost_user_media_workspace_crates//:defs.bzl", "crate_deps")

package(default_visibility = ["//:android_cuttlefish"])

alias(
name = "v4l2_stream_proxy",
actual = select({
"@platforms//cpu:x86_64": ":v4l2_stream_proxy_binary",
"//conditions:default": ":unsupported_binary",
}),
)

rust_binary(
name = "v4l2_stream_proxy_binary",
srcs = [
"src/device.rs",
"src/main.rs",
"src/worker.rs",
],
edition = "2024",
deps = crate_deps([
"clap",
"env_logger",
"libc",
"log",
"nix",
"v4l2r",
"vhost-user-backend",
"virtio-media",
"virtio-queue",
"vm-memory",
]) + [
"//cuttlefish/host/commands/vhost_user_media/vhu_media",
],
)

genrule(
name = "unsupported_binary",
outs = ["unsupported.sh"],
cmd = """cat << 'EOF' > $@
#!/bin/bash
echo "platform unsupported" >&2
exit 1
EOF""",
executable = True,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "v4l2_stream_proxy"
version = "0.1.0"
edition = "2024"

[dependencies]
clap = { workspace = true }
env_logger = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
nix = { workspace = true, features = ["event", "poll"] }
v4l2r = { workspace = true }
vhost-user-backend = { workspace = true }
vhu_media = { workspace = true }
virtio-media = { workspace = true }
virtio-queue = { workspace = true }
vm-memory = { workspace = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# V4L2 Stream Proxy Host Tool

This directory contains the `v4l2_stream_proxy` vhost-user device. To test this device, you can use the provided helper script to start a video stream on the host, which can then be proxied by the `cvd` tool from the host to the guest.

The test helper script is located at:
`tools/testutils/ffmpeg_v4l2_stream_proxy.sh` (relative to the repository root).

## Prerequisites

The script requires `ffmpeg` (and `ffprobe` if streaming from a file) to be installed on the host system.

```bash
sudo apt install ffmpeg
```

## Usage

The script creates a FIFO (named pipe) and runs `ffmpeg` in a loop to continuously feed video into it. When `ffmpeg` exits (or fails), the script automatically restarts it. `ffmpeg`'s normal behavior includes exiting when the FIFO's reader closes the FIFO. This happens whenever the camera's stream is stopped in the guest.

In both modes (Test Source and Video File), the script will print an argument to add to `cvd create`/`cvd start` that will provide a video device in the guest that matches the host configuration.

### 1. Test Source Mode (Default)

If no video file is provided, the script uses `ffmpeg`'s `testsrc` filter to generate a synthetic test pattern (color bars and a timer).

To start streaming a 640x480 video at 30 fps (defaults):

```bash
tools/testutils/ffmpeg_v4l2_stream_proxy.sh
```

You can customize the resolution and frame rate:

```bash
tools/testutils/ffmpeg_v4l2_stream_proxy.sh -w 1280 -H 720 -r 60
```

Available options:
* `-w <width>`: Video width (default: 640)
* `-H <height>`: Video height (default: 480)
* `-r <fps>`: Video FPS (default: 30)
* `-p <fifo_path>`: Path to the FIFO to create/use (default: `/tmp/v4l2_fifo`)

### 2. Video File Mode

To stream a video file (e.g., `.mp4`, `.mjpeg`, `.h264`) in a loop:

```bash
tools/testutils/ffmpeg_v4l2_stream_proxy.sh -f /path/to/video.mp4
```

In this mode, the script uses `ffprobe` to automatically detect the video's resolution and frame rate.

Available options:
* `-f <video_file>`: Path to the host video file
* `-p <fifo_path>`: Path to the FIFO to create/use (default: `/tmp/v4l2_fifo`)

## Connecting to Cuttlefish (CVD)

Once the streaming script is running, start Cuttlefish and configure it to use the stream. Use the parameters printed by the script (or the defaults) to configure the `--media` flag.

Example CVD launch command:

```bash
cvd create --media=v4l2_stream_proxy:input_path=/tmp/v4l2_fifo:input_width=640:input_height=480:input_fps=30
```

## Stopping the Stream

Press `Ctrl+C` in the terminal running the script. The script will catch the interrupt, kill the active `ffmpeg` process, and clean up the FIFO if it was created by the script.
Loading
Loading