Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5521a92959 | ||
|
|
31583d9808 | ||
|
|
0f1ca970d1 | ||
|
|
2453d86fdf | ||
|
|
89c2ef2c8d | ||
|
|
e3e7924bff | ||
|
|
bacac84a96 | ||
|
|
42c7e97e9b | ||
|
|
b16f2eaae1 | ||
|
|
3423e6d766 | ||
|
|
2981b11a49 | ||
|
|
0da276ccba | ||
|
|
e6ecf09079 | ||
|
|
ed86b8d446 | ||
|
|
e1da07e9a8 | ||
|
|
8f10558ea9 | ||
|
|
16f6f5bcc1 | ||
|
|
4634a59631 | ||
|
|
3b58afb0ac | ||
|
|
29c2935c45 | ||
|
|
12c90196fa | ||
|
|
cc714060a0 | ||
|
|
e50f2d3954 | ||
|
|
ae04ad41be | ||
|
|
dde52d679d | ||
|
|
00a002e139 | ||
|
|
d593927b2c | ||
|
|
c4f3a768d5 | ||
|
|
b029b1de9d | ||
|
|
964b6d2243 | ||
|
|
8cc057c8f1 | ||
|
|
edee69d637 |
@@ -33,6 +33,11 @@ else
|
|||||||
src += [ 'src/sys/unix/process.c' ]
|
src += [ 'src/sys/unix/process.c' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
v4l2_support = host_machine.system() == 'linux'
|
||||||
|
if v4l2_support
|
||||||
|
src += [ 'src/v4l2_sink.c' ]
|
||||||
|
endif
|
||||||
|
|
||||||
check_functions = [
|
check_functions = [
|
||||||
'strdup'
|
'strdup'
|
||||||
]
|
]
|
||||||
@@ -49,6 +54,10 @@ if not get_option('crossbuild_windows')
|
|||||||
dependency('sdl2'),
|
dependency('sdl2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if v4l2_support
|
||||||
|
dependencies += dependency('libavdevice')
|
||||||
|
endif
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
# cross-compile mingw32 build (from Linux to Windows)
|
# cross-compile mingw32 build (from Linux to Windows)
|
||||||
@@ -124,6 +133,9 @@ conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
|||||||
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
||||||
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
|
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')
|
configure_file(configuration: conf, output: 'config.h')
|
||||||
|
|
||||||
src_dir = include_directories('src')
|
src_dir = include_directories('src')
|
||||||
|
|||||||
@@ -183,16 +183,16 @@ 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
|
|
||||||
.B \-v, \-\-version
|
|
||||||
Print the version of scrcpy.
|
|
||||||
|
|
||||||
.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").
|
||||||
|
|
||||||
Default is "info" for release builds, "debug" for debug builds.
|
Default is "info" for release builds, "debug" for debug builds.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-v, \-\-version
|
||||||
|
Print the version of scrcpy.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-w, \-\-stay-awake
|
.B \-w, \-\-stay-awake
|
||||||
Keep the device on while scrcpy is running, when the device is plugged in.
|
Keep the device on while scrcpy is running, when the device is plugged in.
|
||||||
|
|||||||
@@ -173,8 +173,10 @@ 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"
|
||||||
" -v, --version\n"
|
" --v4l2_sink /dev/videoN\n"
|
||||||
" Print the version of scrcpy.\n"
|
" Output to v4l2loopback device.\n"
|
||||||
|
" It requires to lock the video orientation (see\n"
|
||||||
|
" --lock-video-orientation).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -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"
|
||||||
@@ -183,6 +185,9 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
#else
|
#else
|
||||||
" Default is info.\n"
|
" Default is info.\n"
|
||||||
#endif
|
#endif
|
||||||
|
"\n"
|
||||||
|
" -v, --version\n"
|
||||||
|
" Print the version of scrcpy.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -w, --stay-awake\n"
|
" -w, --stay-awake\n"
|
||||||
" Keep the device on while scrcpy is running, when the device\n"
|
" Keep the device on while scrcpy is running, when the device\n"
|
||||||
@@ -661,6 +666,7 @@ guess_record_format(const char *filename) {
|
|||||||
#define OPT_LEGACY_PASTE 1024
|
#define OPT_LEGACY_PASTE 1024
|
||||||
#define OPT_ENCODER_NAME 1025
|
#define OPT_ENCODER_NAME 1025
|
||||||
#define OPT_POWER_OFF_ON_CLOSE 1026
|
#define OPT_POWER_OFF_ON_CLOSE 1026
|
||||||
|
#define OPT_V4L2_SINK 1027
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
@@ -702,6 +708,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"show-touches", no_argument, NULL, 't'},
|
{"show-touches", no_argument, NULL, 't'},
|
||||||
{"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
|
||||||
|
{"v4l2_sink", required_argument, NULL, OPT_V4L2_SINK},
|
||||||
|
#endif
|
||||||
{"verbosity", required_argument, NULL, 'V'},
|
{"verbosity", required_argument, NULL, 'V'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||||
@@ -885,17 +894,32 @@ 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;
|
||||||
|
case OPT_V4L2_SINK:
|
||||||
|
opts->v4l2_device = optarg;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts->display && !opts->record_filename) {
|
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
||||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
" or sink to v4l2loopback device (--v4l2_sink)"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
if (opts->v4l2_device && opts->lock_video_orientation == -1) {
|
||||||
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
|
"See --lock-video-orientation.");
|
||||||
|
opts->lock_video_orientation = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int index = optind;
|
int index = optind;
|
||||||
if (index < argc) {
|
if (index < argc) {
|
||||||
LOGE("Unexpected additional argument: %s", argv[index]);
|
LOGE("Unexpected additional argument: %s", argv[index]);
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buffer_write32be(&buf[17],
|
buffer_write32be(&buf[17],
|
||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
return 21;
|
return 21;
|
||||||
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
buf[1] = msg->inject_keycode.action;
|
||||||
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buf[1] = !!msg->set_clipboard.paste;
|
buf[1] = !!msg->set_clipboard.paste;
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
size_t len = write_string(msg->set_clipboard.text,
|
||||||
@@ -77,7 +80,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ struct control_msg {
|
|||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
|
struct {
|
||||||
|
enum android_keyevent_action action; // action for the BACK key
|
||||||
|
// screen may only be turned on on ACTION_DOWN
|
||||||
|
} back_or_screen_on;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by free()
|
char *text; // owned, to be freed by free()
|
||||||
bool paste;
|
bool paste;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
|||||||
av_frame_free(&decoder->frame);
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -98,8 +99,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret;
|
int ret = avcodec_send_packet(decoder->codec_ctx, packet);
|
||||||
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not send video packet: %d", ret);
|
LOGE("Could not send video packet: %d", ret);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#define DECODER_MAX_SINKS 1
|
#define DECODER_MAX_SINKS 2
|
||||||
|
|
||||||
struct decoder {
|
struct decoder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|||||||
@@ -146,13 +146,25 @@ action_cut(struct controller *controller, int actions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
|
// If the screen is off, it is turned on only on ACTION_DOWN
|
||||||
static void
|
static void
|
||||||
press_back_or_turn_screen_on(struct controller *controller) {
|
press_back_or_turn_screen_on(struct controller *controller, int actions) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (actions & ACTION_DOWN) {
|
||||||
LOGW("Could not request 'press back or turn screen on'");
|
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN;
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions & ACTION_UP) {
|
||||||
|
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP;
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,13 +658,23 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||||
if (!im->forward_all_clicks && down) {
|
if (!im->forward_all_clicks) {
|
||||||
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
|
|
||||||
|
if (control && event->button == SDL_BUTTON_X1) {
|
||||||
|
action_app_switch(im->controller, action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (control && event->button == SDL_BUTTON_X2 && down) {
|
||||||
|
expand_notification_panel(im->controller);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||||
press_back_or_turn_screen_on(im->controller);
|
press_back_or_turn_screen_on(im->controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
||||||
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
action_home(im->controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,7 +687,9 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
bool outside = x < r->x || x >= r->x + r->w
|
bool outside = x < r->x || x >= r->x + r->w
|
||||||
|| y < r->y || y >= r->y + r->h;
|
|| y < r->y || y >= r->y + r->h;
|
||||||
if (outside) {
|
if (outside) {
|
||||||
screen_resize_to_fit(im->screen);
|
if (down) {
|
||||||
|
screen_resize_to_fit(im->screen);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
# include <libavdevice/avdevice.h>
|
||||||
|
#endif
|
||||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
@@ -28,6 +31,11 @@ print_version(void) {
|
|||||||
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
||||||
LIBAVUTIL_VERSION_MINOR,
|
LIBAVUTIL_VERSION_MINOR,
|
||||||
LIBAVUTIL_VERSION_MICRO);
|
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
|
static SDL_LogPriority
|
||||||
@@ -90,6 +98,12 @@ main(int argc, char *argv[]) {
|
|||||||
av_register_all();
|
av_register_all();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
if (args.opts.v4l2_device) {
|
||||||
|
avdevice_register_all();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (avformat_network_init()) {
|
if (avformat_network_init()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to recorder */
|
/** Downcast packet_sink to recorder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
||||||
@@ -22,8 +23,8 @@ find_muxer(const char *name) {
|
|||||||
#else
|
#else
|
||||||
oformat = av_oformat_next(oformat);
|
oformat = av_oformat_next(oformat);
|
||||||
#endif
|
#endif
|
||||||
// until null or with name "mp4"
|
// until null or containing the requested name
|
||||||
} while (oformat && strcmp(oformat->name, name));
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
return oformat;
|
return oformat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,18 +219,36 @@ run_recorder(void *data) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||||
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create mutex");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&recorder->queue_cond);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create cond");
|
||||||
|
goto error_mutex_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_init(&recorder->queue);
|
||||||
|
recorder->stopped = false;
|
||||||
|
recorder->failed = false;
|
||||||
|
recorder->header_written = false;
|
||||||
|
recorder->previous = NULL;
|
||||||
|
|
||||||
const char *format_name = recorder_get_format_name(recorder->format);
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
assert(format_name);
|
assert(format_name);
|
||||||
const AVOutputFormat *format = find_muxer(format_name);
|
const AVOutputFormat *format = find_muxer(format_name);
|
||||||
if (!format) {
|
if (!format) {
|
||||||
LOGE("Could not find muxer");
|
LOGE("Could not find muxer");
|
||||||
return false;
|
goto error_cond_destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder->ctx = avformat_alloc_context();
|
recorder->ctx = avformat_alloc_context();
|
||||||
if (!recorder->ctx) {
|
if (!recorder->ctx) {
|
||||||
LOGE("Could not allocate output context");
|
LOGE("Could not allocate output context");
|
||||||
return false;
|
goto error_cond_destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
@@ -243,8 +262,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
|||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
||||||
if (!ostream) {
|
if (!ostream) {
|
||||||
avformat_free_context(recorder->ctx);
|
goto error_avformat_free_context;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
@@ -258,22 +276,31 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGE("Failed to open output file: %s", recorder->filename);
|
LOGE("Failed to open output file: %s", recorder->filename);
|
||||||
// ostream will be cleaned up during context cleaning
|
// ostream will be cleaned up during context cleaning
|
||||||
avformat_free_context(recorder->ctx);
|
goto error_avformat_free_context;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Starting recorder thread");
|
LOGD("Starting recorder thread");
|
||||||
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
|
ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
|
||||||
recorder);
|
recorder);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGC("Could not start recorder thread");
|
LOGC("Could not start recorder thread");
|
||||||
avformat_free_context(recorder->ctx);
|
goto error_avio_close;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
error_avio_close:
|
||||||
|
avio_close(recorder->ctx->pb);
|
||||||
|
error_avformat_free_context:
|
||||||
|
avformat_free_context(recorder->ctx);
|
||||||
|
error_cond_destroy:
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
error_mutex_destroy:
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -287,6 +314,8 @@ recorder_close(struct recorder *recorder) {
|
|||||||
|
|
||||||
avio_close(recorder->ctx->pb);
|
avio_close(recorder->ctx->pb);
|
||||||
avformat_free_context(recorder->ctx);
|
avformat_free_context(recorder->ctx);
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -343,28 +372,8 @@ recorder_init(struct recorder *recorder,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = sc_mutex_init(&recorder->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
LOGC("Could not create mutex");
|
|
||||||
free(recorder->filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->queue_cond);
|
|
||||||
if (!ok) {
|
|
||||||
LOGC("Could not create cond");
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
|
||||||
free(recorder->filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
queue_init(&recorder->queue);
|
|
||||||
recorder->stopped = false;
|
|
||||||
recorder->failed = false;
|
|
||||||
recorder->format = format;
|
recorder->format = format;
|
||||||
recorder->declared_frame_size = declared_frame_size;
|
recorder->declared_frame_size = declared_frame_size;
|
||||||
recorder->header_written = false;
|
|
||||||
recorder->previous = NULL;
|
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops ops = {
|
static const struct sc_packet_sink_ops ops = {
|
||||||
.open = recorder_packet_sink_open,
|
.open = recorder_packet_sink_open,
|
||||||
@@ -379,7 +388,5 @@ recorder_init(struct recorder *recorder,
|
|||||||
|
|
||||||
void
|
void
|
||||||
recorder_destroy(struct recorder *recorder) {
|
recorder_destroy(struct recorder *recorder) {
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
|
||||||
free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,9 @@
|
|||||||
#include "tiny_xpm.h"
|
#include "tiny_xpm.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
# include "v4l2_sink.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct server server;
|
static struct server server;
|
||||||
static struct screen screen;
|
static struct screen screen;
|
||||||
@@ -34,6 +37,9 @@ static struct fps_counter fps_counter;
|
|||||||
static struct stream stream;
|
static struct stream stream;
|
||||||
static struct decoder decoder;
|
static struct decoder decoder;
|
||||||
static struct recorder recorder;
|
static struct recorder recorder;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
static struct sc_v4l2_sink v4l2_sink;
|
||||||
|
#endif
|
||||||
static struct controller controller;
|
static struct controller controller;
|
||||||
static struct file_handler file_handler;
|
static struct file_handler file_handler;
|
||||||
|
|
||||||
@@ -247,6 +253,9 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
bool fps_counter_initialized = false;
|
bool fps_counter_initialized = false;
|
||||||
bool file_handler_initialized = false;
|
bool file_handler_initialized = false;
|
||||||
bool recorder_initialized = false;
|
bool recorder_initialized = false;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
bool v4l2_sink_initialized = false;
|
||||||
|
#endif
|
||||||
bool stream_started = false;
|
bool stream_started = false;
|
||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
@@ -386,6 +395,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
|
// now we consumed the header values, the socket receives the video stream
|
||||||
// start the stream
|
// start the stream
|
||||||
if (!stream_start(&stream)) {
|
if (!stream_start(&stream)) {
|
||||||
@@ -426,6 +447,12 @@ end:
|
|||||||
stream_join(&stream);
|
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,
|
// Destroy the screen only after the stream is guaranteed to be finished,
|
||||||
// because otherwise the screen could receive new frames after destruction
|
// because otherwise the screen could receive new frames after destruction
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ struct scrcpy_options {
|
|||||||
const char *render_driver;
|
const char *render_driver;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
const char *encoder_name;
|
const char *encoder_name;
|
||||||
|
const char *v4l2_device;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
@@ -93,6 +94,7 @@ struct scrcpy_options {
|
|||||||
.render_driver = NULL, \
|
.render_driver = NULL, \
|
||||||
.codec_options = NULL, \
|
.codec_options = NULL, \
|
||||||
.encoder_name = NULL, \
|
.encoder_name = NULL, \
|
||||||
|
.v4l2_device = NULL, \
|
||||||
.log_level = SC_LOG_LEVEL_INFO, \
|
.log_level = SC_LOG_LEVEL_INFO, \
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO, \
|
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||||
.port_range = { \
|
.port_range = { \
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
#include "trait/frame_sink.h"
|
#include "trait/frame_sink.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
|
|
||||||
struct video_buffer;
|
|
||||||
|
|
||||||
struct screen {
|
struct screen {
|
||||||
struct sc_frame_sink frame_sink; // frame sink trait
|
struct sc_frame_sink frame_sink; // frame sink trait
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frame sink trait.
|
* Frame sink trait.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
|
typedef struct AVCodec AVCodec;
|
||||||
|
typedef struct AVPacket AVPacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Packet sink trait.
|
* Packet sink trait.
|
||||||
|
|||||||
@@ -140,6 +140,24 @@ parse_integer_with_suffix(const char *s, long *out) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
strlist_contains(const char *list, char sep, const char *s) {
|
||||||
|
char *p;
|
||||||
|
do {
|
||||||
|
p = strchr(list, sep);
|
||||||
|
|
||||||
|
size_t token_len = p ? (size_t) (p - list) : strlen(list);
|
||||||
|
if (!strncmp(list, s, token_len)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
list = p + 1;
|
||||||
|
}
|
||||||
|
} while (p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
utf8_truncation_index(const char *utf8, size_t max_len) {
|
utf8_truncation_index(const char *utf8, size_t max_len) {
|
||||||
size_t len = strlen(utf8);
|
size_t len = strlen(utf8);
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ parse_integers(const char *s, const char sep, size_t max_items, long *out);
|
|||||||
bool
|
bool
|
||||||
parse_integer_with_suffix(const char *s, long *out);
|
parse_integer_with_suffix(const char *s, long *out);
|
||||||
|
|
||||||
|
// search s in the list separated by sep
|
||||||
|
// for example, strlist_contains("a,bc,def", ',', "bc") returns true
|
||||||
|
bool
|
||||||
|
strlist_contains(const char *list, char sep, const char *s);
|
||||||
|
|
||||||
// return the index to truncate a UTF-8 string at a valid position
|
// return the index to truncate a UTF-8 string at a valid position
|
||||||
size_t
|
size_t
|
||||||
utf8_truncation_index(const char *utf8, size_t max_len);
|
utf8_truncation_index(const char *utf8, size_t max_len);
|
||||||
|
|||||||
265
app/src/v4l2_sink.c
Normal file
265
app/src/v4l2_sink.c
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#include "v4l2_sink.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
/** Downcast frame_sink to sc_v4l2_sink */
|
||||||
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
||||||
|
|
||||||
|
static const AVOutputFormat *
|
||||||
|
find_muxer(const char *name) {
|
||||||
|
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||||
|
void *opaque = NULL;
|
||||||
|
#endif
|
||||||
|
const AVOutputFormat *oformat = NULL;
|
||||||
|
do {
|
||||||
|
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||||
|
oformat = av_muxer_iterate(&opaque);
|
||||||
|
#else
|
||||||
|
oformat = av_oformat_next(oformat);
|
||||||
|
#endif
|
||||||
|
// until null or containing the requested name
|
||||||
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
|
return oformat;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
encode_and_send_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||||
|
int ret = avcodec_send_frame(vs->encoder_ctx, frame);
|
||||||
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
|
LOGE("Could not send v4l2 video frame: %d", ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVPacket *packet = &vs->packet;
|
||||||
|
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
|
||||||
|
if (ret == 0) {
|
||||||
|
// A packet was received
|
||||||
|
av_write_frame(vs->format_ctx, packet);
|
||||||
|
av_packet_unref(packet);
|
||||||
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
|
LOGE("Could not receive v4l2 video packet: %d", ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_v4l2_sink(void *data) {
|
||||||
|
struct sc_v4l2_sink *vs = data;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
sc_mutex_lock(&vs->mutex);
|
||||||
|
|
||||||
|
while (!vs->stopped && vs->vb.pending_frame_consumed) {
|
||||||
|
sc_cond_wait(&vs->cond, &vs->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vs->stopped) {
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
|
video_buffer_consume(&vs->vb, vs->frame);
|
||||||
|
bool ok = encode_and_send_frame(vs, vs->frame);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not send frame to v4l2 sink");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("V4l2 thread ended");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
||||||
|
bool ok = video_buffer_init(&vs->vb);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_mutex_init(&vs->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create mutex");
|
||||||
|
goto error_video_buffer_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&vs->cond);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create cond");
|
||||||
|
goto error_mutex_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
const AVOutputFormat *format = find_muxer("video4linux2,v4l2");
|
||||||
|
if (!format) {
|
||||||
|
LOGE("Could not find v4l2 muxer");
|
||||||
|
goto error_cond_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_RAWVIDEO);
|
||||||
|
if (!encoder) {
|
||||||
|
LOGE("Raw video encoder not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->format_ctx = avformat_alloc_context();
|
||||||
|
if (!vs->format_ctx) {
|
||||||
|
LOGE("Could not allocate v4l2 output context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
|
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
||||||
|
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||||
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
|
vs->format_ctx->oformat = (AVOutputFormat *) format;
|
||||||
|
|
||||||
|
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
||||||
|
if (!ostream) {
|
||||||
|
LOGE("Could not allocate new v4l2 stream");
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
ostream->codecpar->codec_id = encoder->id;
|
||||||
|
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||||
|
ostream->codecpar->width = vs->frame_size.width;
|
||||||
|
ostream->codecpar->height = vs->frame_size.height;
|
||||||
|
|
||||||
|
int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to open output device: %s", vs->device_name);
|
||||||
|
// ostream will be cleaned up during context cleaning
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->encoder_ctx = avcodec_alloc_context3(encoder);
|
||||||
|
if (!vs->encoder_ctx) {
|
||||||
|
LOGC("Could not allocate codec context for v4l2");
|
||||||
|
goto error_avio_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->encoder_ctx->width = vs->frame_size.width;
|
||||||
|
vs->encoder_ctx->height = vs->frame_size.height;
|
||||||
|
vs->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
|
vs->encoder_ctx->time_base.num = 1;
|
||||||
|
vs->encoder_ctx->time_base.den = 1;
|
||||||
|
|
||||||
|
if (avcodec_open2(vs->encoder_ctx, encoder, NULL) < 0) {
|
||||||
|
LOGE("Could not open codec for v4l2");
|
||||||
|
goto error_avcodec_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->frame = av_frame_alloc();
|
||||||
|
if (!vs->frame) {
|
||||||
|
LOGE("Could not create v4l2 frame");
|
||||||
|
goto error_avcodec_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Starting v4l2 thread");
|
||||||
|
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not start v4l2 thread");
|
||||||
|
goto error_av_frame_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGI("v4l2 sink started to device: %s", vs->device_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_av_frame_free:
|
||||||
|
av_frame_free(&vs->frame);
|
||||||
|
error_avcodec_close:
|
||||||
|
avcodec_close(vs->encoder_ctx);
|
||||||
|
error_avcodec_free_context:
|
||||||
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
|
error_avio_close:
|
||||||
|
avio_close(vs->format_ctx->pb);
|
||||||
|
error_avformat_free_context:
|
||||||
|
avformat_free_context(vs->format_ctx);
|
||||||
|
error_cond_destroy:
|
||||||
|
sc_cond_destroy(&vs->cond);
|
||||||
|
error_mutex_destroy:
|
||||||
|
sc_mutex_destroy(&vs->mutex);
|
||||||
|
error_video_buffer_destroy:
|
||||||
|
video_buffer_destroy(&vs->vb);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||||
|
av_frame_free(&vs->frame);
|
||||||
|
avcodec_close(vs->encoder_ctx);
|
||||||
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
|
avio_close(vs->format_ctx->pb);
|
||||||
|
avformat_free_context(vs->format_ctx);
|
||||||
|
sc_cond_destroy(&vs->cond);
|
||||||
|
sc_mutex_destroy(&vs->mutex);
|
||||||
|
video_buffer_destroy(&vs->vb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||||
|
bool ok = video_buffer_push(&vs->vb, frame, NULL);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// signal possible change of vs->vb.pending_frame_consumed
|
||||||
|
sc_cond_signal(&vs->cond);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink) {
|
||||||
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
|
return sc_v4l2_sink_open(vs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_v4l2_frame_sink_close(struct sc_frame_sink *sink) {
|
||||||
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
|
sc_v4l2_sink_close(vs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
|
return sc_v4l2_sink_push(vs, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
|
struct size frame_size) {
|
||||||
|
vs->device_name = strdup(device_name);
|
||||||
|
if (!vs->device_name) {
|
||||||
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->frame_size = frame_size;
|
||||||
|
|
||||||
|
static const struct sc_frame_sink_ops ops = {
|
||||||
|
.open = sc_v4l2_frame_sink_open,
|
||||||
|
.close = sc_v4l2_frame_sink_close,
|
||||||
|
.push = sc_v4l2_frame_sink_push,
|
||||||
|
};
|
||||||
|
|
||||||
|
vs->frame_sink.ops = &ops;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs) {
|
||||||
|
free(vs->device_name);
|
||||||
|
}
|
||||||
38
app/src/v4l2_sink.h
Normal file
38
app/src/v4l2_sink.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef SC_V4L2_SINK_H
|
||||||
|
#define SC_V4L2_SINK_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "coords.h"
|
||||||
|
#include "trait/frame_sink.h"
|
||||||
|
#include "video_buffer.h"
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
struct sc_v4l2_sink {
|
||||||
|
struct sc_frame_sink frame_sink; // frame sink trait
|
||||||
|
|
||||||
|
struct video_buffer vb;
|
||||||
|
AVFormatContext *format_ctx;
|
||||||
|
AVCodecContext *encoder_ctx;
|
||||||
|
|
||||||
|
char *device_name;
|
||||||
|
struct size frame_size;
|
||||||
|
|
||||||
|
sc_thread thread;
|
||||||
|
sc_mutex mutex;
|
||||||
|
sc_cond cond;
|
||||||
|
bool stopped;
|
||||||
|
|
||||||
|
AVFrame *frame;
|
||||||
|
AVPacket packet;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
|
struct size frame_size);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -146,14 +146,18 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
static void test_serialize_back_or_screen_on(void) {
|
static void test_serialize_back_or_screen_on(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
.back_or_screen_on = {
|
||||||
|
.action = AKEY_EVENT_ACTION_UP,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 2);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
0x01, // AKEY_EVENT_ACTION_UP
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,6 +287,18 @@ static void test_parse_integer_with_suffix(void) {
|
|||||||
assert(!ok);
|
assert(!ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_strlist_contains(void) {
|
||||||
|
assert(strlist_contains("a,bc,def", ',', "bc"));
|
||||||
|
assert(!strlist_contains("a,bc,def", ',', "b"));
|
||||||
|
assert(strlist_contains("", ',', ""));
|
||||||
|
assert(strlist_contains("abc,", ',', ""));
|
||||||
|
assert(strlist_contains(",abc", ',', ""));
|
||||||
|
assert(strlist_contains("abc,,def", ',', ""));
|
||||||
|
assert(!strlist_contains("abc", ',', ""));
|
||||||
|
assert(strlist_contains(",,|x", '|', ",,"));
|
||||||
|
assert(strlist_contains("xyz", '\0', "xyz"));
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@@ -304,5 +316,6 @@ int main(int argc, char *argv[]) {
|
|||||||
test_parse_integer();
|
test_parse_integer();
|
||||||
test_parse_integers();
|
test_parse_integers();
|
||||||
test_parse_integer_with_suffix();
|
test_parse_integer_with_suffix();
|
||||||
|
test_strlist_contains();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createBackOrScreenOn(int action) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
msg.action = action;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_CLIPBOARD;
|
msg.type = TYPE_SET_CLIPBOARD;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class ControlMessageReader {
|
|||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
|
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
@@ -66,13 +67,15 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
msg = parseInjectScrollEvent();
|
msg = parseInjectScrollEvent();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
msg = parseBackOrScreenOnEvent();
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
msg = parseSetClipboard();
|
msg = parseSetClipboard();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
msg = parseSetScreenPowerMode();
|
msg = parseSetScreenPowerMode();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
@@ -150,6 +153,14 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseBackOrScreenOnEvent() {
|
||||||
|
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int action = toUnsigned(buffer.get());
|
||||||
|
return ControlMessage.createBackOrScreenOn(action);
|
||||||
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
private ControlMessage parseSetClipboard() {
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class Controller {
|
|||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
pressBackOrTurnScreenOn();
|
pressBackOrTurnScreenOn(msg.getAction());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
@@ -255,12 +255,22 @@ public class Controller {
|
|||||||
}, 200, TimeUnit.MILLISECONDS);
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn() {
|
private boolean pressBackOrTurnScreenOn(int action) {
|
||||||
int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
if (Device.isScreenOn()) {
|
||||||
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) {
|
return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen is off
|
||||||
|
// Only press POWER on ACTION_DOWN
|
||||||
|
if (action != KeyEvent.ACTION_DOWN) {
|
||||||
|
// do nothing,
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepPowerModeOff) {
|
||||||
schedulePowerModeOff();
|
schedulePowerModeOff();
|
||||||
}
|
}
|
||||||
return device.injectKeycode(keycode);
|
return device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setClipboard(String text, boolean paste) {
|
private boolean setClipboard(String text, boolean paste) {
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ public class ControlMessageReaderTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
|
||||||
|
dos.writeByte(KeyEvent.ACTION_UP);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
@@ -161,6 +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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user