Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v4l2loopback support #2268

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e3da97a
Write trailer from recorder thread
rom1v Apr 11, 2021
8bae1f6
Remove option --render-expired-frames
rom1v Apr 11, 2021
adaa889
Remove compat with old FFmpeg decoding API
rom1v Apr 11, 2021
7bb17e1
Remove compat with old FFmpeg codec params API
rom1v Apr 11, 2021
85f6f32
Make video_buffer more generic
rom1v Apr 11, 2021
321bf79
Add container_of() macro
rom1v Apr 11, 2021
1116502
Add packet sink trait
rom1v Apr 11, 2021
fb07f4a
Reorder recorder functions
rom1v Apr 11, 2021
fea3f29
Privatize recorder threading
rom1v Apr 11, 2021
71327e8
Expose recorder as packet sink
rom1v Apr 11, 2021
f82aab2
Reorder decoder functions
rom1v Apr 11, 2021
f27403e
Expose decoder as packet sink
rom1v Apr 11, 2021
eb97139
Make stream push packets to sinks
rom1v Apr 11, 2021
bb6ac2b
Add frame sink trait
rom1v Apr 11, 2021
6d8f0a2
Expose screen as frame sink
rom1v Apr 11, 2021
cec5bcb
Make decoder push frames to sinks
rom1v Apr 11, 2021
8ede4b3
Move video_buffer to screen
rom1v Apr 11, 2021
7309a57
Remove video_buffer callbacks
rom1v Apr 11, 2021
c9a5611
Assert screen closed on destroy
rom1v Apr 11, 2021
99c6a76
Hide the window immediately on close
rom1v Apr 13, 2021
49582e4
Initialize recorder fields on open
rom1v Apr 18, 2021
c8eb5cc
Handle errors using gotos in recorder_open()
rom1v Apr 18, 2021
066f4f5
Handle EAGAIN on send_packet in decoder
rom1v Apr 18, 2021
b2e67b5
Fix recorder comment
rom1v Apr 18, 2021
9d3bf09
Add strlist_contains()
rom1v Apr 19, 2021
195073d
Use strlist_contains() to find a muxer
rom1v Apr 19, 2021
a963d89
Add --lock-video-orientation=initial
rom1v Apr 19, 2021
8605696
Make --lock-video-orientation argument optional
rom1v Apr 19, 2021
0cd902b
Add support for v4l2loopback
martinellimarco Apr 3, 2021
59cd1f5
Document v4l2 sink in README
rom1v Apr 19, 2021
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
69 changes: 56 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ If `--max-size` is also specified, resizing is applied after cropping.
To lock the orientation of the mirroring:

```bash
scrcpy --lock-video-orientation # initial (current) orientation
scrcpy --lock-video-orientation 0 # natural orientation
scrcpy --lock-video-orientation 1 # 90° counterclockwise
scrcpy --lock-video-orientation 2 # 180°
Expand Down Expand Up @@ -225,7 +226,9 @@ error will give the available encoders:
scrcpy --encoder _
```

### Recording
### Capture

#### Recording

It is possible to record the screen while mirroring:

Expand All @@ -249,6 +252,58 @@ variation] does not impact the recorded file.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation


#### v4l2loopback

On Linux, it is possible to send the video stream to a v4l2 loopback device, so
that the Android device can be opened like a webcam by any v4l2-capable tool.

The module `v4l2loopback` must be installed:

```bash
sudo apt install v4l2loopback-dkms
```

To create a v4l2 device:

```bash
sudo modprobe v4l2loopback
```

This will create a new video device in `/dev/videoN`, where `N` is an integer
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
to create several devices or devices with specific IDs).

To list the enabled devices:

```bash
# requires v4l-utils package
v4l2-ctl --list-devices

# simple but might be sufficient
ls /dev/video*
```

To start scrcpy using a v4l2 sink:

```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window
```

(replace `N` by the device ID, check with `ls /dev/video*`)

Once enabled, you can open your video stream with a v4l2-capable tool:

```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC might add some buffering delay
```

For example, you could capture the video within [OBS].

[OBS]: https://obsproject.com/


### Connection

#### Wireless
Expand Down Expand Up @@ -491,18 +546,6 @@ scrcpy -Sw
```


#### Render expired frames

By default, to minimize latency, _scrcpy_ always renders the last decoded frame
available, and drops any previous one.

To force the rendering of all frames (at a cost of a possible increased
latency), use:

```bash
scrcpy --render-expired-frames
```

#### Show touches

For presentations, it may be useful to show physical touches (on the physical
Expand Down
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
18 changes: 11 additions & 7 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.

.TP
.BI "\-\-lock\-video\-orientation " value
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
.BI "\-\-lock\-video\-orientation " [value]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.

Default is -1 (unlocked).
Default is "unlocked".

Passing the option without argument is equivalent to passing "initial".

.TP
.BI "\-\-max\-fps " value
Expand Down Expand Up @@ -155,10 +157,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE

.TP
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.

.TP
.BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
Expand Down Expand Up @@ -187,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
72 changes: 57 additions & 15 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,15 @@ scrcpy_print_usage(const char *arg0) {
" This is a workaround for some devices not behaving as\n"
" expected when setting the device clipboard programmatically.\n"
"\n"
" --lock-video-orientation value\n"
" --lock-video-orientation [value]\n"
" Lock video orientation to value.\n"
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
" Possible values are \"unlocked\", \"initial\" (locked to the\n"
" initial orientation), 0, 1, 2 and 3.\n"
" Natural device orientation is 0, and each increment adds a\n"
" 90 degrees rotation counterclockwise.\n"
" Default is -1 (unlocked).\n"
" Default is \"unlocked\".\n"
" Passing the option without argument is equivalent to passing\n"
" \"initial\".\n"
"\n"
" --max-fps value\n"
" Limit the frame rate of screen capture (officially supported\n"
Expand Down Expand Up @@ -143,12 +146,6 @@ scrcpy_print_usage(const char *arg0) {
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
"\n"
" --render-expired-frames\n"
" By default, to minimize latency, scrcpy always renders the\n"
" last available decoded frame, and drops any previous ones.\n"
" This flag forces to render all frames, at a cost of a\n"
" possible increased latency.\n"
"\n"
" --rotation value\n"
" Set the initial display rotation.\n"
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
Expand Down Expand Up @@ -179,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 @@ -389,15 +393,27 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
}

static bool
parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
parse_lock_video_orientation(const char *s,
enum sc_lock_video_orientation *lock_mode) {
if (!s || !strcmp(s, "initial")) {
// Without argument, lock the initial orientation
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
return true;
}

if (!strcmp(s, "unlocked")) {
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED;
return true;
}

long value;
bool ok = parse_integer_arg(s, &value, false, -1, 3,
bool ok = parse_integer_arg(s, &value, false, 0, 3,
"lock video orientation");
if (!ok) {
return false;
}

*lock_video_orientation = (int8_t) value;
*lock_mode = (enum sc_lock_video_orientation) value;
return true;
}

Expand Down Expand Up @@ -667,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 All @@ -686,7 +703,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
{"lock-video-orientation", required_argument, NULL,
{"lock-video-orientation", optional_argument, NULL,
OPT_LOCK_VIDEO_ORIENTATION},
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
{"max-size", required_argument, NULL, 'm'},
Expand All @@ -708,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 @@ -771,7 +791,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
}
break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) {
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
return false;
}
break;
Expand Down Expand Up @@ -816,7 +837,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->stay_awake = true;
break;
case OPT_RENDER_EXPIRED_FRAMES:
opts->render_expired_frames = true;
LOGW("Option --render-expired-frames has been removed. This "
"flag has been ignored.");
break;
case OPT_WINDOW_TITLE:
opts->window_title = optarg;
Expand Down Expand Up @@ -890,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
3 changes: 3 additions & 0 deletions app/src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)

#define container_of(ptr, type, member) \
((type *) (((char *) (ptr)) - offsetof(type, member)))

#endif
20 changes: 0 additions & 20 deletions app/src/compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,9 @@
# define _DARWIN_C_SOURCE
#endif

#include <libavcodec/version.h>
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>

// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif

// In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(),
Expand All @@ -33,15 +22,6 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif

// In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
// Add a new audio/video encoding and decoding API with decoupled input
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
// avcodec_send_frame() and avcodec_receive_packet().
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
#endif

#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
Expand Down
Loading