Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59cd1f55ad | ||
|
|
0cd902bd07 | ||
|
|
860569641f | ||
|
|
a963d89a64 | ||
|
|
195073d628 | ||
|
|
9d3bf09474 | ||
|
|
b2e67b5a60 | ||
|
|
066f4f5e88 | ||
|
|
c8eb5cc6e3 | ||
|
|
49582e4a2e | ||
|
|
99c6a76eb5 | ||
|
|
c9a5611382 | ||
|
|
7309a573dc | ||
|
|
8ede4b3f58 | ||
|
|
cec5bcbe0b | ||
|
|
6d8f0a247f | ||
|
|
bb6ac2b084 | ||
|
|
eb971390ed | ||
|
|
f27403e27f | ||
|
|
f82aab2057 | ||
|
|
71327e82cb | ||
|
|
fea3f29ffd | ||
|
|
fb07f4af55 | ||
|
|
1116502704 | ||
|
|
321bf79a0b | ||
|
|
85f6f32b9e | ||
|
|
7bb17e1abc | ||
|
|
adaa88952d | ||
|
|
8bae1f6b7f | ||
|
|
e3da97a80f | ||
|
|
b9c3f65fd8 | ||
|
|
d0739911a3 |
57
README.md
57
README.md
@@ -198,6 +198,7 @@ If `--max-size` is also specified, resizing is applied after cropping.
|
|||||||
To lock the orientation of the mirroring:
|
To lock the orientation of the mirroring:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
scrcpy --lock-video-orientation # initial (current) orientation
|
||||||
scrcpy --lock-video-orientation 0 # natural orientation
|
scrcpy --lock-video-orientation 0 # natural orientation
|
||||||
scrcpy --lock-video-orientation 1 # 90° counterclockwise
|
scrcpy --lock-video-orientation 1 # 90° counterclockwise
|
||||||
scrcpy --lock-video-orientation 2 # 180°
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
@@ -225,7 +226,9 @@ error will give the available encoders:
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Recording
|
### Capture
|
||||||
|
|
||||||
|
#### Recording
|
||||||
|
|
||||||
It is possible to record the screen while mirroring:
|
It is possible to record the screen while mirroring:
|
||||||
|
|
||||||
@@ -249,6 +252,58 @@ variation] does not impact the recorded file.
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[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
|
### Connection
|
||||||
|
|
||||||
#### Wireless
|
#### Wireless
|
||||||
|
|||||||
14
app/scrcpy.1
14
app/scrcpy.1
@@ -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.
|
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-lock\-video\-orientation " value
|
.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.
|
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
|
.TP
|
||||||
.BI "\-\-max\-fps " value
|
.BI "\-\-max\-fps " value
|
||||||
@@ -183,6 +185,12 @@ Enable "show touches" on start, restore the initial value on exit.
|
|||||||
|
|
||||||
It only shows physical touches (not clicks from scrcpy).
|
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
|
.TP
|
||||||
.BI "\-V, \-\-verbosity " value
|
.BI "\-V, \-\-verbosity " value
|
||||||
Set the log level ("debug", "info", "warn" or "error").
|
Set the log level ("debug", "info", "warn" or "error").
|
||||||
|
|||||||
@@ -79,12 +79,15 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" This is a workaround for some devices not behaving as\n"
|
" This is a workaround for some devices not behaving as\n"
|
||||||
" expected when setting the device clipboard programmatically.\n"
|
" expected when setting the device clipboard programmatically.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --lock-video-orientation value\n"
|
" --lock-video-orientation [value]\n"
|
||||||
" Lock video orientation to 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"
|
" Natural device orientation is 0, and each increment adds a\n"
|
||||||
" 90 degrees rotation counterclockwise.\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"
|
"\n"
|
||||||
" --max-fps value\n"
|
" --max-fps value\n"
|
||||||
" Limit the frame rate of screen capture (officially supported\n"
|
" Limit the frame rate of screen capture (officially supported\n"
|
||||||
@@ -173,11 +176,13 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" on exit.\n"
|
" on exit.\n"
|
||||||
" It only shows physical touches (not clicks from scrcpy).\n"
|
" It only shows physical touches (not clicks from scrcpy).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --v4l2_sink /dev/videoN\n"
|
#ifdef HAVE_V4L2
|
||||||
|
" --v4l2-sink /dev/videoN\n"
|
||||||
" Output to v4l2loopback device.\n"
|
" Output to v4l2loopback device.\n"
|
||||||
" It requires to lock the video orientation (see\n"
|
" It requires to lock the video orientation (see\n"
|
||||||
" --lock-video-orientation).\n"
|
" --lock-video-orientation).\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
#endif
|
||||||
" -V, --verbosity value\n"
|
" -V, --verbosity value\n"
|
||||||
" Set the log level (debug, info, warn or error).\n"
|
" Set the log level (debug, info, warn or error).\n"
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
@@ -388,15 +393,27 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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;
|
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");
|
"lock video orientation");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*lock_video_orientation = (int8_t) value;
|
*lock_mode = (enum sc_lock_video_orientation) value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +703,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
||||||
{"lock-video-orientation", required_argument, NULL,
|
{"lock-video-orientation", optional_argument, NULL,
|
||||||
OPT_LOCK_VIDEO_ORIENTATION},
|
OPT_LOCK_VIDEO_ORIENTATION},
|
||||||
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
||||||
{"max-size", required_argument, NULL, 'm'},
|
{"max-size", required_argument, NULL, 'm'},
|
||||||
@@ -709,7 +726,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"stay-awake", no_argument, NULL, 'w'},
|
{"stay-awake", no_argument, NULL, 'w'},
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
{"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK},
|
{"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK},
|
||||||
#endif
|
#endif
|
||||||
{"verbosity", required_argument, NULL, 'V'},
|
{"verbosity", required_argument, NULL, 'V'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
@@ -774,7 +791,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -894,29 +912,34 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
case OPT_POWER_OFF_ON_CLOSE:
|
case OPT_POWER_OFF_ON_CLOSE:
|
||||||
opts->power_off_on_close = true;
|
opts->power_off_on_close = true;
|
||||||
break;
|
break;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
case OPT_V4L2_SINK:
|
case OPT_V4L2_SINK:
|
||||||
opts->v4l2_device = optarg;
|
opts->v4l2_device = optarg;
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
||||||
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
||||||
#ifdef HAVE_V4L2
|
" or sink to v4l2loopback device (--v4l2-sink)");
|
||||||
" or sink to v4l2loopback device (--v4l2_sink)"
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
if (opts->v4l2_device && opts->lock_video_orientation
|
||||||
if (opts->v4l2_device && opts->lock_video_orientation == -1) {
|
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
LOGI("Video orientation is locked for v4l2 sink. "
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
"See --lock-video-orientation.");
|
"See --lock-video-orientation.");
|
||||||
opts->lock_video_orientation = 0;
|
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
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,6 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct decoder *dec = NULL;
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
if (!fps_counter_init(&fps_counter)) {
|
if (!fps_counter_init(&fps_counter)) {
|
||||||
goto end;
|
goto end;
|
||||||
@@ -318,7 +317,14 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
file_handler_initialized = true;
|
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);
|
decoder_init(&decoder);
|
||||||
dec = &decoder;
|
dec = &decoder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ enum sc_record_format {
|
|||||||
SC_RECORD_FORMAT_MKV,
|
SC_RECORD_FORMAT_MKV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_lock_video_orientation {
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
|
// lock the current orientation when scrcpy starts
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_1,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_3,
|
||||||
|
};
|
||||||
|
|
||||||
#define SC_MAX_SHORTCUT_MODS 8
|
#define SC_MAX_SHORTCUT_MODS 8
|
||||||
|
|
||||||
enum sc_shortcut_mod {
|
enum sc_shortcut_mod {
|
||||||
@@ -60,7 +70,7 @@ struct scrcpy_options {
|
|||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
uint8_t rotation;
|
uint8_t rotation;
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
@@ -108,7 +118,7 @@ struct scrcpy_options {
|
|||||||
.max_size = 0, \
|
.max_size = 0, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.lock_video_orientation = -1, \
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
/** Downcast frame_sink to sc_v4l2_sink */
|
/** Downcast frame_sink to sc_v4l2_sink */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
||||||
|
|
||||||
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
static const AVOutputFormat *
|
static const AVOutputFormat *
|
||||||
find_muxer(const char *name) {
|
find_muxer(const char *name) {
|
||||||
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||||
@@ -24,7 +26,60 @@ find_muxer(const char *name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
encode_and_send_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) {
|
||||||
|
AVStream *ostream = vs->format_ctx->streams[0];
|
||||||
|
|
||||||
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
|
if (!extradata) {
|
||||||
|
LOGC("Could not allocate extradata");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the first packet to the extra data
|
||||||
|
memcpy(extradata, packet->data, packet->size);
|
||||||
|
|
||||||
|
ostream->codecpar->extradata = extradata;
|
||||||
|
ostream->codecpar->extradata_size = packet->size;
|
||||||
|
|
||||||
|
int ret = avformat_write_header(vs->format_ctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write header to %s", vs->device_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rescale_packet(struct sc_v4l2_sink *vs, AVPacket *packet) {
|
||||||
|
AVStream *ostream = vs->format_ctx->streams[0];
|
||||||
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_packet(struct sc_v4l2_sink *vs, AVPacket *packet) {
|
||||||
|
if (!vs->header_written) {
|
||||||
|
bool ok = write_header(vs, packet);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vs->header_written = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rescale_packet(vs, packet);
|
||||||
|
|
||||||
|
bool ok = av_write_frame(vs->format_ctx, packet) >= 0;
|
||||||
|
|
||||||
|
// Failing to write the last frame is not very serious, no future frame may
|
||||||
|
// depend on it, so the resulting file will still be valid
|
||||||
|
(void) ok;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||||
int ret = avcodec_send_frame(vs->encoder_ctx, frame);
|
int ret = avcodec_send_frame(vs->encoder_ctx, frame);
|
||||||
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not send v4l2 video frame: %d", ret);
|
LOGE("Could not send v4l2 video frame: %d", ret);
|
||||||
@@ -35,7 +90,12 @@ encode_and_send_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
|||||||
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
|
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
// A packet was received
|
// A packet was received
|
||||||
av_write_frame(vs->format_ctx, packet);
|
|
||||||
|
bool ok = write_packet(vs, packet);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not send packet to v4l2 sink");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
av_packet_unref(packet);
|
av_packet_unref(packet);
|
||||||
} else if (ret != AVERROR(EAGAIN)) {
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not receive v4l2 video packet: %d", ret);
|
LOGE("Could not receive v4l2 video packet: %d", ret);
|
||||||
@@ -64,7 +124,7 @@ run_v4l2_sink(void *data) {
|
|||||||
sc_mutex_unlock(&vs->mutex);
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
video_buffer_consume(&vs->vb, vs->frame);
|
video_buffer_consume(&vs->vb, vs->frame);
|
||||||
bool ok = encode_and_send_frame(vs, vs->frame);
|
bool ok = encode_and_write_frame(vs, vs->frame);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not send frame to v4l2 sink");
|
LOGE("Could not send frame to v4l2 sink");
|
||||||
break;
|
break;
|
||||||
@@ -119,6 +179,12 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||||
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
vs->format_ctx->oformat = (AVOutputFormat *) format;
|
vs->format_ctx->oformat = (AVOutputFormat *) format;
|
||||||
|
vs->format_ctx->url = strdup(vs->device_name);
|
||||||
|
if (!vs->format_ctx->url) {
|
||||||
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
||||||
if (!ostream) {
|
if (!ostream) {
|
||||||
@@ -170,6 +236,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
goto error_av_frame_free;
|
goto error_av_frame_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vs->header_written = false;
|
||||||
|
vs->stopped = false;
|
||||||
|
|
||||||
LOGI("v4l2 sink started to device: %s", vs->device_name);
|
LOGI("v4l2 sink started to device: %s", vs->device_name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -196,6 +265,13 @@ error_video_buffer_destroy:
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||||
|
sc_mutex_lock(&vs->mutex);
|
||||||
|
vs->stopped = true;
|
||||||
|
sc_cond_signal(&vs->cond);
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
|
sc_thread_join(&vs->thread, NULL);
|
||||||
|
|
||||||
av_frame_free(&vs->frame);
|
av_frame_free(&vs->frame);
|
||||||
avcodec_close(vs->encoder_ctx);
|
avcodec_close(vs->encoder_ctx);
|
||||||
avcodec_free_context(&vs->encoder_ctx);
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct sc_v4l2_sink {
|
|||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond cond;
|
sc_cond cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
|
bool header_written;
|
||||||
|
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public final class Device {
|
|||||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||||
|
|
||||||
|
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||||
|
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
||||||
|
|
||||||
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
|
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
|
||||||
|
|
||||||
public interface RotationListener {
|
public interface RotationListener {
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ public final class ScreenInfo {
|
|||||||
|
|
||||||
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||||
int rotation = displayInfo.getRotation();
|
int rotation = displayInfo.getRotation();
|
||||||
|
|
||||||
|
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
||||||
|
// The user requested to lock the video orientation to the current orientation
|
||||||
|
lockedVideoOrientation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
Size deviceSize = displayInfo.getSize();
|
Size deviceSize = displayInfo.getSize();
|
||||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||||
if (crop != null) {
|
if (crop != null) {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
|
||||||
Assert.assertEquals(KeyEvent.ACTION_DOWN, event.getAction());
|
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user