Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
github_28344065
scrcpy
提交
6ac237c7
S
scrcpy
项目概览
github_28344065
/
scrcpy
与 Fork 源项目一致
从无法访问的项目Fork
通知
8
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
scrcpy
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
6ac237c7
编写于
4月 16, 2021
作者:
R
Romain Vimont
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
v4l2wip
上级
e9228892
变更
8
隐藏空白更改
内联
并排
Showing
8 changed file
with
371 addition
and
3 deletion
+371
-3
app/meson.build
app/meson.build
+12
-0
app/src/cli.c
app/src/cli.c
+26
-2
app/src/decoder.h
app/src/decoder.h
+1
-1
app/src/main.c
app/src/main.c
+14
-0
app/src/scrcpy.c
app/src/scrcpy.c
+27
-0
app/src/scrcpy.h
app/src/scrcpy.h
+2
-0
app/src/v4l2_sink.c
app/src/v4l2_sink.c
+251
-0
app/src/v4l2_sink.h
app/src/v4l2_sink.h
+38
-0
未找到文件。
app/meson.build
浏览文件 @
6ac237c7
...
...
@@ -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'
]
...
...
@@ -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)
...
...
@@ -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')
...
...
app/src/cli.c
浏览文件 @
6ac237c7
...
...
@@ -173,6 +173,11 @@ scrcpy_print_usage(const char *arg0) {
" on exit.
\n
"
" It only shows physical touches (not clicks from scrcpy).
\n
"
"
\n
"
" --v4l2_sink /dev/videoN
\n
"
" Output to v4l2loopback device.
\n
"
" It requires to lock the video orientation (see
\n
"
" --lock-video-orientation).
\n
"
"
\n
"
" -V, --verbosity value
\n
"
" Set the log level (debug, info, warn or error).
\n
"
#ifndef NDEBUG
...
...
@@ -661,6 +666,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
[])
{
...
...
@@ -702,6 +708,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
},
...
...
@@ -885,17 +894,32 @@ 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
;
case
OPT_V4L2_SINK
:
opts
->
v4l2_device
=
optarg
;
break
;
default:
// getopt prints the error message on stderr
return
false
;
}
}
if
(
!
opts
->
display
&&
!
opts
->
record_filename
)
{
LOGE
(
"-N/--no-display requires screen recording (-r/--record)"
);
if
(
!
opts
->
display
&&
!
opts
->
record_filename
&&
!
opts
->
v4l2_device
)
{
LOGE
(
"-N/--no-display requires either screen recording (-r/--record)"
#ifdef HAVE_V4L2
" or sink to v4l2loopback device (--v4l2_sink)"
#endif
);
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
;
if
(
index
<
argc
)
{
LOGE
(
"Unexpected additional argument: %s"
,
argv
[
index
]);
...
...
app/src/decoder.h
浏览文件 @
6ac237c7
...
...
@@ -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
...
...
app/src/main.c
浏览文件 @
6ac237c7
...
...
@@ -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>
...
...
@@ -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
...
...
@@ -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
;
}
...
...
app/src/scrcpy.c
浏览文件 @
6ac237c7
...
...
@@ -27,6 +27,9 @@
#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
;
...
...
@@ -34,6 +37,9 @@ 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
;
...
...
@@ -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
;
...
...
@@ -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
// start the stream
if
(
!
stream_start
(
&
stream
))
{
...
...
@@ -426,6 +447,12 @@ end:
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
)
{
...
...
app/src/scrcpy.h
浏览文件 @
6ac237c7
...
...
@@ -52,6 +52,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
;
...
...
@@ -93,6 +94,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 = { \
...
...
app/src/v4l2_sink.c
0 → 100644
浏览文件 @
6ac237c7
#include "v4l2_sink.h"
#include "util/log.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 with name "mp4"
}
while
(
oformat
&&
strcmp
(
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
&&
ret
!=
AVERROR
(
EAGAIN
))
{
LOGE
(
"Could not receive v4l2 video packet: %d"
,
ret
);
return
false
;
}
av_write_frame
(
vs
->
format_ctx
,
packet
);
av_packet_unref
(
packet
);
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
=
sc_mutex_init
(
&
vs
->
mutex
);
if
(
!
ok
)
{
LOGC
(
"Could not create mutex"
);
return
false
;
}
ok
=
sc_cond_init
(
&
vs
->
cond
);
if
(
!
ok
)
{
LOGC
(
"Could not create cond"
);
goto
error_mutex_destroy
;
}
const
AVOutputFormat
*
format
=
find_muxer
(
"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
;
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
);
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
);
}
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
);
}
app/src/v4l2_sink.h
0 → 100644
浏览文件 @
6ac237c7
#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
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录