Skip to content

Commit

Permalink
Add support for v4l2loopback
Browse files Browse the repository at this point in the history
It allows to send the video stream to /dev/videoN, so that it can be
captured (like a webcam) by any V4L2-compatible tool.

Refs #2232 <#2232>
Refs #2233 <#2233>

Co-authored-by: Romain Vimont <[email protected]>
  • Loading branch information
martinellimarco and rom1v committed Apr 19, 2021
1 parent 8605696 commit 4841bb5
Show file tree
Hide file tree
Showing 9 changed files with 480 additions and 2 deletions.
12 changes: 12 additions & 0 deletions app/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ else
src += [ 'src/sys/unix/process.c' ]
endif

v4l2_support = host_machine.system() == 'linux'
if v4l2_support
src += [ 'src/v4l2_sink.c' ]
endif

check_functions = [
'strdup'
]
Expand All @@ -49,6 +54,10 @@ if not get_option('crossbuild_windows')
dependency('sdl2'),
]

if v4l2_support
dependencies += dependency('libavdevice')
endif

else

# cross-compile mingw32 build (from Linux to Windows)
Expand Down Expand Up @@ -124,6 +133,9 @@ conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')

# enable V4L2 support (linux only)
conf.set('HAVE_V4L2', v4l2_support)

configure_file(configuration: conf, output: 'config.h')

src_dir = include_directories('src')
Expand Down
6 changes: 6 additions & 0 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ Enable "show touches" on start, restore the initial value on exit.

It only shows physical touches (not clicks from scrcpy).

.TP
.BI "\-\-v4l2_sink " /dev/videoN
Output to v4l2loopback device.

It requires to lock the video orientation (see --lock-video-orientation).

.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("debug", "info", "warn" or "error").
Expand Down
31 changes: 31 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ scrcpy_print_usage(const char *arg0) {
" on exit.\n"
" It only shows physical touches (not clicks from scrcpy).\n"
"\n"
#ifdef HAVE_V4L2
" --v4l2_sink /dev/videoN\n"
" Output to v4l2loopback device.\n"
" It requires to lock the video orientation (see\n"
" --lock-video-orientation).\n"
"\n"
#endif
" -V, --verbosity value\n"
" Set the log level (debug, info, warn or error).\n"
#ifndef NDEBUG
Expand Down Expand Up @@ -676,6 +683,7 @@ guess_record_format(const char *filename) {
#define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026
#define OPT_V4L2_SINK 1027

bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
Expand Down Expand Up @@ -717,6 +725,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"show-touches", no_argument, NULL, 't'},
{"stay-awake", no_argument, NULL, 'w'},
{"turn-screen-off", no_argument, NULL, 'S'},
#ifdef HAVE_V4L2
{"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK},
#endif
{"verbosity", required_argument, NULL, 'V'},
{"version", no_argument, NULL, 'v'},
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
Expand Down Expand Up @@ -901,16 +912,36 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_POWER_OFF_ON_CLOSE:
opts->power_off_on_close = true;
break;
#ifdef HAVE_V4L2
case OPT_V4L2_SINK:
opts->v4l2_device = optarg;
break;
#endif
default:
// getopt prints the error message on stderr
return false;
}
}

#ifdef HAVE_V4L2
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-display requires either screen recording (-r/--record)"
" or sink to v4l2loopback device (--v4l2_sink)");
return false;
}

if (opts->v4l2_device && opts->lock_video_orientation
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. "
"See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
}
#else
if (!opts->display && !opts->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
return false;
}
#endif

int index = optind;
if (index < argc) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include <stdbool.h>
#include <libavformat/avformat.h>

#define DECODER_MAX_SINKS 1
#define DECODER_MAX_SINKS 2

struct decoder {
struct sc_packet_sink packet_sink; // packet sink trait
Expand Down
14 changes: 14 additions & 0 deletions app/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h>

Expand All @@ -28,6 +31,11 @@ print_version(void) {
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
#ifdef HAVE_V4L2
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
#endif
}

static SDL_LogPriority
Expand Down Expand Up @@ -90,6 +98,12 @@ main(int argc, char *argv[]) {
av_register_all();
#endif

#ifdef HAVE_V4L2
if (args.opts.v4l2_device) {
avdevice_register_all();
}
#endif

if (avformat_network_init()) {
return 1;
}
Expand Down
35 changes: 34 additions & 1 deletion app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@
#include "tiny_xpm.h"
#include "util/log.h"
#include "util/net.h"
#ifdef HAVE_V4L2
# include "v4l2_sink.h"
#endif

static struct server server;
static struct screen screen;
static struct fps_counter fps_counter;
static struct stream stream;
static struct decoder decoder;
static struct recorder recorder;
#ifdef HAVE_V4L2
static struct sc_v4l2_sink v4l2_sink;
#endif
static struct controller controller;
static struct file_handler file_handler;

Expand Down Expand Up @@ -247,6 +253,9 @@ scrcpy(const struct scrcpy_options *options) {
bool fps_counter_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
#endif
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
Expand Down Expand Up @@ -295,7 +304,6 @@ scrcpy(const struct scrcpy_options *options) {
goto end;
}

struct decoder *dec = NULL;
if (options->display) {
if (!fps_counter_init(&fps_counter)) {
goto end;
Expand All @@ -309,7 +317,14 @@ scrcpy(const struct scrcpy_options *options) {
}
file_handler_initialized = true;
}
}

struct decoder *dec = NULL;
bool needs_decoder = options->display;
#ifdef HAVE_V4L2
needs_decoder |= !!options->v4l2_device;
#endif
if (needs_decoder) {
decoder_init(&decoder);
dec = &decoder;
}
Expand Down Expand Up @@ -386,6 +401,18 @@ scrcpy(const struct scrcpy_options *options) {
}
}

#ifdef HAVE_V4L2
if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&v4l2_sink, options->v4l2_device, frame_size)) {
goto end;
}

decoder_add_sink(&decoder, &v4l2_sink.frame_sink);

v4l2_sink_initialized = true;
}
#endif

// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
Expand Down Expand Up @@ -426,6 +453,12 @@ scrcpy(const struct scrcpy_options *options) {
stream_join(&stream);
}

#ifdef HAVE_V4L2
if (v4l2_sink_initialized) {
sc_v4l2_sink_destroy(&v4l2_sink);
}
#endif

// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
Expand Down
2 changes: 2 additions & 0 deletions app/src/scrcpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct scrcpy_options {
const char *render_driver;
const char *codec_options;
const char *encoder_name;
const char *v4l2_device;
enum sc_log_level log_level;
enum sc_record_format record_format;
struct sc_port_range port_range;
Expand Down Expand Up @@ -103,6 +104,7 @@ struct scrcpy_options {
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.v4l2_device = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.port_range = { \
Expand Down
Loading

0 comments on commit 4841bb5

Please sign in to comment.