Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dce6abf20 | ||
|
|
dd4c5d3820 | ||
|
|
fa1178195e | ||
|
|
5dcce5f241 | ||
|
|
3f58dd22dc | ||
|
|
cbd75ff1fb | ||
|
|
39cdf68150 | ||
|
|
3492c5c963 | ||
|
|
35f4040a78 | ||
|
|
fd7d6f3822 | ||
|
|
6c3e7f6b45 | ||
|
|
94d90927ad | ||
|
|
93504f5f34 | ||
|
|
a52b1f025d | ||
|
|
46759d3f63 | ||
|
|
e37f9a4d7c | ||
|
|
842fdccaa9 | ||
|
|
8cdae30b01 | ||
|
|
30b8d140e8 | ||
|
|
4f986d4bbb |
@@ -21,7 +21,6 @@ src = [
|
|||||||
'src/mouse_inject.c',
|
'src/mouse_inject.c',
|
||||||
'src/opengl.c',
|
'src/opengl.c',
|
||||||
'src/options.c',
|
'src/options.c',
|
||||||
'src/packet_merger.c',
|
|
||||||
'src/receiver.c',
|
'src/receiver.c',
|
||||||
'src/recorder.c',
|
'src/recorder.c',
|
||||||
'src/scrcpy.c',
|
'src/scrcpy.c',
|
||||||
@@ -201,6 +200,10 @@ conf.set('PORTABLE', get_option('portable'))
|
|||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
||||||
|
|
||||||
|
# the default video bitrate, in bits/second
|
||||||
|
# overridden by option --bit-rate
|
||||||
|
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
||||||
|
|
||||||
# run a server debugger and wait for a client to be attached
|
# run a server debugger and wait for a client to be attached
|
||||||
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||||
|
|
||||||
|
|||||||
22
app/scrcpy.1
22
app/scrcpy.1
@@ -19,34 +19,16 @@ provides display and control of Android devices connected on USB (or over TCP/IP
|
|||||||
.B \-\-always\-on\-top
|
.B \-\-always\-on\-top
|
||||||
Make scrcpy window always on top (above other windows).
|
Make scrcpy window always on top (above other windows).
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-audio\-bit\-rate " value
|
|
||||||
Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
|
||||||
|
|
||||||
Default is 196K (196000).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-audio\-codec " name
|
|
||||||
Select an audio codec (opus or aac).
|
|
||||||
|
|
||||||
Default is opus.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-audio\-encoder " name
|
|
||||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-b, \-\-bit\-rate " value
|
.BI "\-b, \-\-bit\-rate " value
|
||||||
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||||
|
|
||||||
Default is 8M (8000000).
|
Default is 8000000.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-codec " name
|
.BI "\-\-codec " name
|
||||||
Select a video codec (h264, h265 or av1).
|
Select a video codec (h264, h265 or av1).
|
||||||
|
|
||||||
Default is h264.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...]
|
.BI "\-\-codec\-options " key\fR[:\fItype\fR]=\fIvalue\fR[,...]
|
||||||
Set a list of comma-separated key:type=value options for the device encoder.
|
Set a list of comma-separated key:type=value options for the device encoder.
|
||||||
@@ -98,7 +80,7 @@ Also see \fB\-d\fR (\fB\-\-select\-usb\fR).
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-encoder " name
|
.BI "\-\-encoder " name
|
||||||
Use a specific MediaCodec encoder (depending on the codec provided by \fB\-\-codec\fR).
|
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-force\-adb\-forward
|
.B \-\-force\-adb\-forward
|
||||||
|
|||||||
@@ -57,11 +57,8 @@
|
|||||||
#define OPT_NO_CLEANUP 1037
|
#define OPT_NO_CLEANUP 1037
|
||||||
#define OPT_PRINT_FPS 1038
|
#define OPT_PRINT_FPS 1038
|
||||||
#define OPT_NO_POWER_ON 1039
|
#define OPT_NO_POWER_ON 1039
|
||||||
#define OPT_VIDEO_CODEC 1040
|
#define OPT_CODEC 1040
|
||||||
#define OPT_NO_AUDIO 1041
|
#define OPT_NO_AUDIO 1041
|
||||||
#define OPT_AUDIO_BIT_RATE 1042
|
|
||||||
#define OPT_AUDIO_CODEC 1043
|
|
||||||
#define OPT_AUDIO_ENCODER_NAME 1044
|
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
char shortopt;
|
char shortopt;
|
||||||
@@ -102,43 +99,19 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "always-on-top",
|
.longopt = "always-on-top",
|
||||||
.text = "Make scrcpy window always on top (above other windows).",
|
.text = "Make scrcpy window always on top (above other windows).",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_AUDIO_BIT_RATE,
|
|
||||||
.longopt = "audio-bit-rate",
|
|
||||||
.argdesc = "value",
|
|
||||||
.text = "Encode the audio at the given bit-rate, expressed in bits/s. "
|
|
||||||
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
|
||||||
"Default is 196K (196000).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt_id = OPT_AUDIO_CODEC,
|
|
||||||
.longopt = "audio-codec",
|
|
||||||
.argdesc = "name",
|
|
||||||
.text = "Select an audio codec (opus or aac).\n"
|
|
||||||
"Default is opus.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt_id = OPT_AUDIO_ENCODER_NAME,
|
|
||||||
.longopt = "audio-encoder",
|
|
||||||
.argdesc = "name",
|
|
||||||
.text = "Use a specific MediaCodec audio encoder (depending on the "
|
|
||||||
"codec provided by --audio-codec).",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.shortopt = 'b',
|
.shortopt = 'b',
|
||||||
.longopt = "bit-rate",
|
.longopt = "bit-rate",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
.text = "Encode the video at the given bit-rate, expressed in bits/s. "
|
.text = "Encode the video at the gitven bit-rate, expressed in bits/s. "
|
||||||
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
"Default is 8M (8000000).",
|
"Default is " STR(DEFAULT_BIT_RATE) ".",
|
||||||
},
|
},
|
||||||
// TODO keep OPT_CODEC to avoid partial matching with codec_options
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_VIDEO_CODEC,
|
.longopt_id = OPT_CODEC,
|
||||||
.longopt = "video-codec",
|
.longopt = "codec",
|
||||||
.argdesc = "name",
|
.argdesc = "name",
|
||||||
.text = "Select a video codec (h264, h265 or av1).\n"
|
.text = "Select a video codec (h264, h265 or av1).",
|
||||||
"Default is h264.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CODEC_OPTIONS,
|
.longopt_id = OPT_CODEC_OPTIONS,
|
||||||
@@ -200,8 +173,7 @@ static const struct sc_option options[] = {
|
|||||||
.longopt_id = OPT_ENCODER_NAME,
|
.longopt_id = OPT_ENCODER_NAME,
|
||||||
.longopt = "encoder",
|
.longopt = "encoder",
|
||||||
.argdesc = "name",
|
.argdesc = "name",
|
||||||
.text = "Use a specific MediaCodec encoder (depending on the codec "
|
.text = "Use a specific MediaCodec encoder (must be a H.264 encoder).",
|
||||||
"provided by --codec).",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
.longopt_id = OPT_FORCE_ADB_FORWARD,
|
||||||
@@ -1419,7 +1391,7 @@ guess_record_format(const char *filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_video_codec(const char *optarg, enum sc_codec *codec) {
|
parse_codec(const char *optarg, enum sc_codec *codec) {
|
||||||
if (!strcmp(optarg, "h264")) {
|
if (!strcmp(optarg, "h264")) {
|
||||||
*codec = SC_CODEC_H264;
|
*codec = SC_CODEC_H264;
|
||||||
return true;
|
return true;
|
||||||
@@ -1432,21 +1404,7 @@ parse_video_codec(const char *optarg, enum sc_codec *codec) {
|
|||||||
*codec = SC_CODEC_AV1;
|
*codec = SC_CODEC_AV1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LOGE("Unsupported video codec: %s (expected h264, h265 or av1)", optarg);
|
LOGE("Unsupported codec: %s (expected h264, h265 or av1)", optarg);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_audio_codec(const char *optarg, enum sc_codec *codec) {
|
|
||||||
if (!strcmp(optarg, "opus")) {
|
|
||||||
*codec = SC_CODEC_OPUS;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!strcmp(optarg, "aac")) {
|
|
||||||
*codec = SC_CODEC_AAC;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
LOGE("Unsupported audio codec: %s (expected opus)", optarg);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1465,11 +1423,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_AUDIO_BIT_RATE:
|
|
||||||
if (!parse_bit_rate(optarg, &opts->audio_bit_rate)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_CROP:
|
case OPT_CROP:
|
||||||
opts->crop = optarg;
|
opts->crop = optarg;
|
||||||
break;
|
break;
|
||||||
@@ -1644,9 +1597,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_ENCODER_NAME:
|
case OPT_ENCODER_NAME:
|
||||||
opts->encoder_name = optarg;
|
opts->encoder_name = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_AUDIO_ENCODER_NAME:
|
|
||||||
opts->audio_encoder_name = optarg;
|
|
||||||
break;
|
|
||||||
case OPT_FORCE_ADB_FORWARD:
|
case OPT_FORCE_ADB_FORWARD:
|
||||||
opts->force_adb_forward = true;
|
opts->force_adb_forward = true;
|
||||||
break;
|
break;
|
||||||
@@ -1694,13 +1644,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_PRINT_FPS:
|
case OPT_PRINT_FPS:
|
||||||
opts->start_fps_counter = true;
|
opts->start_fps_counter = true;
|
||||||
break;
|
break;
|
||||||
case OPT_VIDEO_CODEC:
|
case OPT_CODEC:
|
||||||
if (!parse_video_codec(optarg, &opts->video_codec)) {
|
if (!parse_codec(optarg, &opts->codec)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_AUDIO_CODEC:
|
|
||||||
if (!parse_audio_codec(optarg, &opts->audio_codec)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1812,6 +1757,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->record_format == SC_RECORD_FORMAT_MP4
|
||||||
|
&& opts->codec == SC_CODEC_AV1) {
|
||||||
|
LOGE("Could not mux AV1 stream into MP4 container "
|
||||||
|
"(record to mkv or select another video codec)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!opts->control) {
|
if (!opts->control) {
|
||||||
if (opts->turn_screen_off) {
|
if (opts->turn_screen_off) {
|
||||||
LOGE("Could not request to turn screen off if control is disabled");
|
LOGE("Could not request to turn screen off if control is disabled");
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
|||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = sc_receiver_init(&controller->receiver, control_socket, acksync);
|
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_mutex_init(&controller->mutex);
|
ok = sc_mutex_init(&controller->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&controller->msg_cond);
|
ok = sc_cond_init(&controller->msg_cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
sc_mutex_destroy(&controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ sc_controller_destroy(struct sc_controller *controller) {
|
|||||||
sc_control_msg_destroy(&msg);
|
sc_control_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -117,7 +117,7 @@ sc_controller_start(struct sc_controller *controller) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_receiver_start(&controller->receiver)) {
|
if (!receiver_start(&controller->receiver)) {
|
||||||
sc_controller_stop(controller);
|
sc_controller_stop(controller);
|
||||||
sc_thread_join(&controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
return false;
|
return false;
|
||||||
@@ -137,5 +137,5 @@ sc_controller_stop(struct sc_controller *controller) {
|
|||||||
void
|
void
|
||||||
sc_controller_join(struct sc_controller *controller) {
|
sc_controller_join(struct sc_controller *controller) {
|
||||||
sc_thread_join(&controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
sc_receiver_join(&controller->receiver);
|
receiver_join(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct sc_controller {
|
|||||||
sc_cond msg_cond;
|
sc_cond msg_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_control_msg_queue queue;
|
struct sc_control_msg_queue queue;
|
||||||
struct sc_receiver receiver;
|
struct receiver receiver;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ sc_decoder_open_sinks(struct sc_decoder *decoder) {
|
|||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->open(sink)) {
|
if (!sink->ops->open(sink)) {
|
||||||
|
LOGE("Could not open frame sink %d", i);
|
||||||
sc_decoder_close_first_sinks(decoder, i);
|
sc_decoder_close_first_sinks(decoder, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -62,6 +63,7 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_decoder_open_sinks(decoder)) {
|
if (!sc_decoder_open_sinks(decoder)) {
|
||||||
|
LOGE("Could not open decoder sinks");
|
||||||
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);
|
||||||
@@ -84,6 +86,7 @@ push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
|||||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
if (!sink->ops->push(sink, frame)) {
|
if (!sink->ops->push(sink, frame)) {
|
||||||
|
LOGE("Could not send frame to sink %d", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "packet_merger.h"
|
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "util/binary.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
@@ -19,12 +18,17 @@
|
|||||||
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
||||||
|
|
||||||
static enum AVCodecID
|
static enum AVCodecID
|
||||||
sc_demuxer_to_avcodec_id(uint32_t codec_id) {
|
sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer) {
|
||||||
|
uint8_t data[4];
|
||||||
|
ssize_t r = net_recv_all(demuxer->socket, data, 4);
|
||||||
|
if (r < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII
|
#define SC_CODEC_ID_H264 UINT32_C(0x68323634) // "h264" in ASCII
|
||||||
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
|
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
|
||||||
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
|
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
|
||||||
#define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII
|
uint32_t codec_id = sc_read32be(data);
|
||||||
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII"
|
|
||||||
switch (codec_id) {
|
switch (codec_id) {
|
||||||
case SC_CODEC_ID_H264:
|
case SC_CODEC_ID_H264:
|
||||||
return AV_CODEC_ID_H264;
|
return AV_CODEC_ID_H264;
|
||||||
@@ -32,28 +36,12 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) {
|
|||||||
return AV_CODEC_ID_HEVC;
|
return AV_CODEC_ID_HEVC;
|
||||||
case SC_CODEC_ID_AV1:
|
case SC_CODEC_ID_AV1:
|
||||||
return AV_CODEC_ID_AV1;
|
return AV_CODEC_ID_AV1;
|
||||||
case SC_CODEC_ID_OPUS:
|
|
||||||
return AV_CODEC_ID_OPUS;
|
|
||||||
case SC_CODEC_ID_AAC:
|
|
||||||
return AV_CODEC_ID_AAC;
|
|
||||||
default:
|
default:
|
||||||
LOGE("Unknown codec id 0x%08" PRIx32, codec_id);
|
LOGE("Unknown codec id 0x%08" PRIx32, codec_id);
|
||||||
return AV_CODEC_ID_NONE;
|
return AV_CODEC_ID_NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
|
||||||
uint8_t data[4];
|
|
||||||
ssize_t r = net_recv_all(demuxer->socket, data, 4);
|
|
||||||
if (r < 4) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*codec_id = sc_read32be(data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||||
// The video stream contains raw packets, without time information. When we
|
// The video stream contains raw packets, without time information. When we
|
||||||
@@ -117,6 +105,7 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
|||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
if (!sink->ops->push(sink, packet)) {
|
if (!sink->ops->push(sink, packet)) {
|
||||||
|
LOGE("Could not send config packet to sink %d", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,9 +115,50 @@ push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||||
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
|
|
||||||
|
// A config packet must not be decoded immediately (it contains no
|
||||||
|
// frame); instead, it must be concatenated with the future data packet.
|
||||||
|
if (demuxer->pending || is_config) {
|
||||||
|
if (demuxer->pending) {
|
||||||
|
size_t offset = demuxer->pending->size;
|
||||||
|
if (av_grow_packet(demuxer->pending, packet->size)) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(demuxer->pending->data + offset, packet->data, packet->size);
|
||||||
|
} else {
|
||||||
|
demuxer->pending = av_packet_alloc();
|
||||||
|
if (!demuxer->pending) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (av_packet_ref(demuxer->pending, packet)) {
|
||||||
|
LOG_OOM();
|
||||||
|
av_packet_free(&demuxer->pending);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_config) {
|
||||||
|
// prepare the concat packet to send to the decoder
|
||||||
|
demuxer->pending->pts = packet->pts;
|
||||||
|
demuxer->pending->dts = packet->dts;
|
||||||
|
demuxer->pending->flags = packet->flags;
|
||||||
|
packet = demuxer->pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ok = push_packet_to_sinks(demuxer, packet);
|
bool ok = push_packet_to_sinks(demuxer, packet);
|
||||||
|
|
||||||
|
if (!is_config && demuxer->pending) {
|
||||||
|
// the pending packet must be discarded (consumed or error)
|
||||||
|
av_packet_free(&demuxer->pending);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Demuxer '%s': could not process packet", demuxer->name);
|
LOGE("Could not process packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +183,7 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
|||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||||
if (!sink->ops->open(sink, codec)) {
|
if (!sink->ops->open(sink, codec)) {
|
||||||
|
LOGE("Could not open packet sink %d", i);
|
||||||
sc_demuxer_close_first_sinks(demuxer, i);
|
sc_demuxer_close_first_sinks(demuxer, i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -161,93 +192,56 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
sc_demuxer_disable_sinks(struct sc_demuxer *demuxer) {
|
|
||||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
|
||||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
|
||||||
if (sink->ops->disable) {
|
|
||||||
sink->ops->disable(sink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_demuxer(void *data) {
|
run_demuxer(void *data) {
|
||||||
struct sc_demuxer *demuxer = data;
|
struct sc_demuxer *demuxer = data;
|
||||||
|
|
||||||
// Flag to report end-of-stream (i.e. device disconnected)
|
enum AVCodecID codec_id = sc_demuxer_recv_codec_id(demuxer);
|
||||||
bool eos = false;
|
|
||||||
|
|
||||||
uint32_t raw_codec_id;
|
|
||||||
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Demuxer '%s': stream disabled due to connection error",
|
|
||||||
demuxer->name);
|
|
||||||
eos = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (raw_codec_id == 0) {
|
|
||||||
LOGW("Demuxer '%s': stream explicitly disabled by the device",
|
|
||||||
demuxer->name);
|
|
||||||
sc_demuxer_disable_sinks(demuxer);
|
|
||||||
eos = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id);
|
|
||||||
if (codec_id == AV_CODEC_ID_NONE) {
|
if (codec_id == AV_CODEC_ID_NONE) {
|
||||||
LOGE("Demuxer '%s': stream disabled due to unsupported codec",
|
// Error already logged
|
||||||
demuxer->name);
|
|
||||||
sc_demuxer_disable_sinks(demuxer);
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVCodec *codec = avcodec_find_decoder(codec_id);
|
const AVCodec *codec = avcodec_find_decoder(codec_id);
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
LOGE("Demuxer '%s': stream disabled due to missing decoder",
|
LOGE("H.264 decoder not found");
|
||||||
demuxer->name);
|
goto end;
|
||||||
sc_demuxer_disable_sinks(demuxer);
|
}
|
||||||
|
|
||||||
|
demuxer->codec_ctx = avcodec_alloc_context3(codec);
|
||||||
|
if (!demuxer->codec_ctx) {
|
||||||
|
LOG_OOM();
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
||||||
goto end;
|
LOGE("Could not open demuxer sinks");
|
||||||
|
goto finally_free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config packets must be merged with the next non-config packet only for
|
demuxer->parser = av_parser_init(codec_id);
|
||||||
// video streams
|
if (!demuxer->parser) {
|
||||||
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO;
|
LOGE("Could not initialize parser");
|
||||||
|
goto finally_close_sinks;
|
||||||
struct sc_packet_merger merger;
|
|
||||||
|
|
||||||
if (must_merge_config_packet) {
|
|
||||||
sc_packet_merger_init(&merger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We must only pass complete frames to av_parser_parse2()!
|
||||||
|
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
||||||
|
demuxer->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
AVPacket *packet = av_packet_alloc();
|
||||||
if (!packet) {
|
if (!packet) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto finally_close_sinks;
|
goto finally_close_parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// end of stream
|
// end of stream
|
||||||
eos = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (must_merge_config_packet) {
|
|
||||||
// Prepend any config packet to the next media packet
|
|
||||||
ok = sc_packet_merger_merge(&merger, packet);
|
|
||||||
if (!ok) {
|
|
||||||
av_packet_unref(packet);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_demuxer_push_packet(demuxer, packet);
|
ok = sc_demuxer_push_packet(demuxer, packet);
|
||||||
av_packet_unref(packet);
|
av_packet_unref(packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -256,31 +250,35 @@ run_demuxer(void *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Demuxer '%s': end of frames", demuxer->name);
|
LOGD("End of frames");
|
||||||
|
|
||||||
if (must_merge_config_packet) {
|
if (demuxer->pending) {
|
||||||
sc_packet_merger_destroy(&merger);
|
av_packet_free(&demuxer->pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
|
finally_close_parser:
|
||||||
|
av_parser_close(demuxer->parser);
|
||||||
finally_close_sinks:
|
finally_close_sinks:
|
||||||
sc_demuxer_close_sinks(demuxer);
|
sc_demuxer_close_sinks(demuxer);
|
||||||
|
finally_free_codec_ctx:
|
||||||
|
avcodec_free_context(&demuxer->codec_ctx);
|
||||||
end:
|
end:
|
||||||
demuxer->cbs->on_ended(demuxer, eos, demuxer->cbs_userdata);
|
demuxer->cbs->on_eos(demuxer, demuxer->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata) {
|
||||||
assert(socket != SC_SOCKET_NONE);
|
assert(socket != SC_SOCKET_NONE);
|
||||||
|
|
||||||
demuxer->name = name; // statically allocated
|
|
||||||
demuxer->socket = socket;
|
demuxer->socket = socket;
|
||||||
|
demuxer->pending = NULL;
|
||||||
demuxer->sink_count = 0;
|
demuxer->sink_count = 0;
|
||||||
|
|
||||||
assert(cbs && cbs->on_ended);
|
assert(cbs && cbs->on_eos);
|
||||||
|
|
||||||
demuxer->cbs = cbs;
|
demuxer->cbs = cbs;
|
||||||
demuxer->cbs_userdata = cbs_userdata;
|
demuxer->cbs_userdata = cbs_userdata;
|
||||||
@@ -296,12 +294,12 @@ sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
||||||
LOGD("Demuxer '%s': starting thread", demuxer->name);
|
LOGD("Starting demuxer thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
bool ok = sc_thread_create(&demuxer->thread, run_demuxer, "scrcpy-demuxer",
|
||||||
demuxer);
|
demuxer);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Demuxer '%s': could not start thread", demuxer->name);
|
LOGE("Could not start demuxer thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,25 +15,28 @@
|
|||||||
#define SC_DEMUXER_MAX_SINKS 2
|
#define SC_DEMUXER_MAX_SINKS 2
|
||||||
|
|
||||||
struct sc_demuxer {
|
struct sc_demuxer {
|
||||||
const char *name; // must be statically allocated (e.g. a string literal)
|
|
||||||
|
|
||||||
sc_socket socket;
|
sc_socket socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
|
|
||||||
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
|
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
|
||||||
unsigned sink_count;
|
unsigned sink_count;
|
||||||
|
|
||||||
|
AVCodecContext *codec_ctx;
|
||||||
|
AVCodecParserContext *parser;
|
||||||
|
// successive packets may need to be concatenated, until a non-config
|
||||||
|
// packet is available
|
||||||
|
AVPacket *pending;
|
||||||
|
|
||||||
const struct sc_demuxer_callbacks *cbs;
|
const struct sc_demuxer_callbacks *cbs;
|
||||||
void *cbs_userdata;
|
void *cbs_userdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_demuxer_callbacks {
|
struct sc_demuxer_callbacks {
|
||||||
void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata);
|
void (*on_eos)(struct sc_demuxer *demuxer, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
// The name must be statically allocated (e.g. a string literal)
|
|
||||||
void
|
void
|
||||||
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
sc_demuxer_init(struct sc_demuxer *demuxer, sc_socket socket,
|
||||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
|
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||||
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||||
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||||
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
||||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
|
||||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.v4l2_device = NULL,
|
.v4l2_device = NULL,
|
||||||
#endif
|
#endif
|
||||||
.log_level = SC_LOG_LEVEL_INFO,
|
.log_level = SC_LOG_LEVEL_INFO,
|
||||||
.video_codec = SC_CODEC_H264,
|
.codec = SC_CODEC_H264,
|
||||||
.audio_codec = SC_CODEC_OPUS,
|
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
||||||
.port_range = {
|
.port_range = {
|
||||||
@@ -28,8 +27,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.count = 2,
|
.count = 2,
|
||||||
},
|
},
|
||||||
.max_size = 0,
|
.max_size = 0,
|
||||||
.bit_rate = 0,
|
.bit_rate = DEFAULT_BIT_RATE,
|
||||||
.audio_bit_rate = 0,
|
|
||||||
.max_fps = 0,
|
.max_fps = 0,
|
||||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
||||||
.rotation = 0,
|
.rotation = 0,
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ enum sc_codec {
|
|||||||
SC_CODEC_H264,
|
SC_CODEC_H264,
|
||||||
SC_CODEC_H265,
|
SC_CODEC_H265,
|
||||||
SC_CODEC_AV1,
|
SC_CODEC_AV1,
|
||||||
SC_CODEC_OPUS,
|
|
||||||
SC_CODEC_AAC,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_lock_video_orientation {
|
enum sc_lock_video_orientation {
|
||||||
@@ -97,13 +95,11 @@ 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 *audio_encoder_name;
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
const char *v4l2_device;
|
const char *v4l2_device;
|
||||||
#endif
|
#endif
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_codec video_codec;
|
enum sc_codec codec;
|
||||||
enum sc_codec audio_codec;
|
|
||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
enum sc_mouse_input_mode mouse_input_mode;
|
enum sc_mouse_input_mode mouse_input_mode;
|
||||||
@@ -113,7 +109,6 @@ struct scrcpy_options {
|
|||||||
struct sc_shortcut_mods shortcut_mods;
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
enum sc_lock_video_orientation lock_video_orientation;
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
uint8_t rotation;
|
uint8_t rotation;
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
#include "packet_merger.h"
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_packet_merger_init(struct sc_packet_merger *merger) {
|
|
||||||
merger->config = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_packet_merger_destroy(struct sc_packet_merger *merger) {
|
|
||||||
free(merger->config);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet) {
|
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
|
||||||
|
|
||||||
if (is_config) {
|
|
||||||
free(merger->config);
|
|
||||||
|
|
||||||
merger->config = malloc(packet->size);
|
|
||||||
if (!merger->config) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(merger->config, packet->data, packet->size);
|
|
||||||
merger->config_size = packet->size;
|
|
||||||
} else if (merger->config) {
|
|
||||||
size_t config_size = merger->config_size;
|
|
||||||
size_t media_size = packet->size;
|
|
||||||
|
|
||||||
if (av_grow_packet(packet, config_size)) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memmove(packet->data + config_size, packet->data, media_size);
|
|
||||||
memcpy(packet->data, merger->config, config_size);
|
|
||||||
|
|
||||||
free(merger->config);
|
|
||||||
merger->config = NULL;
|
|
||||||
// merger->size is meaningless when merger->config is NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#ifndef SC_PACKET_MERGER_H
|
|
||||||
#define SC_PACKET_MERGER_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Config packets (containing the SPS/PPS) are sent in-band. A new config
|
|
||||||
* packet is sent whenever a new encoding session is started (on start and on
|
|
||||||
* device orientation change).
|
|
||||||
*
|
|
||||||
* Every time a config packet is received, it must be sent alone (for recorder
|
|
||||||
* extradata), then concatenated to the next media packet (for correct decoding
|
|
||||||
* and recording).
|
|
||||||
*
|
|
||||||
* This helper reads every input packet and modifies each media packet which
|
|
||||||
* immediately follows a config packet to prepend the config packet payload.
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct sc_packet_merger {
|
|
||||||
uint8_t *config;
|
|
||||||
size_t config_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_packet_merger_init(struct sc_packet_merger *merger);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_packet_merger_destroy(struct sc_packet_merger *merger);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the packet is a config packet, then keep its data for later.
|
|
||||||
* Otherwise (if the packet is a media packet), then if a config packet is
|
|
||||||
* pending, prepend the config packet to this packet (so the packet is
|
|
||||||
* modified!).
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_packet_merger_merge(struct sc_packet_merger *merger, AVPacket *packet);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
bool ok = sc_mutex_init(&receiver->mutex);
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -21,12 +21,12 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_receiver_destroy(struct sc_receiver *receiver) {
|
receiver_destroy(struct receiver *receiver) {
|
||||||
sc_mutex_destroy(&receiver->mutex);
|
sc_mutex_destroy(&receiver->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_msg(struct sc_receiver *receiver, struct device_msg *msg) {
|
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
char *current = SDL_GetClipboardText();
|
char *current = SDL_GetClipboardText();
|
||||||
@@ -51,7 +51,7 @@ process_msg(struct sc_receiver *receiver, struct device_msg *msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len) {
|
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct device_msg msg;
|
struct device_msg msg;
|
||||||
@@ -76,7 +76,7 @@ process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len)
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
run_receiver(void *data) {
|
run_receiver(void *data) {
|
||||||
struct sc_receiver *receiver = data;
|
struct receiver *receiver = data;
|
||||||
|
|
||||||
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
@@ -108,7 +108,7 @@ run_receiver(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_start(struct sc_receiver *receiver) {
|
receiver_start(struct receiver *receiver) {
|
||||||
LOGD("Starting receiver thread");
|
LOGD("Starting receiver thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
||||||
@@ -122,6 +122,6 @@ sc_receiver_start(struct sc_receiver *receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_receiver_join(struct sc_receiver *receiver) {
|
receiver_join(struct receiver *receiver) {
|
||||||
sc_thread_join(&receiver->thread, NULL);
|
sc_thread_join(&receiver->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
// receive events from the device
|
// receive events from the device
|
||||||
// managed by the controller
|
// managed by the controller
|
||||||
struct sc_receiver {
|
struct receiver {
|
||||||
sc_socket control_socket;
|
sc_socket control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
@@ -20,18 +20,18 @@ struct sc_receiver {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
||||||
struct sc_acksync *acksync);
|
struct sc_acksync *acksync);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_receiver_destroy(struct sc_receiver *receiver);
|
receiver_destroy(struct receiver *receiver);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_start(struct sc_receiver *receiver);
|
receiver_start(struct receiver *receiver);
|
||||||
|
|
||||||
// no sc_receiver_stop(), it will automatically stop on control_socket shutdown
|
// no receiver_stop(), it will automatically stop on control_socket shutdown
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_receiver_join(struct sc_receiver *receiver);
|
receiver_join(struct receiver *receiver);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,11 +8,10 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
/** Downcast packet sinks to recorder */
|
/** Downcast packet_sink to recorder */
|
||||||
#define DOWNCAST_VIDEO(SINK) \
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_recorder, packet_sink)
|
||||||
container_of(SINK, struct sc_recorder, video_packet_sink)
|
|
||||||
#define DOWNCAST_AUDIO(SINK) \
|
#define SC_PTS_ORIGIN_NONE UINT64_C(-1)
|
||||||
container_of(SINK, struct sc_recorder, audio_packet_sink)
|
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
@@ -81,7 +80,9 @@ sc_recorder_get_format_name(enum sc_record_format format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_set_extradata(AVStream *ostream, const AVPacket *packet) {
|
sc_recorder_write_header(struct sc_recorder *recorder, const AVPacket *packet) {
|
||||||
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
|
|
||||||
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
if (!extradata) {
|
if (!extradata) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
@@ -93,56 +94,183 @@ sc_recorder_set_extradata(AVStream *ostream, const AVPacket *packet) {
|
|||||||
|
|
||||||
ostream->codecpar->extradata = extradata;
|
ostream->codecpar->extradata = extradata;
|
||||||
ostream->codecpar->extradata_size = packet->size;
|
ostream->codecpar->extradata_size = packet->size;
|
||||||
|
|
||||||
|
int ret = avformat_write_header(recorder->ctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write header to %s", recorder->filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static void
|
||||||
sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) {
|
sc_recorder_rescale_packet(struct sc_recorder *recorder, AVPacket *packet) {
|
||||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, stream->time_base);
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index,
|
sc_recorder_write(struct sc_recorder *recorder, AVPacket *packet) {
|
||||||
AVPacket *packet) {
|
if (!recorder->header_written) {
|
||||||
AVStream *stream = recorder->ctx->streams[stream_index];
|
if (packet->pts != AV_NOPTS_VALUE) {
|
||||||
sc_recorder_rescale_packet(stream, packet);
|
LOGE("The first packet is not a config packet");
|
||||||
return av_interleaved_write_frame(recorder->ctx, packet) >= 0;
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = sc_recorder_write_header(recorder, packet);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
recorder->header_written = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet->pts == AV_NOPTS_VALUE) {
|
||||||
|
// ignore config packets
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_recorder_rescale_packet(recorder, packet);
|
||||||
|
return av_write_frame(recorder->ctx, packet) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static int
|
||||||
sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) {
|
run_recorder(void *data) {
|
||||||
return sc_recorder_write_stream(recorder, recorder->video_stream_index,
|
struct sc_recorder *recorder = data;
|
||||||
packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool
|
for (;;) {
|
||||||
sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) {
|
sc_mutex_lock(&recorder->mutex);
|
||||||
return sc_recorder_write_stream(recorder, recorder->audio_stream_index,
|
|
||||||
packet);
|
while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
||||||
|
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if stopped is set, continue to process the remaining events (to
|
||||||
|
// finish the recording) before actually stopping
|
||||||
|
|
||||||
|
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) {
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
struct sc_record_packet *last = recorder->previous;
|
||||||
|
if (last) {
|
||||||
|
// assign an arbitrary duration to the last packet
|
||||||
|
last->packet->duration = 100000;
|
||||||
|
bool ok = sc_recorder_write(recorder, last->packet);
|
||||||
|
if (!ok) {
|
||||||
|
// 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
|
||||||
|
LOGW("Could not record last packet");
|
||||||
|
}
|
||||||
|
sc_record_packet_delete(last);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_record_packet *rec;
|
||||||
|
sc_queue_take(&recorder->queue, next, &rec);
|
||||||
|
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
if (recorder->pts_origin == SC_PTS_ORIGIN_NONE
|
||||||
|
&& rec->packet->pts != AV_NOPTS_VALUE) {
|
||||||
|
// First PTS received
|
||||||
|
recorder->pts_origin = rec->packet->pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rec->packet->pts != AV_NOPTS_VALUE) {
|
||||||
|
// Set PTS relatve to the origin
|
||||||
|
rec->packet->pts -= recorder->pts_origin;
|
||||||
|
rec->packet->dts = rec->packet->pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recorder->previous is only written from this thread, no need to lock
|
||||||
|
struct sc_record_packet *previous = recorder->previous;
|
||||||
|
recorder->previous = rec;
|
||||||
|
|
||||||
|
if (!previous) {
|
||||||
|
// we just received the first packet
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// config packets have no PTS, we must ignore them
|
||||||
|
if (rec->packet->pts != AV_NOPTS_VALUE
|
||||||
|
&& previous->packet->pts != AV_NOPTS_VALUE) {
|
||||||
|
// we now know the duration of the previous packet
|
||||||
|
previous->packet->duration =
|
||||||
|
rec->packet->pts - previous->packet->pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = sc_recorder_write(recorder, previous->packet);
|
||||||
|
sc_record_packet_delete(previous);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not record packet");
|
||||||
|
|
||||||
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
recorder->failed = true;
|
||||||
|
// discard pending packets
|
||||||
|
sc_recorder_queue_clear(&recorder->queue);
|
||||||
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recorder->failed) {
|
||||||
|
if (recorder->header_written) {
|
||||||
|
int ret = av_write_trailer(recorder->ctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||||
|
recorder->failed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the recorded file is empty
|
||||||
|
recorder->failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->failed) {
|
||||||
|
LOGE("Recording failed to %s", recorder->filename);
|
||||||
|
} else {
|
||||||
|
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
||||||
|
LOGI("Recording complete to %s file: %s", format_name,
|
||||||
|
recorder->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Recorder thread ended");
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_open_output_file(struct sc_recorder *recorder) {
|
sc_recorder_open(struct sc_recorder *recorder, const AVCodec *input_codec) {
|
||||||
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&recorder->queue_cond);
|
||||||
|
if (!ok) {
|
||||||
|
goto error_mutex_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_queue_init(&recorder->queue);
|
||||||
|
recorder->stopped = false;
|
||||||
|
recorder->failed = false;
|
||||||
|
recorder->header_written = false;
|
||||||
|
recorder->previous = NULL;
|
||||||
|
recorder->pts_origin = SC_PTS_ORIGIN_NONE;
|
||||||
|
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
const char *format_name = sc_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) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return false;
|
goto error_cond_destroy;
|
||||||
}
|
|
||||||
|
|
||||||
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
|
||||||
AVIO_FLAG_WRITE);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to open output file: %s", recorder->filename);
|
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
@@ -154,436 +282,71 @@ sc_recorder_open_output_file(struct sc_recorder *recorder) {
|
|||||||
av_dict_set(&recorder->ctx->metadata, "comment",
|
av_dict_set(&recorder->ctx->metadata, "comment",
|
||||||
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
||||||
|
|
||||||
|
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
||||||
|
if (!ostream) {
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
ostream->codecpar->codec_id = input_codec->id;
|
||||||
|
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||||
|
ostream->codecpar->width = recorder->declared_frame_size.width;
|
||||||
|
ostream->codecpar->height = recorder->declared_frame_size.height;
|
||||||
|
|
||||||
|
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
||||||
|
AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to open output file: %s", recorder->filename);
|
||||||
|
// ostream will be cleaned up during context cleaning
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Starting recorder thread");
|
||||||
|
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
|
||||||
|
recorder);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not start recorder thread");
|
||||||
|
goto error_avio_close;
|
||||||
|
}
|
||||||
|
|
||||||
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
return true;
|
||||||
sc_recorder_close_output_file(struct sc_recorder *recorder) {
|
|
||||||
|
error_avio_close:
|
||||||
avio_close(recorder->ctx->pb);
|
avio_close(recorder->ctx->pb);
|
||||||
|
error_avformat_free_context:
|
||||||
avformat_free_context(recorder->ctx);
|
avformat_free_context(recorder->ctx);
|
||||||
}
|
error_cond_destroy:
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
error_mutex_destroy:
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_recorder_wait_video_stream(struct sc_recorder *recorder) {
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
while (!recorder->video_codec && !recorder->stopped) {
|
|
||||||
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
|
|
||||||
}
|
|
||||||
const AVCodec *codec = recorder->video_codec;
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
if (codec) {
|
|
||||||
AVStream *stream = avformat_new_stream(recorder->ctx, codec);
|
|
||||||
if (!stream) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
stream->codecpar->codec_id = codec->id;
|
|
||||||
stream->codecpar->format = AV_PIX_FMT_YUV420P;
|
|
||||||
stream->codecpar->width = recorder->declared_frame_size.width;
|
|
||||||
stream->codecpar->height = recorder->declared_frame_size.height;
|
|
||||||
|
|
||||||
recorder->video_stream_index = stream->index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_recorder_wait_audio_stream(struct sc_recorder *recorder) {
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
while (!recorder->audio_codec && !recorder->audio_disabled
|
|
||||||
&& !recorder->stopped) {
|
|
||||||
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->audio_disabled) {
|
|
||||||
// Reset audio flag. From there, the recorder thread may access this
|
|
||||||
// flag without any mutex.
|
|
||||||
recorder->audio = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AVCodec *codec = recorder->audio_codec;
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
if (codec) {
|
|
||||||
AVStream *stream = avformat_new_stream(recorder->ctx, codec);
|
|
||||||
if (!stream) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
|
|
||||||
stream->codecpar->codec_id = codec->id;
|
|
||||||
stream->codecpar->ch_layout.nb_channels = 2;
|
|
||||||
stream->codecpar->sample_rate = 48000;
|
|
||||||
|
|
||||||
recorder->audio_stream_index = stream->index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool
|
|
||||||
sc_recorder_has_empty_queues(struct sc_recorder *recorder) {
|
|
||||||
if (sc_queue_is_empty(&recorder->video_queue)) {
|
|
||||||
// The video queue is empty
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->audio && sc_queue_is_empty(&recorder->audio_queue)) {
|
|
||||||
// The audio queue is empty (when audio is enabled)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No queue is empty
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_recorder_process_header(struct sc_recorder *recorder) {
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
|
|
||||||
while (!recorder->stopped && sc_recorder_has_empty_queues(recorder)) {
|
|
||||||
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) {
|
|
||||||
// If the recorder is stopped, don't process anything if there are not
|
|
||||||
// at least video packets
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_record_packet *video_pkt;
|
|
||||||
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
|
||||||
|
|
||||||
struct sc_record_packet *audio_pkt = NULL;
|
|
||||||
if (!sc_queue_is_empty(&recorder->audio_queue)) {
|
|
||||||
assert(recorder->audio);
|
|
||||||
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
int ret = false;
|
|
||||||
|
|
||||||
if (video_pkt->packet->pts != AV_NOPTS_VALUE) {
|
|
||||||
LOGE("The first video packet is not a config packet");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(recorder->video_stream_index >= 0);
|
|
||||||
AVStream *video_stream =
|
|
||||||
recorder->ctx->streams[recorder->video_stream_index];
|
|
||||||
bool ok = sc_recorder_set_extradata(video_stream, video_pkt->packet);
|
|
||||||
if (!ok) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio_pkt) {
|
|
||||||
if (audio_pkt->packet->pts != AV_NOPTS_VALUE) {
|
|
||||||
LOGE("The first audio packet is not a config packet");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(recorder->audio_stream_index >= 0);
|
|
||||||
AVStream *audio_stream =
|
|
||||||
recorder->ctx->streams[recorder->audio_stream_index];
|
|
||||||
ok = sc_recorder_set_extradata(audio_stream, audio_pkt->packet);
|
|
||||||
if (!ok) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = avformat_write_header(recorder->ctx, NULL) >= 0;
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Failed to write header to %s", recorder->filename);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = true;
|
|
||||||
|
|
||||||
end:
|
|
||||||
sc_record_packet_delete(video_pkt);
|
|
||||||
if (audio_pkt) {
|
|
||||||
sc_record_packet_delete(audio_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_recorder_process_packets(struct sc_recorder *recorder) {
|
|
||||||
int64_t pts_origin = AV_NOPTS_VALUE;
|
|
||||||
|
|
||||||
bool header_written = sc_recorder_process_header(recorder);
|
|
||||||
if (!header_written) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_record_packet *video_pkt = NULL;
|
|
||||||
struct sc_record_packet *audio_pkt = NULL;
|
|
||||||
|
|
||||||
// We can write a video packet only once we received the next one so that
|
|
||||||
// we can set its duration (next_pts - current_pts)
|
|
||||||
struct sc_record_packet *video_pkt_previous = NULL;
|
|
||||||
|
|
||||||
bool error = false;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
|
|
||||||
while (!recorder->stopped) {
|
|
||||||
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
|
||||||
// A new packet may be assigned to video_pkt and be processed
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (recorder->audio && !audio_pkt
|
|
||||||
&& !sc_queue_is_empty(&recorder->audio_queue)) {
|
|
||||||
// A new packet may be assigned to audio_pkt and be processed
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If stopped is set, continue to process the remaining events (to
|
|
||||||
// finish the recording) before actually stopping.
|
|
||||||
|
|
||||||
// If there is no audio, then the audio_queue will remain empty forever
|
|
||||||
// and audio_pkt will always be NULL.
|
|
||||||
assert(recorder->audio
|
|
||||||
|| (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue)));
|
|
||||||
|
|
||||||
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
|
||||||
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!audio_pkt && !sc_queue_is_empty(&recorder->audio_queue)) {
|
|
||||||
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->stopped && !video_pkt && !audio_pkt) {
|
|
||||||
assert(sc_queue_is_empty(&recorder->video_queue));
|
|
||||||
assert(sc_queue_is_empty(&recorder->audio_queue));
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(video_pkt || audio_pkt); // at least one
|
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
// Ignore further config packets (e.g. on device orientation
|
|
||||||
// change). The next non-config packet will have the config packet
|
|
||||||
// data prepended.
|
|
||||||
if (video_pkt && video_pkt->packet->pts == AV_NOPTS_VALUE) {
|
|
||||||
sc_record_packet_delete(video_pkt);
|
|
||||||
video_pkt = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio_pkt && audio_pkt->packet->pts == AV_NOPTS_VALUE) {
|
|
||||||
sc_record_packet_delete(audio_pkt);
|
|
||||||
audio_pkt= NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pts_origin == AV_NOPTS_VALUE) {
|
|
||||||
if (!recorder->audio) {
|
|
||||||
assert(video_pkt);
|
|
||||||
pts_origin = video_pkt->packet->pts;
|
|
||||||
} else if (video_pkt && audio_pkt) {
|
|
||||||
pts_origin =
|
|
||||||
MIN(video_pkt->packet->pts, audio_pkt->packet->pts);
|
|
||||||
} else if (recorder->stopped) {
|
|
||||||
if (video_pkt) {
|
|
||||||
// The recorder is stopped without audio, record the video
|
|
||||||
// packets
|
|
||||||
pts_origin = video_pkt->packet->pts;
|
|
||||||
} else {
|
|
||||||
// Fail if there is no video
|
|
||||||
error = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
// If the recorder is stopped while one of the streams has no
|
|
||||||
// packets, then we must avoid a live-loop and correctly record
|
|
||||||
// the stream having packets.
|
|
||||||
pts_origin = video_pkt ? video_pkt->packet->pts
|
|
||||||
: audio_pkt->packet->pts;
|
|
||||||
} else {
|
|
||||||
// We need both video and audio packets to initialize pts_origin
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(pts_origin != AV_NOPTS_VALUE);
|
|
||||||
|
|
||||||
if (video_pkt) {
|
|
||||||
video_pkt->packet->pts -= pts_origin;
|
|
||||||
video_pkt->packet->dts = video_pkt->packet->pts;
|
|
||||||
|
|
||||||
if (video_pkt_previous) {
|
|
||||||
// we now know the duration of the previous packet
|
|
||||||
video_pkt_previous->packet->duration =
|
|
||||||
video_pkt->packet->pts - video_pkt_previous->packet->pts;
|
|
||||||
|
|
||||||
bool ok = sc_recorder_write_video(recorder,
|
|
||||||
video_pkt_previous->packet);
|
|
||||||
sc_record_packet_delete(video_pkt_previous);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not record video packet");
|
|
||||||
error = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video_pkt_previous = video_pkt;
|
|
||||||
video_pkt = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio_pkt) {
|
|
||||||
audio_pkt->packet->pts -= pts_origin;
|
|
||||||
audio_pkt->packet->dts = audio_pkt->packet->pts;
|
|
||||||
|
|
||||||
bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not record audio packet");
|
|
||||||
error = true;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_record_packet_delete(audio_pkt);
|
|
||||||
audio_pkt = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the last video packet
|
|
||||||
struct sc_record_packet *last = video_pkt_previous;
|
|
||||||
if (last) {
|
|
||||||
// assign an arbitrary duration to the last packet
|
|
||||||
last->packet->duration = 100000;
|
|
||||||
bool ok = sc_recorder_write_video(recorder, last->packet);
|
|
||||||
if (!ok) {
|
|
||||||
// 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
|
|
||||||
LOGW("Could not record last packet");
|
|
||||||
}
|
|
||||||
sc_record_packet_delete(last);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = av_write_trailer(recorder->ctx);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
|
||||||
error = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
end:
|
|
||||||
if (video_pkt) {
|
|
||||||
sc_record_packet_delete(video_pkt);
|
|
||||||
}
|
|
||||||
if (audio_pkt) {
|
|
||||||
sc_record_packet_delete(audio_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !error;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_recorder_record(struct sc_recorder *recorder) {
|
|
||||||
bool ok = sc_recorder_open_output_file(recorder);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_recorder_wait_video_stream(recorder);
|
|
||||||
if (!ok) {
|
|
||||||
sc_recorder_close_output_file(recorder);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->audio) {
|
|
||||||
ok = sc_recorder_wait_audio_stream(recorder);
|
|
||||||
if (!ok) {
|
|
||||||
sc_recorder_close_output_file(recorder);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If recorder->stopped, process any queued packet anyway
|
|
||||||
|
|
||||||
ok = sc_recorder_process_packets(recorder);
|
|
||||||
sc_recorder_close_output_file(recorder);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_recorder(void *data) {
|
|
||||||
struct sc_recorder *recorder = data;
|
|
||||||
|
|
||||||
bool success = sc_recorder_record(recorder);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
// Prevent the producer to push any new packet
|
|
||||||
recorder->stopped = true;
|
|
||||||
// Discard pending packets
|
|
||||||
sc_recorder_queue_clear(&recorder->video_queue);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
const char *format_name = sc_recorder_get_format_name(recorder->format);
|
|
||||||
LOGI("Recording complete to %s file: %s", format_name,
|
|
||||||
recorder->filename);
|
|
||||||
} else {
|
|
||||||
LOGE("Recording failed to %s", recorder->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("Recorder thread ended");
|
|
||||||
|
|
||||||
recorder->cbs->on_ended(recorder, success, recorder->cbs_userdata);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
|
|
||||||
const AVCodec *codec) {
|
|
||||||
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
|
||||||
assert(codec);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
if (recorder->stopped) {
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder->video_codec = codec;
|
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) {
|
sc_recorder_close(struct sc_recorder *recorder) {
|
||||||
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
// EOS also stops the recorder
|
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
sc_thread_join(&recorder->thread, NULL);
|
||||||
|
|
||||||
|
avio_close(recorder->ctx->pb);
|
||||||
|
avformat_free_context(recorder->ctx);
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
sc_recorder_push(struct sc_recorder *recorder, const AVPacket *packet) {
|
||||||
const AVPacket *packet) {
|
|
||||||
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
assert(!recorder->stopped);
|
||||||
|
|
||||||
if (recorder->stopped) {
|
if (recorder->failed) {
|
||||||
// reject any new packet
|
// reject any new packet (this will stop the stream)
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -595,9 +358,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rec->packet->stream_index = 0;
|
sc_queue_push(&recorder->queue, next, rec);
|
||||||
|
|
||||||
sc_queue_push(&recorder->video_queue, next, rec);
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
@@ -605,189 +366,51 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
|
sc_recorder_packet_sink_open(struct sc_packet_sink *sink,
|
||||||
const AVCodec *codec) {
|
const AVCodec *codec) {
|
||||||
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
struct sc_recorder *recorder = DOWNCAST(sink);
|
||||||
assert(recorder->audio);
|
return sc_recorder_open(recorder, codec);
|
||||||
// only written from this thread, no need to lock
|
|
||||||
assert(!recorder->audio_disabled);
|
|
||||||
assert(codec);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
recorder->audio_codec = codec;
|
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
|
sc_recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
struct sc_recorder *recorder = DOWNCAST(sink);
|
||||||
assert(recorder->audio);
|
sc_recorder_close(recorder);
|
||||||
// only written from this thread, no need to lock
|
|
||||||
assert(!recorder->audio_disabled);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
// EOS also stops the recorder
|
|
||||||
recorder->stopped = true;
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
|
sc_recorder_packet_sink_push(struct sc_packet_sink *sink,
|
||||||
const AVPacket *packet) {
|
const AVPacket *packet) {
|
||||||
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
struct sc_recorder *recorder = DOWNCAST(sink);
|
||||||
assert(recorder->audio);
|
return sc_recorder_push(recorder, packet);
|
||||||
// only written from this thread, no need to lock
|
|
||||||
assert(!recorder->audio_disabled);
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
|
|
||||||
if (recorder->stopped) {
|
|
||||||
// reject any new packet
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
|
||||||
if (!rec) {
|
|
||||||
LOG_OOM();
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rec->packet->stream_index = 1;
|
|
||||||
|
|
||||||
sc_queue_push(&recorder->audio_queue, next, rec);
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
|
||||||
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
|
|
||||||
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
|
||||||
assert(recorder->audio);
|
|
||||||
// only written from this thread, no need to lock
|
|
||||||
assert(!recorder->audio_disabled);
|
|
||||||
assert(!recorder->audio_codec);
|
|
||||||
|
|
||||||
LOGW("Audio stream recording disabled");
|
|
||||||
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
recorder->audio_disabled = true;
|
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
sc_recorder_init(struct sc_recorder *recorder,
|
||||||
enum sc_record_format format, bool audio,
|
const char *filename,
|
||||||
struct sc_size declared_frame_size,
|
enum sc_record_format format,
|
||||||
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
|
struct sc_size declared_frame_size) {
|
||||||
recorder->filename = strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = sc_mutex_init(&recorder->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_free_filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->queue_cond);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_mutex_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&recorder->stream_cond);
|
|
||||||
if (!ok) {
|
|
||||||
goto error_queue_cond_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder->audio = audio;
|
|
||||||
|
|
||||||
sc_queue_init(&recorder->video_queue);
|
|
||||||
sc_queue_init(&recorder->audio_queue);
|
|
||||||
recorder->stopped = false;
|
|
||||||
|
|
||||||
recorder->video_codec = NULL;
|
|
||||||
recorder->audio_codec = NULL;
|
|
||||||
recorder->audio_disabled = false;
|
|
||||||
|
|
||||||
recorder->video_stream_index = -1;
|
|
||||||
recorder->audio_stream_index = -1;
|
|
||||||
|
|
||||||
recorder->format = format;
|
recorder->format = format;
|
||||||
recorder->declared_frame_size = declared_frame_size;
|
recorder->declared_frame_size = declared_frame_size;
|
||||||
|
|
||||||
assert(cbs && cbs->on_ended);
|
static const struct sc_packet_sink_ops ops = {
|
||||||
|
.open = sc_recorder_packet_sink_open,
|
||||||
recorder->cbs = cbs;
|
.close = sc_recorder_packet_sink_close,
|
||||||
recorder->cbs_userdata = cbs_userdata;
|
.push = sc_recorder_packet_sink_push,
|
||||||
|
|
||||||
static const struct sc_packet_sink_ops video_ops = {
|
|
||||||
.open = sc_recorder_video_packet_sink_open,
|
|
||||||
.close = sc_recorder_video_packet_sink_close,
|
|
||||||
.push = sc_recorder_video_packet_sink_push,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
recorder->video_packet_sink.ops = &video_ops;
|
recorder->packet_sink.ops = &ops;
|
||||||
|
|
||||||
if (audio) {
|
|
||||||
static const struct sc_packet_sink_ops audio_ops = {
|
|
||||||
.open = sc_recorder_audio_packet_sink_open,
|
|
||||||
.close = sc_recorder_audio_packet_sink_close,
|
|
||||||
.push = sc_recorder_audio_packet_sink_push,
|
|
||||||
.disable = sc_recorder_audio_packet_sink_disable,
|
|
||||||
};
|
|
||||||
|
|
||||||
recorder->audio_packet_sink.ops = &audio_ops;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_thread_create(&recorder->thread, run_recorder, "scrcpy-recorder",
|
|
||||||
recorder);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not start recorder thread");
|
|
||||||
goto error_stream_cond_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_stream_cond_destroy:
|
|
||||||
sc_cond_destroy(&recorder->stream_cond);
|
|
||||||
error_queue_cond_destroy:
|
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
error_mutex_destroy:
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
|
||||||
error_free_filename:
|
|
||||||
free(recorder->filename);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_recorder_stop(struct sc_recorder *recorder) {
|
|
||||||
sc_mutex_lock(&recorder->mutex);
|
|
||||||
recorder->stopped = true;
|
|
||||||
sc_cond_signal(&recorder->queue_cond);
|
|
||||||
sc_cond_signal(&recorder->stream_cond);
|
|
||||||
sc_mutex_unlock(&recorder->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_recorder_join(struct sc_recorder *recorder) {
|
|
||||||
sc_thread_join(&recorder->thread, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder) {
|
sc_recorder_destroy(struct sc_recorder *recorder) {
|
||||||
sc_cond_destroy(&recorder->stream_cond);
|
|
||||||
sc_cond_destroy(&recorder->queue_cond);
|
|
||||||
sc_mutex_destroy(&recorder->mutex);
|
|
||||||
free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,63 +20,34 @@ struct sc_record_packet {
|
|||||||
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
||||||
|
|
||||||
struct sc_recorder {
|
struct sc_recorder {
|
||||||
struct sc_packet_sink video_packet_sink;
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
struct sc_packet_sink audio_packet_sink;
|
|
||||||
|
|
||||||
/* The audio flag is unprotected:
|
|
||||||
* - it is initialized from sc_recorder_init() from the main thread;
|
|
||||||
* - it may be reset once from the recorder thread if the audio is
|
|
||||||
* disabled dynamically.
|
|
||||||
*
|
|
||||||
* Therefore, once the recorder thread is started, only the recorder thread
|
|
||||||
* may access it without data races.
|
|
||||||
*/
|
|
||||||
bool audio;
|
|
||||||
|
|
||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct sc_size declared_frame_size;
|
struct sc_size declared_frame_size;
|
||||||
|
bool header_written;
|
||||||
|
|
||||||
|
uint64_t pts_origin;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond queue_cond;
|
sc_cond queue_cond;
|
||||||
// set on sc_recorder_stop(), packet_sink close or recording failure
|
bool stopped; // set on recorder_close()
|
||||||
bool stopped;
|
bool failed; // set on packet write failure
|
||||||
struct sc_recorder_queue video_queue;
|
struct sc_recorder_queue queue;
|
||||||
struct sc_recorder_queue audio_queue;
|
|
||||||
|
|
||||||
// wake up the recorder thread once the video or audio codec is known
|
// we can write a packet only once we received the next one so that we can
|
||||||
sc_cond stream_cond;
|
// set its duration (next_pts - current_pts)
|
||||||
const AVCodec *video_codec;
|
// "previous" is only accessed from the recorder thread, so it does not
|
||||||
const AVCodec *audio_codec;
|
// need to be protected by the mutex
|
||||||
// Instead of providing an audio_codec, the demuxer may notify that the
|
struct sc_record_packet *previous;
|
||||||
// stream is disabled if the device could not capture audio
|
|
||||||
bool audio_disabled;
|
|
||||||
|
|
||||||
int video_stream_index;
|
|
||||||
int audio_stream_index;
|
|
||||||
|
|
||||||
const struct sc_recorder_callbacks *cbs;
|
|
||||||
void *cbs_userdata;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_recorder_callbacks {
|
|
||||||
void (*on_ended)(struct sc_recorder *recorder, bool success,
|
|
||||||
void *userdata);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format, bool audio,
|
enum sc_record_format format,
|
||||||
struct sc_size declared_frame_size,
|
struct sc_size declared_frame_size);
|
||||||
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_recorder_stop(struct sc_recorder *recorder);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_recorder_join(struct sc_recorder *recorder);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_recorder_destroy(struct sc_recorder *recorder);
|
sc_recorder_destroy(struct sc_recorder *recorder);
|
||||||
|
|||||||
@@ -156,15 +156,9 @@ event_loop(struct scrcpy *s) {
|
|||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SC_EVENT_DEVICE_DISCONNECTED:
|
case EVENT_STREAM_STOPPED:
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
return SCRCPY_EXIT_DISCONNECTED;
|
||||||
case SC_EVENT_DEMUXER_ERROR:
|
|
||||||
LOGE("Demuxer error");
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
case SC_EVENT_RECORDER_ERROR:
|
|
||||||
LOGE("Recorder error");
|
|
||||||
return SCRCPY_EXIT_FAILURE;
|
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
@@ -186,10 +180,10 @@ await_for_server(bool *connected) {
|
|||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
*connected = false;
|
*connected = false;
|
||||||
return true;
|
return true;
|
||||||
case SC_EVENT_SERVER_CONNECTION_FAILED:
|
case EVENT_SERVER_CONNECTION_FAILED:
|
||||||
LOGE("Server connection failed");
|
LOGE("Server connection failed");
|
||||||
return false;
|
return false;
|
||||||
case SC_EVENT_SERVER_CONNECTED:
|
case EVENT_SERVER_CONNECTED:
|
||||||
LOGD("Server connected");
|
LOGD("Server connected");
|
||||||
*connected = true;
|
*connected = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -240,37 +234,19 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
sc_video_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
|
||||||
void *userdata) {
|
|
||||||
(void) recorder;
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
PUSH_EVENT(SC_EVENT_RECORDER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
|
||||||
void *userdata) {
|
|
||||||
(void) demuxer;
|
(void) demuxer;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
if (eos) {
|
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
||||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
|
||||||
} else {
|
|
||||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
sc_audio_demuxer_on_eos(struct sc_demuxer *demuxer, void *userdata) {
|
||||||
void *userdata) {
|
|
||||||
(void) demuxer;
|
(void) demuxer;
|
||||||
(void) eos;
|
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -278,7 +254,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED);
|
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -286,7 +262,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED);
|
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -299,9 +275,8 @@ sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
|||||||
// event
|
// event
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
|
||||||
static uint32_t
|
static uint32_t
|
||||||
scrcpy_generate_scid() {
|
scrcpy_generate_uid() {
|
||||||
struct sc_rand rand;
|
struct sc_rand rand;
|
||||||
sc_rand_init(&rand);
|
sc_rand_init(&rand);
|
||||||
// Only use 31 bits to avoid issues with signed values on the Java-side
|
// Only use 31 bits to avoid issues with signed values on the Java-side
|
||||||
@@ -342,23 +317,21 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
struct sc_acksync *acksync = NULL;
|
||||||
|
|
||||||
uint32_t scid = scrcpy_generate_scid();
|
uint32_t uid = scrcpy_generate_uid();
|
||||||
|
|
||||||
struct sc_server_params params = {
|
struct sc_server_params params = {
|
||||||
.scid = scid,
|
.uid = uid,
|
||||||
.req_serial = options->serial,
|
.req_serial = options->serial,
|
||||||
.select_usb = options->select_usb,
|
.select_usb = options->select_usb,
|
||||||
.select_tcpip = options->select_tcpip,
|
.select_tcpip = options->select_tcpip,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.video_codec = options->video_codec,
|
.codec = options->codec,
|
||||||
.audio_codec = options->audio_codec,
|
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
.tunnel_host = options->tunnel_host,
|
.tunnel_host = options->tunnel_host,
|
||||||
.tunnel_port = options->tunnel_port,
|
.tunnel_port = options->tunnel_port,
|
||||||
.max_size = options->max_size,
|
.max_size = options->max_size,
|
||||||
.bit_rate = options->bit_rate,
|
.bit_rate = options->bit_rate,
|
||||||
.audio_bit_rate = options->audio_bit_rate,
|
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
.lock_video_orientation = options->lock_video_orientation,
|
.lock_video_orientation = options->lock_video_orientation,
|
||||||
.control = options->control,
|
.control = options->control,
|
||||||
@@ -368,7 +341,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.stay_awake = options->stay_awake,
|
.stay_awake = options->stay_awake,
|
||||||
.codec_options = options->codec_options,
|
.codec_options = options->codec_options,
|
||||||
.encoder_name = options->encoder_name,
|
.encoder_name = options->encoder_name,
|
||||||
.audio_encoder_name = options->audio_encoder_name,
|
|
||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
@@ -447,12 +419,10 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
struct sc_recorder *rec = NULL;
|
struct sc_recorder *rec = NULL;
|
||||||
if (options->record_filename) {
|
if (options->record_filename) {
|
||||||
static const struct sc_recorder_callbacks recorder_cbs = {
|
if (!sc_recorder_init(&s->recorder,
|
||||||
.on_ended = sc_recorder_on_ended,
|
options->record_filename,
|
||||||
};
|
options->record_format,
|
||||||
if (!sc_recorder_init(&s->recorder, options->record_filename,
|
info->frame_size)) {
|
||||||
options->record_format, options->audio,
|
|
||||||
info->frame_size, &recorder_cbs, NULL)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
rec = &s->recorder;
|
||||||
@@ -462,16 +432,16 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
av_log_set_callback(av_log_callback);
|
av_log_set_callback(av_log_callback);
|
||||||
|
|
||||||
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
|
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
|
||||||
.on_ended = sc_video_demuxer_on_ended,
|
.on_eos = sc_video_demuxer_on_eos,
|
||||||
};
|
};
|
||||||
sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket,
|
sc_demuxer_init(&s->video_demuxer, s->server.video_socket,
|
||||||
&video_demuxer_cbs, NULL);
|
&video_demuxer_cbs, NULL);
|
||||||
|
|
||||||
if (options->audio) {
|
if (options->audio) {
|
||||||
static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
|
static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
|
||||||
.on_ended = sc_audio_demuxer_on_ended,
|
.on_eos = sc_audio_demuxer_on_eos,
|
||||||
};
|
};
|
||||||
sc_demuxer_init(&s->audio_demuxer, "audio", s->server.audio_socket,
|
sc_demuxer_init(&s->audio_demuxer, s->server.audio_socket,
|
||||||
&audio_demuxer_cbs, NULL);
|
&audio_demuxer_cbs, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,10 +450,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rec) {
|
if (rec) {
|
||||||
sc_demuxer_add_sink(&s->video_demuxer, &rec->video_packet_sink);
|
sc_demuxer_add_sink(&s->video_demuxer, &rec->packet_sink);
|
||||||
if (options->audio) {
|
|
||||||
sc_demuxer_add_sink(&s->audio_demuxer, &rec->audio_packet_sink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_controller *controller = NULL;
|
struct sc_controller *controller = NULL;
|
||||||
@@ -735,9 +702,6 @@ end:
|
|||||||
if (file_pusher_initialized) {
|
if (file_pusher_initialized) {
|
||||||
sc_file_pusher_stop(&s->file_pusher);
|
sc_file_pusher_stop(&s->file_pusher);
|
||||||
}
|
}
|
||||||
if (recorder_initialized) {
|
|
||||||
sc_recorder_stop(&s->recorder);
|
|
||||||
}
|
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_interrupt(&s->screen);
|
sc_screen_interrupt(&s->screen);
|
||||||
}
|
}
|
||||||
@@ -789,7 +753,6 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (recorder_initialized) {
|
if (recorder_initialized) {
|
||||||
sc_recorder_join(&s->recorder);
|
|
||||||
sc_recorder_destroy(&s->recorder);
|
sc_recorder_destroy(&s->recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
bool need_new_event;
|
bool need_new_event;
|
||||||
if (previous_skipped) {
|
if (previous_skipped) {
|
||||||
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||||
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead, unless the previous event failed
|
// this new frame instead, unless the previous event failed
|
||||||
need_new_event = screen->event_failed;
|
need_new_event = screen->event_failed;
|
||||||
} else {
|
} else {
|
||||||
@@ -380,7 +380,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
|
|
||||||
if (need_new_event) {
|
if (need_new_event) {
|
||||||
static SDL_Event new_frame_event = {
|
static SDL_Event new_frame_event = {
|
||||||
.type = SC_EVENT_NEW_FRAME,
|
.type = EVENT_NEW_FRAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
@@ -820,7 +820,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|||||||
bool relative_mode = sc_screen_is_relative_mode(screen);
|
bool relative_mode = sc_screen_is_relative_mode(screen);
|
||||||
|
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case SC_EVENT_NEW_FRAME: {
|
case EVENT_NEW_FRAME: {
|
||||||
bool ok = sc_screen_update_frame(screen);
|
bool ok = sc_screen_update_frame(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Frame update failed\n");
|
LOGW("Frame update failed\n");
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ sc_server_params_destroy(struct sc_server_params *params) {
|
|||||||
free((char *) params->crop);
|
free((char *) params->crop);
|
||||||
free((char *) params->codec_options);
|
free((char *) params->codec_options);
|
||||||
free((char *) params->encoder_name);
|
free((char *) params->encoder_name);
|
||||||
free((char *) params->audio_encoder_name);
|
|
||||||
free((char *) params->tcpip_dst);
|
free((char *) params->tcpip_dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +97,6 @@ sc_server_params_copy(struct sc_server_params *dst,
|
|||||||
COPY(crop);
|
COPY(crop);
|
||||||
COPY(codec_options);
|
COPY(codec_options);
|
||||||
COPY(encoder_name);
|
COPY(encoder_name);
|
||||||
COPY(audio_encoder_name);
|
|
||||||
COPY(tcpip_dst);
|
COPY(tcpip_dst);
|
||||||
#undef COPY
|
#undef COPY
|
||||||
|
|
||||||
@@ -167,10 +165,6 @@ sc_server_get_codec_name(enum sc_codec codec) {
|
|||||||
return "h265";
|
return "h265";
|
||||||
case SC_CODEC_AV1:
|
case SC_CODEC_AV1:
|
||||||
return "av1";
|
return "av1";
|
||||||
case SC_CODEC_OPUS:
|
|
||||||
return "opus";
|
|
||||||
case SC_CODEC_AAC:
|
|
||||||
return "aac";
|
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -219,24 +213,15 @@ execute_server(struct sc_server *server,
|
|||||||
cmd[count++] = p; \
|
cmd[count++] = p; \
|
||||||
}
|
}
|
||||||
|
|
||||||
ADD_PARAM("scid=%08x", params->scid);
|
ADD_PARAM("uid=%08x", params->uid);
|
||||||
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
||||||
|
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
|
||||||
|
|
||||||
if (params->bit_rate) {
|
|
||||||
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
|
|
||||||
}
|
|
||||||
if (!params->audio) {
|
if (!params->audio) {
|
||||||
ADD_PARAM("audio=false");
|
ADD_PARAM("audio=false");
|
||||||
} else if (params->audio_bit_rate) {
|
|
||||||
ADD_PARAM("audio_bit_rate=%" PRIu32, params->audio_bit_rate);
|
|
||||||
}
|
}
|
||||||
if (params->video_codec != SC_CODEC_H264) {
|
if (params->codec != SC_CODEC_H264) {
|
||||||
ADD_PARAM("video_codec=%s",
|
ADD_PARAM("codec=%s", sc_server_get_codec_name(params->codec));
|
||||||
sc_server_get_codec_name(params->video_codec));
|
|
||||||
}
|
|
||||||
if (params->audio_codec != SC_CODEC_OPUS) {
|
|
||||||
ADD_PARAM("audio_codec=%s",
|
|
||||||
sc_server_get_codec_name(params->audio_codec));
|
|
||||||
}
|
}
|
||||||
if (params->max_size) {
|
if (params->max_size) {
|
||||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||||
@@ -273,9 +258,6 @@ execute_server(struct sc_server *server,
|
|||||||
if (params->encoder_name) {
|
if (params->encoder_name) {
|
||||||
ADD_PARAM("encoder_name=%s", params->encoder_name);
|
ADD_PARAM("encoder_name=%s", params->encoder_name);
|
||||||
}
|
}
|
||||||
if (params->audio_encoder_name) {
|
|
||||||
ADD_PARAM("audio_encoder_name=%s", params->audio_encoder_name);
|
|
||||||
}
|
|
||||||
if (params->power_off_on_close) {
|
if (params->power_off_on_close) {
|
||||||
ADD_PARAM("power_off_on_close=true");
|
ADD_PARAM("power_off_on_close=true");
|
||||||
}
|
}
|
||||||
@@ -839,7 +821,7 @@ run_server(void *data) {
|
|||||||
LOGD("Device serial: %s", serial);
|
LOGD("Device serial: %s", serial);
|
||||||
|
|
||||||
int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x",
|
int r = asprintf(&server->device_socket_name, SC_SOCKET_NAME_PREFIX "%08x",
|
||||||
params->scid);
|
params->uid);
|
||||||
if (r == -1) {
|
if (r == -1) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
|
|||||||
@@ -22,21 +22,18 @@ struct sc_server_info {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server_params {
|
struct sc_server_params {
|
||||||
uint32_t scid;
|
uint32_t uid;
|
||||||
const char *req_serial;
|
const char *req_serial;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_codec video_codec;
|
enum sc_codec codec;
|
||||||
enum sc_codec audio_codec;
|
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
const char *encoder_name;
|
const char *encoder_name;
|
||||||
const char *audio_encoder_name;
|
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint32_t tunnel_host;
|
uint32_t tunnel_host;
|
||||||
uint16_t tunnel_port;
|
uint16_t tunnel_port;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
int8_t lock_video_orientation;
|
||||||
bool control;
|
bool control;
|
||||||
|
|||||||
@@ -19,20 +19,9 @@ struct sc_packet_sink {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_packet_sink_ops {
|
struct sc_packet_sink_ops {
|
||||||
/* The codec instance is static, it is valid until the end of the program */
|
|
||||||
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
|
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
|
||||||
void (*close)(struct sc_packet_sink *sink);
|
void (*close)(struct sc_packet_sink *sink);
|
||||||
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
||||||
|
|
||||||
/*/
|
|
||||||
* Called when the input stream has been disabled at runtime.
|
|
||||||
*
|
|
||||||
* If it is called, then open(), close() and push() will never be called.
|
|
||||||
*
|
|
||||||
* It is useful to notify the recorder that the requested audio stream has
|
|
||||||
* finally been disabled because the device could not capture it.
|
|
||||||
*/
|
|
||||||
void (*disable)(struct sc_packet_sink *sink);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
|||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
event.type = SC_EVENT_USB_DEVICE_DISCONNECTED;
|
event.type = EVENT_USB_DEVICE_DISCONNECTED;
|
||||||
int ret = SDL_PushEvent(&event);
|
int ret = SDL_PushEvent(&event);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
||||||
@@ -34,7 +34,7 @@ event_loop(struct scrcpy_otg *s) {
|
|||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
case EVENT_USB_DEVICE_DISCONNECTED:
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
return SCRCPY_EXIT_DISCONNECTED;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package com.genymobile.scrcpy;
|
|||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
|
||||||
public enum AudioCodec implements Codec {
|
public enum AudioCodec implements Codec {
|
||||||
OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS),
|
OPUS(0x6f_70_75_73, "opus", MediaFormat.MIMETYPE_AUDIO_OPUS);
|
||||||
AAC(0x00_61_61_63, "aac", MediaFormat.MIMETYPE_AUDIO_AAC);
|
|
||||||
|
|
||||||
private final int id; // 4-byte ASCII representation of the name
|
private final int id; // 4-byte ASCII representation of the name
|
||||||
private final String name;
|
private final String name;
|
||||||
@@ -18,7 +17,7 @@ public enum AudioCodec implements Codec {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type getType() {
|
public Type getType() {
|
||||||
return Type.AUDIO;
|
return Type.VIDEO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ import android.media.MediaRecorder;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public final class AudioEncoder {
|
public final class AudioEncoder {
|
||||||
|
|
||||||
private static class InputTask {
|
private static class InputTask {
|
||||||
private final int index;
|
final int index;
|
||||||
|
|
||||||
InputTask(int index) {
|
InputTask(int index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@@ -29,8 +30,8 @@ public final class AudioEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class OutputTask {
|
private static class OutputTask {
|
||||||
private final int index;
|
final int index;
|
||||||
private final MediaCodec.BufferInfo bufferInfo;
|
final MediaCodec.BufferInfo bufferInfo;
|
||||||
|
|
||||||
OutputTask(int index, MediaCodec.BufferInfo bufferInfo) {
|
OutputTask(int index, MediaCodec.BufferInfo bufferInfo) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@@ -38,15 +39,18 @@ public final class AudioEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String MIMETYPE = MediaFormat.MIMETYPE_AUDIO_OPUS;
|
||||||
private static final int SAMPLE_RATE = 48000;
|
private static final int SAMPLE_RATE = 48000;
|
||||||
private static final int CHANNELS = 2;
|
private static final int CHANNELS = 2;
|
||||||
|
private static final int BIT_RATE = 128000;
|
||||||
|
|
||||||
private static final int BUFFER_MS = 15; // milliseconds
|
private static int BUFFER_MS = 15; // milliseconds
|
||||||
private static final int BUFFER_SIZE = SAMPLE_RATE * CHANNELS * BUFFER_MS / 1000;
|
private static final int BUFFER_SIZE = SAMPLE_RATE * CHANNELS * BUFFER_MS / 1000;
|
||||||
|
|
||||||
private final Streamer streamer;
|
private AudioRecord recorder;
|
||||||
private final int bitRate;
|
private MediaCodec mediaCodec;
|
||||||
private final String encoderName;
|
|
||||||
|
private final AtomicBoolean cleanUpDone = new AtomicBoolean(false);
|
||||||
|
|
||||||
// Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4).
|
// Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4).
|
||||||
// So many pending tasks would lead to an unacceptable delay anyway.
|
// So many pending tasks would lead to an unacceptable delay anyway.
|
||||||
@@ -61,12 +65,6 @@ public final class AudioEncoder {
|
|||||||
|
|
||||||
private boolean ended;
|
private boolean ended;
|
||||||
|
|
||||||
public AudioEncoder(Streamer streamer, int bitRate, String encoderName) {
|
|
||||||
this.streamer = streamer;
|
|
||||||
this.bitRate = bitRate;
|
|
||||||
this.encoderName = encoderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AudioFormat createAudioFormat() {
|
private static AudioFormat createAudioFormat() {
|
||||||
AudioFormat.Builder builder = new AudioFormat.Builder();
|
AudioFormat.Builder builder = new AudioFormat.Builder();
|
||||||
builder.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
|
builder.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
|
||||||
@@ -89,17 +87,17 @@ public final class AudioEncoder {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaFormat createFormat(String mimeType, int bitRate) {
|
private static MediaFormat createFormat() {
|
||||||
MediaFormat format = new MediaFormat();
|
MediaFormat format = new MediaFormat();
|
||||||
format.setString(MediaFormat.KEY_MIME, mimeType);
|
format.setString(MediaFormat.KEY_MIME, MIMETYPE);
|
||||||
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
|
||||||
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
|
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
|
||||||
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
|
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
private void inputThread(MediaCodec mediaCodec, AudioRecord recorder) throws IOException, InterruptedException {
|
private void inputThread() throws IOException, InterruptedException {
|
||||||
final AudioTimestamp timestamp = new AudioTimestamp();
|
final AudioTimestamp timestamp = new AudioTimestamp();
|
||||||
long previousPts = 0;
|
long previousPts = 0;
|
||||||
long nextPts = 0;
|
long nextPts = 0;
|
||||||
@@ -143,14 +141,12 @@ public final class AudioEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
|
private void outputThread() throws IOException, InterruptedException {
|
||||||
streamer.writeHeader();
|
|
||||||
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
OutputTask task = outputTasks.take();
|
OutputTask task = outputTasks.take();
|
||||||
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
|
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
|
||||||
try {
|
try {
|
||||||
streamer.writePacket(buffer, task.bufferInfo);
|
Ln.i("Audio packet [pts=" + task.bufferInfo.presentationTimeUs + "] " + buffer.remaining() + " bytes");
|
||||||
} finally {
|
} finally {
|
||||||
mediaCodec.releaseOutputBuffer(task.index, false);
|
mediaCodec.releaseOutputBuffer(task.index, false);
|
||||||
}
|
}
|
||||||
@@ -162,7 +158,7 @@ public final class AudioEncoder {
|
|||||||
try {
|
try {
|
||||||
encode();
|
encode();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Ln.e("Audio encoding error", e);
|
// this is expected on close
|
||||||
} finally {
|
} finally {
|
||||||
Ln.d("Audio encoder stopped");
|
Ln.d("Audio encoder stopped");
|
||||||
}
|
}
|
||||||
@@ -200,39 +196,25 @@ public final class AudioEncoder {
|
|||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
public void encode() throws IOException {
|
public void encode() throws IOException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException
|
||||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
|
||||||
streamer.writeDisableStream();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaCodec mediaCodec = null;
|
|
||||||
AudioRecord recorder = null;
|
|
||||||
|
|
||||||
boolean mediaCodecStarted = false;
|
|
||||||
boolean recorderStarted = false;
|
|
||||||
try {
|
try {
|
||||||
Codec codec = streamer.getCodec();
|
|
||||||
mediaCodec = createMediaCodec(codec, encoderName);
|
|
||||||
recorder = createAudioRecord();
|
recorder = createAudioRecord();
|
||||||
|
|
||||||
mediaCodecThread = new HandlerThread("AudioEncoder");
|
mediaCodecThread = new HandlerThread("AudioEncoder");
|
||||||
mediaCodecThread.start();
|
mediaCodecThread.start();
|
||||||
|
|
||||||
MediaFormat format = createFormat(codec.getMimeType(), bitRate);
|
MediaFormat format = createFormat();
|
||||||
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
|
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
|
||||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||||
|
|
||||||
recorder.startRecording();
|
recorder.startRecording();
|
||||||
recorderStarted = true;
|
|
||||||
|
|
||||||
final MediaCodec mediaCodecRef = mediaCodec;
|
|
||||||
final AudioRecord recorderRef = recorder;
|
|
||||||
inputThread = new Thread(() -> {
|
inputThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
inputThread(mediaCodecRef, recorderRef);
|
inputThread();
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
Ln.e("Audio capture error", e);
|
// this is expected on close
|
||||||
} finally {
|
} finally {
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
@@ -240,90 +222,50 @@ public final class AudioEncoder {
|
|||||||
|
|
||||||
outputThread = new Thread(() -> {
|
outputThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
outputThread(mediaCodecRef);
|
outputThread();
|
||||||
} catch (InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
// this is expected on close
|
// this is expected on close
|
||||||
} catch (IOException e) {
|
|
||||||
// Broken pipe is expected on close, because the socket is closed by the client
|
|
||||||
if (!IO.isBrokenPipe(e)) {
|
|
||||||
Ln.e("Audio encoding error", e);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mediaCodec.start();
|
mediaCodec.start();
|
||||||
mediaCodecStarted = true;
|
|
||||||
inputThread.start();
|
inputThread.start();
|
||||||
outputThread.start();
|
outputThread.start();
|
||||||
|
} catch (Throwable e) {
|
||||||
waitEnded();
|
mediaCodec.release();
|
||||||
} catch (ConfigurationException e) {
|
|
||||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
|
||||||
} finally {
|
|
||||||
if (!recorderStarted) {
|
|
||||||
// Notify the client that the audio could not be captured
|
|
||||||
streamer.writeDisableStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup everything (either at the end or on error at any step of the initialization)
|
|
||||||
if (mediaCodecThread != null) {
|
|
||||||
Looper looper = mediaCodecThread.getLooper();
|
|
||||||
if (looper != null) {
|
|
||||||
looper.quitSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inputThread != null) {
|
|
||||||
inputThread.interrupt();
|
|
||||||
}
|
|
||||||
if (outputThread != null) {
|
|
||||||
outputThread.interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (mediaCodecThread != null) {
|
|
||||||
mediaCodecThread.join();
|
|
||||||
}
|
|
||||||
if (inputThread != null) {
|
|
||||||
inputThread.join();
|
|
||||||
}
|
|
||||||
if (outputThread != null) {
|
|
||||||
outputThread.join();
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Should never happen
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaCodec != null) {
|
|
||||||
if (mediaCodecStarted) {
|
|
||||||
mediaCodec.stop();
|
|
||||||
}
|
|
||||||
mediaCodec.release();
|
|
||||||
}
|
|
||||||
if (recorder != null) {
|
if (recorder != null) {
|
||||||
if (recorderStarted) {
|
|
||||||
recorder.stop();
|
|
||||||
}
|
|
||||||
recorder.release();
|
recorder.release();
|
||||||
}
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
waitEnded();
|
||||||
|
} finally {
|
||||||
|
cleanUp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
private void cleanUp() {
|
||||||
if (encoderName != null) {
|
mediaCodecThread.getLooper().quit();
|
||||||
Ln.d("Creating audio encoder by name: '" + encoderName + "'");
|
inputThread.interrupt();
|
||||||
try {
|
outputThread.interrupt();
|
||||||
return MediaCodec.createByCodecName(encoderName);
|
|
||||||
} catch (IllegalArgumentException e) {
|
try {
|
||||||
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName));
|
mediaCodecThread.join();
|
||||||
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
inputThread.join();
|
||||||
}
|
outputThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Should never happen
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
|
||||||
Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'");
|
mediaCodec.stop();
|
||||||
return mediaCodec;
|
mediaCodec.release();
|
||||||
|
recorder.stop();
|
||||||
|
recorder.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EncoderCallback extends MediaCodec.Callback {
|
private class EncoderCallback extends MediaCodec.Callback {
|
||||||
|
|||||||
@@ -2,16 +2,10 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
public interface Codec {
|
public interface Codec {
|
||||||
|
|
||||||
enum Type {
|
enum Type {VIDEO}
|
||||||
VIDEO,
|
|
||||||
AUDIO,
|
|
||||||
}
|
|
||||||
|
|
||||||
Type getType();
|
Type getType();
|
||||||
|
|
||||||
int getId();
|
int getId();
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
String getMimeType();
|
String getMimeType();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import android.media.MediaCodecInfo;
|
|
||||||
import android.media.MediaCodecList;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class CodecUtils {
|
|
||||||
|
|
||||||
private CodecUtils() {
|
|
||||||
// not instantiable
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String buildUnknownEncoderMessage(Codec codec, String encoderName) {
|
|
||||||
StringBuilder msg = new StringBuilder("Encoder '").append(encoderName).append("' for ").append(codec.getName()).append(" not found");
|
|
||||||
MediaCodecInfo[] encoders = listEncoders(codec.getMimeType());
|
|
||||||
if (encoders != null && encoders.length > 0) {
|
|
||||||
msg.append("\nTry to use one of the available encoders:");
|
|
||||||
String codecOption = codec.getType() == Codec.Type.VIDEO ? "video-codec" : "audio-codec";
|
|
||||||
for (MediaCodecInfo encoder : encoders) {
|
|
||||||
msg.append("\n scrcpy --").append(codecOption).append("=").append(codec.getName());
|
|
||||||
msg.append(" --encoder='").append(encoder.getName()).append("'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MediaCodecInfo[] listEncoders(String mimeType) {
|
|
||||||
List<MediaCodecInfo> result = new ArrayList<>();
|
|
||||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
|
||||||
for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
|
|
||||||
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) {
|
|
||||||
result.add(codecInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toArray(new MediaCodecInfo[result.size()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
public class ConfigurationException extends Exception {
|
|
||||||
public ConfigurationException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,17 +51,17 @@ public final class DesktopConnection implements Closeable {
|
|||||||
return localSocket;
|
return localSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getSocketName(int scid) {
|
private static String getSocketName(int uid) {
|
||||||
if (scid == -1) {
|
if (uid == -1) {
|
||||||
// If no SCID is set, use "scrcpy" to simplify using scrcpy-server alone
|
// If no UID is set, use "scrcpy" to simplify using scrcpy-server alone
|
||||||
return SOCKET_NAME_PREFIX;
|
return SOCKET_NAME_PREFIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SOCKET_NAME_PREFIX + String.format("_%08x", scid);
|
return SOCKET_NAME_PREFIX + String.format("_%08x", uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DesktopConnection open(int scid, boolean tunnelForward, boolean audio, boolean control, boolean sendDummyByte) throws IOException {
|
public static DesktopConnection open(int uid, boolean tunnelForward, boolean audio, boolean control, boolean sendDummyByte) throws IOException {
|
||||||
String socketName = getSocketName(scid);
|
String socketName = getSocketName(uid);
|
||||||
|
|
||||||
LocalSocket videoSocket = null;
|
LocalSocket videoSocket = null;
|
||||||
LocalSocket audioSocket = null;
|
LocalSocket audioSocket = null;
|
||||||
|
|||||||
@@ -61,12 +61,12 @@ public final class Device {
|
|||||||
|
|
||||||
private final boolean supportsInputEvents;
|
private final boolean supportsInputEvents;
|
||||||
|
|
||||||
public Device(Options options) throws ConfigurationException {
|
public Device(Options options) {
|
||||||
displayId = options.getDisplayId();
|
displayId = options.getDisplayId();
|
||||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
if (displayInfo == null) {
|
if (displayInfo == null) {
|
||||||
Ln.e(buildUnknownDisplayIdMessage(displayId));
|
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
|
||||||
throw new ConfigurationException("Unknown display id: " + displayId);
|
throw new InvalidDisplayIdException(displayId, displayIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
int displayInfoFlags = displayInfo.getFlags();
|
int displayInfoFlags = displayInfo.getFlags();
|
||||||
@@ -130,18 +130,6 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildUnknownDisplayIdMessage(int displayId) {
|
|
||||||
StringBuilder msg = new StringBuilder("Display ").append(displayId).append(" not found");
|
|
||||||
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
|
|
||||||
if (displayIds != null && displayIds.length > 0) {
|
|
||||||
msg.append("\nTry to use one of the available display ids:");
|
|
||||||
for (int id : displayIds) {
|
|
||||||
msg.append("\n scrcpy --display=").append(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setMaxSize(int newMaxSize) {
|
public synchronized void setMaxSize(int newMaxSize) {
|
||||||
maxSize = newMaxSize;
|
maxSize = newMaxSize;
|
||||||
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
|
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
|
||||||
|
|||||||
@@ -48,9 +48,4 @@ public final class IO {
|
|||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBrokenPipe(IOException e) {
|
|
||||||
Throwable cause = e.getCause();
|
|
||||||
return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public class InvalidDisplayIdException extends RuntimeException {
|
||||||
|
|
||||||
|
private final int displayId;
|
||||||
|
private final int[] availableDisplayIds;
|
||||||
|
|
||||||
|
public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) {
|
||||||
|
super("There is no display having id " + displayId);
|
||||||
|
this.displayId = displayId;
|
||||||
|
this.availableDisplayIds = availableDisplayIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDisplayId() {
|
||||||
|
return displayId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getAvailableDisplayIds() {
|
||||||
|
return availableDisplayIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
|
|
||||||
|
public class InvalidEncoderException extends RuntimeException {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final MediaCodecInfo[] availableEncoders;
|
||||||
|
|
||||||
|
public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) {
|
||||||
|
super("There is no encoder having name '" + name + '"');
|
||||||
|
this.name = name;
|
||||||
|
this.availableEncoders = availableEncoders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaCodecInfo[] getAvailableEncoders() {
|
||||||
|
return availableEncoders;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,11 @@ import java.util.List;
|
|||||||
public class Options {
|
public class Options {
|
||||||
|
|
||||||
private Ln.Level logLevel = Ln.Level.DEBUG;
|
private Ln.Level logLevel = Ln.Level.DEBUG;
|
||||||
private int scid = -1; // 31-bit non-negative value, or -1
|
private int uid = -1; // 31-bit non-negative value, or -1
|
||||||
private boolean audio = true;
|
private boolean audio = true;
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private VideoCodec videoCodec = VideoCodec.H264;
|
private VideoCodec codec = VideoCodec.H264;
|
||||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
|
||||||
private int bitRate = 8000000;
|
private int bitRate = 8000000;
|
||||||
private int audioBitRate = 196000;
|
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
private int lockVideoOrientation = -1;
|
private int lockVideoOrientation = -1;
|
||||||
private boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
@@ -24,7 +22,6 @@ public class Options {
|
|||||||
private boolean stayAwake;
|
private boolean stayAwake;
|
||||||
private List<CodecOption> codecOptions;
|
private List<CodecOption> codecOptions;
|
||||||
private String encoderName;
|
private String encoderName;
|
||||||
private String audioEncoderName;
|
|
||||||
private boolean powerOffScreenOnClose;
|
private boolean powerOffScreenOnClose;
|
||||||
private boolean clipboardAutosync = true;
|
private boolean clipboardAutosync = true;
|
||||||
private boolean downsizeOnError = true;
|
private boolean downsizeOnError = true;
|
||||||
@@ -45,12 +42,12 @@ public class Options {
|
|||||||
this.logLevel = logLevel;
|
this.logLevel = logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getScid() {
|
public int getUid() {
|
||||||
return scid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScid(int scid) {
|
public void setUid(int uid) {
|
||||||
this.scid = scid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getAudio() {
|
public boolean getAudio() {
|
||||||
@@ -69,20 +66,12 @@ public class Options {
|
|||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VideoCodec getVideoCodec() {
|
public VideoCodec getCodec() {
|
||||||
return videoCodec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVideoCodec(VideoCodec videoCodec) {
|
public void setCodec(VideoCodec codec) {
|
||||||
this.videoCodec = videoCodec;
|
this.codec = codec;
|
||||||
}
|
|
||||||
|
|
||||||
public AudioCodec getAudioCodec() {
|
|
||||||
return audioCodec;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioCodec(AudioCodec audioCodec) {
|
|
||||||
this.audioCodec = audioCodec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBitRate() {
|
public int getBitRate() {
|
||||||
@@ -93,14 +82,6 @@ public class Options {
|
|||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAudioBitRate() {
|
|
||||||
return audioBitRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioBitRate(int audioBitRate) {
|
|
||||||
this.audioBitRate = audioBitRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxFps() {
|
public int getMaxFps() {
|
||||||
return maxFps;
|
return maxFps;
|
||||||
}
|
}
|
||||||
@@ -181,14 +162,6 @@ public class Options {
|
|||||||
this.encoderName = encoderName;
|
this.encoderName = encoderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAudioEncoderName() {
|
|
||||||
return audioEncoderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioEncoderName(String audioEncoderName) {
|
|
||||||
this.audioEncoderName = audioEncoderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
|
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
|
||||||
this.powerOffScreenOnClose = powerOffScreenOnClose;
|
this.powerOffScreenOnClose = powerOffScreenOnClose;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
|
import android.media.MediaCodecList;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -13,6 +14,8 @@ import android.view.Surface;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@@ -39,8 +42,8 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
private boolean firstFrameSent;
|
private boolean firstFrameSent;
|
||||||
private int consecutiveErrors;
|
private int consecutiveErrors;
|
||||||
|
|
||||||
public ScreenEncoder(Device device, Streamer streamer, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
|
public ScreenEncoder(Device device, Streamer streamer, int bitRate, int maxFps, List<CodecOption> codecOptions,
|
||||||
boolean downsizeOnError) {
|
String encoderName, boolean downsizeOnError) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
@@ -59,10 +62,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
return rotationChanged.getAndSet(false);
|
return rotationChanged.getAndSet(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void streamScreen() throws IOException, ConfigurationException {
|
public void streamScreen() throws IOException {
|
||||||
Codec codec = streamer.getCodec();
|
String videoMimeType = streamer.getCodec().getMimeType();
|
||||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
MediaCodec codec = createCodec(videoMimeType, encoderName);
|
||||||
MediaFormat format = createFormat(codec.getMimeType(), bitRate, maxFps, codecOptions);
|
MediaFormat format = createFormat(videoMimeType, bitRate, maxFps, codecOptions);
|
||||||
IBinder display = createDisplay();
|
IBinder display = createDisplay();
|
||||||
device.setRotationListener(this);
|
device.setRotationListener(this);
|
||||||
|
|
||||||
@@ -81,8 +84,8 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
|
|
||||||
Surface surface = null;
|
Surface surface = null;
|
||||||
try {
|
try {
|
||||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||||
surface = mediaCodec.createInputSurface();
|
surface = codec.createInputSurface();
|
||||||
|
|
||||||
// does not include the locked video orientation
|
// does not include the locked video orientation
|
||||||
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
||||||
@@ -90,11 +93,11 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
int layerStack = device.getLayerStack();
|
int layerStack = device.getLayerStack();
|
||||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||||
|
|
||||||
mediaCodec.start();
|
codec.start();
|
||||||
|
|
||||||
alive = encode(mediaCodec, streamer);
|
alive = encode(codec, streamer);
|
||||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||||
mediaCodec.stop();
|
codec.stop();
|
||||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||||
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
if (!prepareRetry(device, screenInfo)) {
|
if (!prepareRetry(device, screenInfo)) {
|
||||||
@@ -103,14 +106,14 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
Ln.i("Retrying...");
|
Ln.i("Retrying...");
|
||||||
alive = true;
|
alive = true;
|
||||||
} finally {
|
} finally {
|
||||||
mediaCodec.reset();
|
codec.reset();
|
||||||
if (surface != null) {
|
if (surface != null) {
|
||||||
surface.release();
|
surface.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (alive);
|
} while (alive);
|
||||||
} finally {
|
} finally {
|
||||||
mediaCodec.release();
|
codec.release();
|
||||||
device.setRotationListener(null);
|
device.setRotationListener(null);
|
||||||
SurfaceControl.destroyDisplay(display);
|
SurfaceControl.destroyDisplay(display);
|
||||||
}
|
}
|
||||||
@@ -196,19 +199,30 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
return !eof;
|
return !eof;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
private static MediaCodecInfo[] listEncoders(String videoMimeType) {
|
||||||
|
List<MediaCodecInfo> result = new ArrayList<>();
|
||||||
|
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||||
|
for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
|
||||||
|
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(videoMimeType)) {
|
||||||
|
result.add(codecInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toArray(new MediaCodecInfo[result.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MediaCodec createCodec(String videoMimeType, String encoderName) throws IOException {
|
||||||
if (encoderName != null) {
|
if (encoderName != null) {
|
||||||
Ln.d("Creating encoder by name: '" + encoderName + "'");
|
Ln.d("Creating encoder by name: '" + encoderName + "'");
|
||||||
try {
|
try {
|
||||||
return MediaCodec.createByCodecName(encoderName);
|
return MediaCodec.createByCodecName(encoderName);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Ln.e(CodecUtils.buildUnknownEncoderMessage(codec, encoderName));
|
MediaCodecInfo[] encoders = listEncoders(videoMimeType);
|
||||||
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
throw new InvalidEncoderException(encoderName, encoders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
MediaCodec codec = MediaCodec.createEncoderByType(videoMimeType);
|
||||||
Ln.d("Using encoder: '" + mediaCodec.getName() + "'");
|
Ln.d("Using encoder: '" + codec.getName() + "'");
|
||||||
return mediaCodec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
|
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
@@ -58,14 +59,14 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
private static void scrcpy(Options options) throws IOException {
|
||||||
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
||||||
final Device device = new Device(options);
|
final Device device = new Device(options);
|
||||||
List<CodecOption> codecOptions = options.getCodecOptions();
|
List<CodecOption> codecOptions = options.getCodecOptions();
|
||||||
|
|
||||||
Thread initThread = startInitThread(options);
|
Thread initThread = startInitThread(options);
|
||||||
|
|
||||||
int scid = options.getScid();
|
int uid = options.getUid();
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
boolean control = options.getControl();
|
boolean control = options.getControl();
|
||||||
boolean audio = options.getAudio();
|
boolean audio = options.getAudio();
|
||||||
@@ -92,15 +93,14 @@ public final class Server {
|
|||||||
Workarounds.fillAppInfo();
|
Workarounds.fillAppInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller controller = null;
|
try (DesktopConnection connection = DesktopConnection.open(uid, tunnelForward, audio, control, sendDummyByte)) {
|
||||||
AudioEncoder audioEncoder = null;
|
VideoCodec codec = options.getCodec();
|
||||||
|
|
||||||
try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) {
|
|
||||||
if (options.getSendDeviceMeta()) {
|
if (options.getSendDeviceMeta()) {
|
||||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||||
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller controller = null;
|
||||||
if (control) {
|
if (control) {
|
||||||
controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
|
controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
|
||||||
controller.start();
|
controller.start();
|
||||||
@@ -109,46 +109,41 @@ public final class Server {
|
|||||||
device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text));
|
device.setClipboardListener(text -> controllerRef.getSender().pushClipboardText(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioEncoder audioEncoder = null;
|
||||||
if (audio) {
|
if (audio) {
|
||||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), options.getAudioCodec(), options.getSendCodecId(),
|
audioEncoder = new AudioEncoder();
|
||||||
options.getSendFrameMeta());
|
|
||||||
audioEncoder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioEncoderName());
|
|
||||||
audioEncoder.start();
|
audioEncoder.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecId(),
|
Streamer videoStreamer = new Streamer(connection.getVideoFd(), codec, options.getSendCodecId(), options.getSendFrameMeta());
|
||||||
options.getSendFrameMeta());
|
ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(),
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getBitRate(), options.getMaxFps(), codecOptions,
|
codecOptions, options.getEncoderName(), options.getDownsizeOnError());
|
||||||
options.getEncoderName(), options.getDownsizeOnError());
|
|
||||||
try {
|
try {
|
||||||
// synchronous
|
// synchronous
|
||||||
screenEncoder.streamScreen();
|
screenEncoder.streamScreen();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Broken pipe is expected on close, because the socket is closed by the client
|
// this is expected on close
|
||||||
if (!IO.isBrokenPipe(e)) {
|
} finally {
|
||||||
Ln.e("Video encoding error", e);
|
Ln.d("Screen streaming stopped");
|
||||||
}
|
initThread.interrupt();
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
Ln.d("Screen streaming stopped");
|
|
||||||
initThread.interrupt();
|
|
||||||
if (audioEncoder != null) {
|
|
||||||
audioEncoder.stop();
|
|
||||||
}
|
|
||||||
if (controller != null) {
|
|
||||||
controller.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
initThread.join();
|
|
||||||
if (audioEncoder != null) {
|
if (audioEncoder != null) {
|
||||||
audioEncoder.join();
|
audioEncoder.stop();
|
||||||
}
|
}
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
controller.join();
|
controller.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
initThread.join();
|
||||||
|
if (audioEncoder != null) {
|
||||||
|
audioEncoder.join();
|
||||||
|
}
|
||||||
|
if (controller != null) {
|
||||||
|
controller.join();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +154,6 @@ public final class Server {
|
|||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MethodLength")
|
|
||||||
private static Options createOptions(String... args) {
|
private static Options createOptions(String... args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
throw new IllegalArgumentException("Missing client version");
|
throw new IllegalArgumentException("Missing client version");
|
||||||
@@ -182,12 +176,12 @@ public final class Server {
|
|||||||
String key = arg.substring(0, equalIndex);
|
String key = arg.substring(0, equalIndex);
|
||||||
String value = arg.substring(equalIndex + 1);
|
String value = arg.substring(equalIndex + 1);
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "scid":
|
case "uid":
|
||||||
int scid = Integer.parseInt(value, 0x10);
|
int uid = Integer.parseInt(value, 0x10);
|
||||||
if (scid < -1) {
|
if (uid < -1) {
|
||||||
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
|
throw new IllegalArgumentException("uid may not be negative (except -1 for 'none'): " + uid);
|
||||||
}
|
}
|
||||||
options.setScid(scid);
|
options.setUid(uid);
|
||||||
break;
|
break;
|
||||||
case "log_level":
|
case "log_level":
|
||||||
Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
|
Ln.Level level = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
|
||||||
@@ -197,19 +191,12 @@ public final class Server {
|
|||||||
boolean audio = Boolean.parseBoolean(value);
|
boolean audio = Boolean.parseBoolean(value);
|
||||||
options.setAudio(audio);
|
options.setAudio(audio);
|
||||||
break;
|
break;
|
||||||
case "video_codec":
|
case "codec":
|
||||||
VideoCodec videoCodec = VideoCodec.findByName(value);
|
VideoCodec codec = VideoCodec.findByName(value);
|
||||||
if (videoCodec == null) {
|
if (codec == null) {
|
||||||
throw new IllegalArgumentException("Video codec " + value + " not supported");
|
throw new IllegalArgumentException("Video codec " + value + " not supported");
|
||||||
}
|
}
|
||||||
options.setVideoCodec(videoCodec);
|
options.setCodec(codec);
|
||||||
break;
|
|
||||||
case "audio_codec":
|
|
||||||
AudioCodec audioCodec = AudioCodec.findByName(value);
|
|
||||||
if (audioCodec == null) {
|
|
||||||
throw new IllegalArgumentException("Audio codec " + value + " not supported");
|
|
||||||
}
|
|
||||||
options.setAudioCodec(audioCodec);
|
|
||||||
break;
|
break;
|
||||||
case "max_size":
|
case "max_size":
|
||||||
int maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
int maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||||
@@ -219,10 +206,6 @@ public final class Server {
|
|||||||
int bitRate = Integer.parseInt(value);
|
int bitRate = Integer.parseInt(value);
|
||||||
options.setBitRate(bitRate);
|
options.setBitRate(bitRate);
|
||||||
break;
|
break;
|
||||||
case "audio_bit_rate":
|
|
||||||
int audioBitRate = Integer.parseInt(value);
|
|
||||||
options.setAudioBitRate(audioBitRate);
|
|
||||||
break;
|
|
||||||
case "max_fps":
|
case "max_fps":
|
||||||
int maxFps = Integer.parseInt(value);
|
int maxFps = Integer.parseInt(value);
|
||||||
options.setMaxFps(maxFps);
|
options.setMaxFps(maxFps);
|
||||||
@@ -264,10 +247,6 @@ public final class Server {
|
|||||||
options.setEncoderName(value);
|
options.setEncoderName(value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "audio_encoder_name":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.setAudioEncoderName(value);
|
|
||||||
}
|
|
||||||
case "power_off_on_close":
|
case "power_off_on_close":
|
||||||
boolean powerOffScreenOnClose = Boolean.parseBoolean(value);
|
boolean powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||||
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
|
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
|
||||||
@@ -338,19 +317,38 @@ public final class Server {
|
|||||||
return new Rect(x, y, x + width, y + height);
|
return new Rect(x, y, x + width, y + height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void suggestFix(Throwable e) {
|
||||||
|
if (e instanceof InvalidDisplayIdException) {
|
||||||
|
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
||||||
|
int[] displayIds = idie.getAvailableDisplayIds();
|
||||||
|
if (displayIds != null && displayIds.length > 0) {
|
||||||
|
Ln.e("Try to use one of the available display ids:");
|
||||||
|
for (int id : displayIds) {
|
||||||
|
Ln.e(" scrcpy --display " + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e instanceof InvalidEncoderException) {
|
||||||
|
InvalidEncoderException iee = (InvalidEncoderException) e;
|
||||||
|
MediaCodecInfo[] encoders = iee.getAvailableEncoders();
|
||||||
|
if (encoders != null && encoders.length > 0) {
|
||||||
|
Ln.e("Try to use one of the available encoders:");
|
||||||
|
for (MediaCodecInfo encoder : encoders) {
|
||||||
|
Ln.e(" scrcpy --encoder '" + encoder.getName() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||||
Ln.e("Exception on thread " + t, e);
|
Ln.e("Exception on thread " + t, e);
|
||||||
|
suggestFix(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
Options options = createOptions(args);
|
Options options = createOptions(args);
|
||||||
|
|
||||||
Ln.initLogLevel(options.getLogLevel());
|
Ln.initLogLevel(options.getLogLevel());
|
||||||
|
|
||||||
try {
|
scrcpy(options);
|
||||||
scrcpy(options);
|
|
||||||
} catch (ConfigurationException e) {
|
|
||||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ public final class Streamer {
|
|||||||
private static final long PACKET_FLAG_CONFIG = 1L << 63;
|
private static final long PACKET_FLAG_CONFIG = 1L << 63;
|
||||||
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
|
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
|
||||||
|
|
||||||
private static final long AOPUSHDR = 0x5244485355504F41L; // "AOPUSHDR" in ASCII (little-endian)
|
|
||||||
|
|
||||||
private final FileDescriptor fd;
|
private final FileDescriptor fd;
|
||||||
private final Codec codec;
|
private final Codec codec;
|
||||||
private final boolean sendCodecId;
|
private final boolean sendCodecId;
|
||||||
@@ -40,17 +38,7 @@ public final class Streamer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeDisableStream() throws IOException {
|
|
||||||
// Writing 0 (32-bit) as codec-id means that the device disables the stream (because it could not capture)
|
|
||||||
byte[] zeros = new byte[4];
|
|
||||||
IO.writeFully(fd, zeros, 0, zeros.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException {
|
public void writePacket(ByteBuffer codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException {
|
||||||
if (codec == AudioCodec.OPUS) {
|
|
||||||
fixOpusConfigPacket(codecBuffer, bufferInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendFrameMeta) {
|
if (sendFrameMeta) {
|
||||||
writeFrameMeta(fd, bufferInfo, codecBuffer.remaining());
|
writeFrameMeta(fd, bufferInfo, codecBuffer.remaining());
|
||||||
}
|
}
|
||||||
@@ -76,46 +64,4 @@ public final class Streamer {
|
|||||||
headerBuffer.flip();
|
headerBuffer.flip();
|
||||||
IO.writeFully(fd, headerBuffer);
|
IO.writeFully(fd, headerBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fixOpusConfigPacket(ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo) throws IOException {
|
|
||||||
// Here is an example of the config packet received for an OPUS stream:
|
|
||||||
//
|
|
||||||
// 00000000 41 4f 50 55 53 48 44 52 13 00 00 00 00 00 00 00 |AOPUSHDR........|
|
|
||||||
// -------------- BELOW IS THE PART WE MUST PUT AS EXTRADATA -------------------
|
|
||||||
// 00000010 4f 70 75 73 48 65 61 64 01 01 38 01 80 bb 00 00 |OpusHead..8.....|
|
|
||||||
// 00000020 00 00 00 |... |
|
|
||||||
// ------------------------------------------------------------------------------
|
|
||||||
// 00000020 41 4f 50 55 53 44 4c 59 08 00 00 00 00 | AOPUSDLY.....|
|
|
||||||
// 00000030 00 00 00 a0 2e 63 00 00 00 00 00 41 4f 50 55 53 |.....c.....AOPUS|
|
|
||||||
// 00000040 50 52 4c 08 00 00 00 00 00 00 00 00 b4 c4 04 00 |PRL.............|
|
|
||||||
// 00000050 00 00 00 |...|
|
|
||||||
//
|
|
||||||
// Each "section" is prefixed by a 64-bit ID and a 64-bit length.
|
|
||||||
|
|
||||||
boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
|
|
||||||
if (!isConfig) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (buffer.remaining() >= 16) {
|
|
||||||
long id = buffer.getLong();
|
|
||||||
long sizeLong = buffer.getLong();
|
|
||||||
if (sizeLong < 0 || sizeLong >= 0x7FFFFFFF) {
|
|
||||||
throw new IOException("Invalid block size in OPUS header: " + sizeLong);
|
|
||||||
}
|
|
||||||
int size = (int) sizeLong;
|
|
||||||
if (id == AOPUSHDR) {
|
|
||||||
if (buffer.remaining() < size) {
|
|
||||||
throw new IOException("Not enough data in OPUS header (invalid size: " + size + ")");
|
|
||||||
}
|
|
||||||
// Set the buffer to point to the OPUS header slice
|
|
||||||
buffer.limit(buffer.position() + size);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.position(buffer.position() + size);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IOException("OPUS header not found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.app.Instrumentation;
|
||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|||||||
BIN
u/.ninja_deps
BIN
u/.ninja_deps
Binary file not shown.
62
u/.ninja_log
62
u/.ninja_log
@@ -1,62 +0,0 @@
|
|||||||
# ninja log v5
|
|
||||||
0 243 1542027815 build.ninja ef03cd8523486e97
|
|
||||||
0 58 1542027815 app/app@@scrcpy@exe/src_str_util.c.o 2aa692e7aa83914
|
|
||||||
0 87 1542027815 app/app@@scrcpy@exe/src_sys_unix_net.c.o 7ea14bd07e90ff97
|
|
||||||
0 91 1542027815 app/app@@scrcpy@exe/src_sys_unix_command.c.o dd44ba15cc3d6a7e
|
|
||||||
1 113 1542027815 app/app@@scrcpy@exe/src_command.c.o 1eaa0f061a5c0447
|
|
||||||
0 114 1542027815 app/app@@scrcpy@exe/src_server.c.o 8b376071b5e0aaf1
|
|
||||||
1 117 1542027815 app/app@@scrcpy@exe/src_controller.c.o 907de440054c77e7
|
|
||||||
1 146 1542027815 app/app@@scrcpy@exe/src_control_event.c.o fcfa5a6c322ebf8b
|
|
||||||
91 202 1542027815 app/app@@scrcpy@exe/src_device.c.o 9ac9441f4f2e4d54
|
|
||||||
58 215 1542027815 app/app@@scrcpy@exe/src_convert.c.o 9de268e9b915094e
|
|
||||||
115 219 1542027815 app/app@@scrcpy@exe/src_fps_counter.c.o 22b968c51acd256b
|
|
||||||
114 235 1542027815 app/app@@scrcpy@exe/src_file_handler.c.o 11e303a26f189d9a
|
|
||||||
117 286 1542027815 app/app@@scrcpy@exe/src_frames.c.o 3c5c4dbee035e5ab
|
|
||||||
88 319 1542027815 app/app@@scrcpy@exe/src_decoder.c.o 60c1438cf7786895
|
|
||||||
202 338 1542027815 app/app@@scrcpy@exe/src_lock_util.c.o 9265bcc92f144427
|
|
||||||
215 367 1542027815 app/app@@scrcpy@exe/src_net.c.o 718f65aa73583163
|
|
||||||
220 408 1542027815 app/app@@scrcpy@exe/src_recorder.c.o 676a7500fb0d45cb
|
|
||||||
1 470 1542027815 app/app@@scrcpy@exe/src_tiny_xpm.c.o 91851ad29940a4b1
|
|
||||||
286 485 1542027815 app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o 46bff52a98c0b5ca
|
|
||||||
319 487 1542027815 app/app@@test_control_event_queue@exe/src_control_event.c.o 76492af89a914173
|
|
||||||
1 488 1542027815 app/app@@scrcpy@exe/src_main.c.o e7dc8583797471c5
|
|
||||||
367 488 1542027815 app/app@@test_control_event_serialize@exe/src_control_event.c.o fcff2e1105474edf
|
|
||||||
0 497 1542027815 app/app@@scrcpy@exe/src_screen.c.o 329c18ec2111c8ff
|
|
||||||
408 515 1542027815 app/app@@test_strutil@exe/tests_test_strutil.c.o 15440f4bca20c50d
|
|
||||||
338 517 1542027815 app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o baa0b48891372fcc
|
|
||||||
470 525 1542027815 app/app@@test_strutil@exe/src_str_util.c.o fcb3a91d36e23e11
|
|
||||||
525 561 1542027815 app/test_strutil 3448478dadf99adf
|
|
||||||
487 582 1542027815 app/test_control_event_queue bfca00bc894d3c4f
|
|
||||||
517 606 1542027815 app/test_control_event_serialize e06ab4ce04dd4fad
|
|
||||||
147 638 1542027815 app/app@@scrcpy@exe/src_input_manager.c.o 1fe285b256bf5908
|
|
||||||
236 713 1542027815 app/app@@scrcpy@exe/src_scrcpy.c.o 8b0bae90b272da98
|
|
||||||
713 891 1542027816 app/scrcpy 8fba96817bb2802c
|
|
||||||
485 5716 1542027820 server/scrcpy-server.jar 8511d30842df298f
|
|
||||||
0 264 1542027826 build.ninja ef03cd8523486e97
|
|
||||||
1 31 1542027826 app/app@@scrcpy@exe/src_fps_counter.c.o 22b968c51acd256b
|
|
||||||
1 44 1542027826 app/app@@scrcpy@exe/src_file_handler.c.o 11e303a26f189d9a
|
|
||||||
1 47 1542027826 app/app@@scrcpy@exe/src_controller.c.o 907de440054c77e7
|
|
||||||
2 50 1542027826 app/app@@scrcpy@exe/src_frames.c.o 3c5c4dbee035e5ab
|
|
||||||
2 50 1542027826 app/app@@scrcpy@exe/src_recorder.c.o 676a7500fb0d45cb
|
|
||||||
1 65 1542027826 app/app@@scrcpy@exe/src_decoder.c.o 60c1438cf7786895
|
|
||||||
31 82 1542027826 app/app@@scrcpy@exe/src_server.c.o 8b376071b5e0aaf1
|
|
||||||
2 108 1542027826 app/app@@scrcpy@exe/src_input_manager.c.o 1fe285b256bf5908
|
|
||||||
2 129 1542027826 app/app@@scrcpy@exe/src_screen.c.o 329c18ec2111c8ff
|
|
||||||
2 162 1542027826 app/app@@scrcpy@exe/src_scrcpy.c.o 8b0bae90b272da98
|
|
||||||
1 339 1542027826 app/app@@scrcpy@exe/src_main.c.o e7dc8583797471c5
|
|
||||||
339 538 1542027827 app/scrcpy 8fba96817bb2802c
|
|
||||||
44 753 1542027827 server/scrcpy-server.jar 8511d30842df298f
|
|
||||||
0 276 1542027871 build.ninja ef03cd8523486e97
|
|
||||||
1 37 1542027872 app/app@@scrcpy@exe/src_file_handler.c.o 11e303a26f189d9a
|
|
||||||
1 42 1542027872 app/app@@scrcpy@exe/src_controller.c.o 907de440054c77e7
|
|
||||||
1 45 1542027872 app/app@@scrcpy@exe/src_fps_counter.c.o 22b968c51acd256b
|
|
||||||
2 49 1542027872 app/app@@scrcpy@exe/src_recorder.c.o 676a7500fb0d45cb
|
|
||||||
1 52 1542027872 app/app@@scrcpy@exe/src_frames.c.o 3c5c4dbee035e5ab
|
|
||||||
0 64 1542027872 app/app@@scrcpy@exe/src_decoder.c.o 60c1438cf7786895
|
|
||||||
37 80 1542027872 app/app@@scrcpy@exe/src_server.c.o 8b376071b5e0aaf1
|
|
||||||
1 128 1542027872 app/app@@scrcpy@exe/src_input_manager.c.o 1fe285b256bf5908
|
|
||||||
2 138 1542027872 app/app@@scrcpy@exe/src_screen.c.o 329c18ec2111c8ff
|
|
||||||
2 150 1542027872 app/app@@scrcpy@exe/src_scrcpy.c.o 8b0bae90b272da98
|
|
||||||
1 370 1542027872 app/app@@scrcpy@exe/src_main.c.o e7dc8583797471c5
|
|
||||||
370 578 1542027872 app/scrcpy 8fba96817bb2802c
|
|
||||||
42 688 1542027872 server/scrcpy-server.jar 8511d30842df298f
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autogenerated by the Meson build system.
|
|
||||||
* Do not edit, your changes will be lost.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#define BUILD_DEBUG
|
|
||||||
|
|
||||||
#define DEFAULT_BIT_RATE 8000000
|
|
||||||
|
|
||||||
#define DEFAULT_LOCAL_PORT 27183
|
|
||||||
|
|
||||||
#define DEFAULT_MAX_SIZE 0
|
|
||||||
|
|
||||||
#define HIDPI_SUPPORT
|
|
||||||
|
|
||||||
#undef OVERRIDE_SERVER_PATH
|
|
||||||
|
|
||||||
#define PREFIX "/usr/local"
|
|
||||||
|
|
||||||
#define PREFIXED_SERVER_PATH "/share/scrcpy/scrcpy-server.jar"
|
|
||||||
|
|
||||||
#define SCRCPY_VERSION "1.6"
|
|
||||||
|
|
||||||
#define SKIP_FRAMES
|
|
||||||
|
|
||||||
#undef WINDOWS_NOCONSOLE
|
|
||||||
|
|
||||||
BIN
u/app/scrcpy
BIN
u/app/scrcpy
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
263
u/build.ninja
263
u/build.ninja
@@ -1,263 +0,0 @@
|
|||||||
# This is the build file for project "scrcpy"
|
|
||||||
# It is autogenerated by the Meson build system.
|
|
||||||
# Do not edit by hand.
|
|
||||||
|
|
||||||
ninja_required_version = 1.5.1
|
|
||||||
|
|
||||||
# Rules for compiling.
|
|
||||||
|
|
||||||
rule c_COMPILER
|
|
||||||
command = ccache cc $ARGS -MD -MQ $out -MF '$DEPFILE' -o $out -c $in
|
|
||||||
deps = gcc
|
|
||||||
depfile = $DEPFILE
|
|
||||||
description = Compiling C object $out.
|
|
||||||
|
|
||||||
rule c_PCH
|
|
||||||
command = ccache cc $ARGS -MD -MQ $out -MF '$DEPFILE' -o $out -c $in
|
|
||||||
deps = gcc
|
|
||||||
depfile = $DEPFILE
|
|
||||||
description = Precompiling header $in.
|
|
||||||
|
|
||||||
|
|
||||||
# Rules for linking.
|
|
||||||
|
|
||||||
rule STATIC_LINKER
|
|
||||||
command = rm -f $out && gcc-ar $LINK_ARGS $out $in
|
|
||||||
description = Linking static target $out.
|
|
||||||
|
|
||||||
rule c_LINKER
|
|
||||||
command = ccache cc $ARGS -o $out $in $LINK_ARGS $aliasing
|
|
||||||
description = Linking target $out.
|
|
||||||
|
|
||||||
|
|
||||||
rule SHSYM
|
|
||||||
command = /usr/bin/meson --internal symbolextractor $in $out $CROSS
|
|
||||||
restat = 1
|
|
||||||
description = Generating symbol file $out.
|
|
||||||
|
|
||||||
# Other rules
|
|
||||||
|
|
||||||
rule CUSTOM_COMMAND
|
|
||||||
command = $COMMAND
|
|
||||||
description = $DESC
|
|
||||||
restat = 1
|
|
||||||
|
|
||||||
rule CUSTOM_COMMAND_DEP
|
|
||||||
command = $COMMAND
|
|
||||||
description = $DESC
|
|
||||||
deps = gcc
|
|
||||||
depfile = $DEPFILE
|
|
||||||
restat = 1
|
|
||||||
|
|
||||||
rule REGENERATE_BUILD
|
|
||||||
command = /usr/bin/meson --internal regenerate /home/rom/projects/scrcpy /home/rom/projects/scrcpy/u --backend ninja
|
|
||||||
description = Regenerating build files.
|
|
||||||
generator = 1
|
|
||||||
|
|
||||||
|
|
||||||
# Phony build target, always out of date
|
|
||||||
build PHONY: phony
|
|
||||||
|
|
||||||
# Build rules for targets
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_main.c.o: c_COMPILER ../app/src/main.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_main.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_command.c.o: c_COMPILER ../app/src/command.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_command.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_control_event.c.o: c_COMPILER ../app/src/control_event.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_control_event.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_controller.c.o: c_COMPILER ../app/src/controller.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_controller.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_convert.c.o: c_COMPILER ../app/src/convert.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_convert.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_decoder.c.o: c_COMPILER ../app/src/decoder.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_decoder.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_device.c.o: c_COMPILER ../app/src/device.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_device.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_file_handler.c.o: c_COMPILER ../app/src/file_handler.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_file_handler.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_fps_counter.c.o: c_COMPILER ../app/src/fps_counter.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_fps_counter.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_frames.c.o: c_COMPILER ../app/src/frames.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_frames.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_input_manager.c.o: c_COMPILER ../app/src/input_manager.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_input_manager.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_lock_util.c.o: c_COMPILER ../app/src/lock_util.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_lock_util.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_net.c.o: c_COMPILER ../app/src/net.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_net.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_recorder.c.o: c_COMPILER ../app/src/recorder.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_recorder.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_scrcpy.c.o: c_COMPILER ../app/src/scrcpy.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_scrcpy.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_screen.c.o: c_COMPILER ../app/src/screen.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_screen.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_server.c.o: c_COMPILER ../app/src/server.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_server.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_str_util.c.o: c_COMPILER ../app/src/str_util.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_str_util.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_tiny_xpm.c.o: c_COMPILER ../app/src/tiny_xpm.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_tiny_xpm.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_sys_unix_command.c.o: c_COMPILER ../app/src/sys/unix/command.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_sys_unix_command.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@scrcpy@exe/src_sys_unix_net.c.o: c_COMPILER ../app/src/sys/unix/net.c
|
|
||||||
DEPFILE = app/app@@scrcpy@exe/src_sys_unix_net.c.o.d
|
|
||||||
ARGS = -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/scrcpy: c_LINKER app/app@@scrcpy@exe/src_main.c.o app/app@@scrcpy@exe/src_command.c.o app/app@@scrcpy@exe/src_control_event.c.o app/app@@scrcpy@exe/src_controller.c.o app/app@@scrcpy@exe/src_convert.c.o app/app@@scrcpy@exe/src_decoder.c.o app/app@@scrcpy@exe/src_device.c.o app/app@@scrcpy@exe/src_file_handler.c.o app/app@@scrcpy@exe/src_fps_counter.c.o app/app@@scrcpy@exe/src_frames.c.o app/app@@scrcpy@exe/src_input_manager.c.o app/app@@scrcpy@exe/src_lock_util.c.o app/app@@scrcpy@exe/src_net.c.o app/app@@scrcpy@exe/src_recorder.c.o app/app@@scrcpy@exe/src_scrcpy.c.o app/app@@scrcpy@exe/src_screen.c.o app/app@@scrcpy@exe/src_server.c.o app/app@@scrcpy@exe/src_str_util.c.o app/app@@scrcpy@exe/src_tiny_xpm.c.o app/app@@scrcpy@exe/src_sys_unix_command.c.o app/app@@scrcpy@exe/src_sys_unix_net.c.o | /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so
|
|
||||||
LINK_ARGS = -Wl,--no-undefined -Wl,--as-needed -Wl,--start-group /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so -Wl,--end-group
|
|
||||||
|
|
||||||
build app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o: c_COMPILER ../app/tests/test_control_event_queue.c
|
|
||||||
DEPFILE = app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o.d
|
|
||||||
ARGS = -Iapp/app@@test_control_event_queue@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@test_control_event_queue@exe/src_control_event.c.o: c_COMPILER ../app/src/control_event.c
|
|
||||||
DEPFILE = app/app@@test_control_event_queue@exe/src_control_event.c.o.d
|
|
||||||
ARGS = -Iapp/app@@test_control_event_queue@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/test_control_event_queue: c_LINKER app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o app/app@@test_control_event_queue@exe/src_control_event.c.o | /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so
|
|
||||||
LINK_ARGS = -Wl,--no-undefined -Wl,--as-needed -Wl,--start-group /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so -Wl,--end-group
|
|
||||||
|
|
||||||
build app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o: c_COMPILER ../app/tests/test_control_event_serialize.c
|
|
||||||
DEPFILE = app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o.d
|
|
||||||
ARGS = -Iapp/app@@test_control_event_serialize@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@test_control_event_serialize@exe/src_control_event.c.o: c_COMPILER ../app/src/control_event.c
|
|
||||||
DEPFILE = app/app@@test_control_event_serialize@exe/src_control_event.c.o.d
|
|
||||||
ARGS = -Iapp/app@@test_control_event_serialize@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/test_control_event_serialize: c_LINKER app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o app/app@@test_control_event_serialize@exe/src_control_event.c.o | /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so
|
|
||||||
LINK_ARGS = -Wl,--no-undefined -Wl,--as-needed -Wl,--start-group /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so -Wl,--end-group
|
|
||||||
|
|
||||||
build app/app@@test_strutil@exe/tests_test_strutil.c.o: c_COMPILER ../app/tests/test_strutil.c
|
|
||||||
DEPFILE = app/app@@test_strutil@exe/tests_test_strutil.c.o.d
|
|
||||||
ARGS = -Iapp/app@@test_strutil@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/app@@test_strutil@exe/src_str_util.c.o: c_COMPILER ../app/src/str_util.c
|
|
||||||
DEPFILE = app/app@@test_strutil@exe/src_str_util.c.o.d
|
|
||||||
ARGS = -Iapp/app@@test_strutil@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT
|
|
||||||
|
|
||||||
build app/test_strutil: c_LINKER app/app@@test_strutil@exe/tests_test_strutil.c.o app/app@@test_strutil@exe/src_str_util.c.o | /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so
|
|
||||||
LINK_ARGS = -Wl,--no-undefined -Wl,--as-needed -Wl,--start-group /usr/lib/x86_64-linux-gnu/libavformat.so /usr/lib/x86_64-linux-gnu/libavcodec.so /usr/lib/x86_64-linux-gnu/libavutil.so /usr/lib/x86_64-linux-gnu/libSDL2.so -Wl,--end-group
|
|
||||||
|
|
||||||
build server/scrcpy-server.jar: CUSTOM_COMMAND ../server/. | /home/rom/projects/scrcpy/server/./scripts/build-wrapper.sh PHONY
|
|
||||||
COMMAND = /home/rom/projects/scrcpy/server/./scripts/build-wrapper.sh ../server/. server/scrcpy-server.jar debug
|
|
||||||
description = Generating$ scrcpy-server$ with$ a$ custom$ command.
|
|
||||||
|
|
||||||
build meson-run: CUSTOM_COMMAND
|
|
||||||
COMMAND = /usr/bin/meson --internal commandrunner /home/rom/projects/scrcpy /home/rom/projects/scrcpy/u '' /usr/bin/meson scripts/run-scrcpy.sh
|
|
||||||
description = Running$ external$ command$ run.
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build run: phony meson-run
|
|
||||||
|
|
||||||
# Test rules
|
|
||||||
|
|
||||||
build meson-test: CUSTOM_COMMAND all PHONY
|
|
||||||
COMMAND = /usr/bin/meson test --no-rebuild --print-errorlogs
|
|
||||||
DESC = Running$ all$ tests.
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build test: phony meson-test
|
|
||||||
|
|
||||||
build meson-benchmark: CUSTOM_COMMAND all PHONY
|
|
||||||
COMMAND = /usr/bin/meson test --benchmark --logbase benchmarklog --num-processes=1 --no-rebuild
|
|
||||||
DESC = Running$ benchmark$ suite.
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build benchmark: phony meson-benchmark
|
|
||||||
|
|
||||||
# Install rules
|
|
||||||
|
|
||||||
build meson-install: CUSTOM_COMMAND PHONY | all
|
|
||||||
DESC = Installing$ files.
|
|
||||||
COMMAND = /usr/bin/meson install --no-rebuild
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build install: phony meson-install
|
|
||||||
|
|
||||||
build meson-dist: CUSTOM_COMMAND PHONY
|
|
||||||
DESC = Creating$ source$ packages
|
|
||||||
COMMAND = /usr/bin/meson --internal dist /home/rom/projects/scrcpy /home/rom/projects/scrcpy/u /usr/bin/meson
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build dist: phony meson-dist
|
|
||||||
|
|
||||||
# Suffix
|
|
||||||
|
|
||||||
build meson-scan-build: CUSTOM_COMMAND PHONY
|
|
||||||
COMMAND = /usr/bin/meson --internal scanbuild /home/rom/projects/scrcpy /home/rom/projects/scrcpy/u /usr/bin/meson -Dbuild_app=true -Dbuild_server=true -Dcrossbuild_windows=false -Dhidpi_support=true -Doverride_server_path= -Dprebuilt_server= -Dskip_frames=true -Dwindows_noconsole=false
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build scan-build: phony meson-scan-build
|
|
||||||
|
|
||||||
build meson-uninstall: CUSTOM_COMMAND PHONY
|
|
||||||
COMMAND = /usr/bin/meson --internal uninstall
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build uninstall: phony meson-uninstall
|
|
||||||
|
|
||||||
build all: phony app/scrcpy app/test_control_event_queue app/test_control_event_serialize app/test_strutil server/scrcpy-server.jar
|
|
||||||
|
|
||||||
default all
|
|
||||||
|
|
||||||
build clean: phony meson-clean
|
|
||||||
|
|
||||||
build meson-clean-ctlist: CUSTOM_COMMAND PHONY
|
|
||||||
COMMAND = /usr/bin/meson --internal cleantrees /home/rom/projects/scrcpy/u/meson-private/cleantrees.dat
|
|
||||||
description = Cleaning$ custom$ target$ directories.
|
|
||||||
|
|
||||||
build clean-ctlist: phony meson-clean-ctlist
|
|
||||||
|
|
||||||
build meson-clean: CUSTOM_COMMAND PHONY | clean-ctlist
|
|
||||||
COMMAND = ninja -t clean
|
|
||||||
description = Cleaning.
|
|
||||||
|
|
||||||
build build.ninja: REGENERATE_BUILD ../meson.build ../app/meson.build ../server/meson.build meson-private/coredata.dat ../meson_options.txt
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build reconfigure: REGENERATE_BUILD PHONY
|
|
||||||
pool = console
|
|
||||||
|
|
||||||
build ../meson.build ../app/meson.build ../server/meson.build meson-private/coredata.dat ../meson_options.txt: phony
|
|
||||||
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_main.c.o' -MF 'app/app@@scrcpy@exe/src_main.c.o.d' -o 'app/app@@scrcpy@exe/src_main.c.o' -c ../app/src/main.c",
|
|
||||||
"file": "../app/src/main.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_command.c.o' -MF 'app/app@@scrcpy@exe/src_command.c.o.d' -o 'app/app@@scrcpy@exe/src_command.c.o' -c ../app/src/command.c",
|
|
||||||
"file": "../app/src/command.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_control_event.c.o' -MF 'app/app@@scrcpy@exe/src_control_event.c.o.d' -o 'app/app@@scrcpy@exe/src_control_event.c.o' -c ../app/src/control_event.c",
|
|
||||||
"file": "../app/src/control_event.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_controller.c.o' -MF 'app/app@@scrcpy@exe/src_controller.c.o.d' -o 'app/app@@scrcpy@exe/src_controller.c.o' -c ../app/src/controller.c",
|
|
||||||
"file": "../app/src/controller.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_convert.c.o' -MF 'app/app@@scrcpy@exe/src_convert.c.o.d' -o 'app/app@@scrcpy@exe/src_convert.c.o' -c ../app/src/convert.c",
|
|
||||||
"file": "../app/src/convert.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_decoder.c.o' -MF 'app/app@@scrcpy@exe/src_decoder.c.o.d' -o 'app/app@@scrcpy@exe/src_decoder.c.o' -c ../app/src/decoder.c",
|
|
||||||
"file": "../app/src/decoder.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_device.c.o' -MF 'app/app@@scrcpy@exe/src_device.c.o.d' -o 'app/app@@scrcpy@exe/src_device.c.o' -c ../app/src/device.c",
|
|
||||||
"file": "../app/src/device.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_file_handler.c.o' -MF 'app/app@@scrcpy@exe/src_file_handler.c.o.d' -o 'app/app@@scrcpy@exe/src_file_handler.c.o' -c ../app/src/file_handler.c",
|
|
||||||
"file": "../app/src/file_handler.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_fps_counter.c.o' -MF 'app/app@@scrcpy@exe/src_fps_counter.c.o.d' -o 'app/app@@scrcpy@exe/src_fps_counter.c.o' -c ../app/src/fps_counter.c",
|
|
||||||
"file": "../app/src/fps_counter.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_frames.c.o' -MF 'app/app@@scrcpy@exe/src_frames.c.o.d' -o 'app/app@@scrcpy@exe/src_frames.c.o' -c ../app/src/frames.c",
|
|
||||||
"file": "../app/src/frames.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_input_manager.c.o' -MF 'app/app@@scrcpy@exe/src_input_manager.c.o.d' -o 'app/app@@scrcpy@exe/src_input_manager.c.o' -c ../app/src/input_manager.c",
|
|
||||||
"file": "../app/src/input_manager.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_lock_util.c.o' -MF 'app/app@@scrcpy@exe/src_lock_util.c.o.d' -o 'app/app@@scrcpy@exe/src_lock_util.c.o' -c ../app/src/lock_util.c",
|
|
||||||
"file": "../app/src/lock_util.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_net.c.o' -MF 'app/app@@scrcpy@exe/src_net.c.o.d' -o 'app/app@@scrcpy@exe/src_net.c.o' -c ../app/src/net.c",
|
|
||||||
"file": "../app/src/net.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_recorder.c.o' -MF 'app/app@@scrcpy@exe/src_recorder.c.o.d' -o 'app/app@@scrcpy@exe/src_recorder.c.o' -c ../app/src/recorder.c",
|
|
||||||
"file": "../app/src/recorder.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_scrcpy.c.o' -MF 'app/app@@scrcpy@exe/src_scrcpy.c.o.d' -o 'app/app@@scrcpy@exe/src_scrcpy.c.o' -c ../app/src/scrcpy.c",
|
|
||||||
"file": "../app/src/scrcpy.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_screen.c.o' -MF 'app/app@@scrcpy@exe/src_screen.c.o.d' -o 'app/app@@scrcpy@exe/src_screen.c.o' -c ../app/src/screen.c",
|
|
||||||
"file": "../app/src/screen.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_server.c.o' -MF 'app/app@@scrcpy@exe/src_server.c.o.d' -o 'app/app@@scrcpy@exe/src_server.c.o' -c ../app/src/server.c",
|
|
||||||
"file": "../app/src/server.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_str_util.c.o' -MF 'app/app@@scrcpy@exe/src_str_util.c.o.d' -o 'app/app@@scrcpy@exe/src_str_util.c.o' -c ../app/src/str_util.c",
|
|
||||||
"file": "../app/src/str_util.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_tiny_xpm.c.o' -MF 'app/app@@scrcpy@exe/src_tiny_xpm.c.o.d' -o 'app/app@@scrcpy@exe/src_tiny_xpm.c.o' -c ../app/src/tiny_xpm.c",
|
|
||||||
"file": "../app/src/tiny_xpm.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_sys_unix_command.c.o' -MF 'app/app@@scrcpy@exe/src_sys_unix_command.c.o.d' -o 'app/app@@scrcpy@exe/src_sys_unix_command.c.o' -c ../app/src/sys/unix/command.c",
|
|
||||||
"file": "../app/src/sys/unix/command.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@scrcpy@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@scrcpy@exe/src_sys_unix_net.c.o' -MF 'app/app@@scrcpy@exe/src_sys_unix_net.c.o.d' -o 'app/app@@scrcpy@exe/src_sys_unix_net.c.o' -c ../app/src/sys/unix/net.c",
|
|
||||||
"file": "../app/src/sys/unix/net.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@test_control_event_queue@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o' -MF 'app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o.d' -o 'app/app@@test_control_event_queue@exe/tests_test_control_event_queue.c.o' -c ../app/tests/test_control_event_queue.c",
|
|
||||||
"file": "../app/tests/test_control_event_queue.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@test_control_event_queue@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@test_control_event_queue@exe/src_control_event.c.o' -MF 'app/app@@test_control_event_queue@exe/src_control_event.c.o.d' -o 'app/app@@test_control_event_queue@exe/src_control_event.c.o' -c ../app/src/control_event.c",
|
|
||||||
"file": "../app/src/control_event.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@test_control_event_serialize@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o' -MF 'app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o.d' -o 'app/app@@test_control_event_serialize@exe/tests_test_control_event_serialize.c.o' -c ../app/tests/test_control_event_serialize.c",
|
|
||||||
"file": "../app/tests/test_control_event_serialize.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@test_control_event_serialize@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@test_control_event_serialize@exe/src_control_event.c.o' -MF 'app/app@@test_control_event_serialize@exe/src_control_event.c.o.d' -o 'app/app@@test_control_event_serialize@exe/src_control_event.c.o' -c ../app/src/control_event.c",
|
|
||||||
"file": "../app/src/control_event.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@test_strutil@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@test_strutil@exe/tests_test_strutil.c.o' -MF 'app/app@@test_strutil@exe/tests_test_strutil.c.o.d' -o 'app/app@@test_strutil@exe/tests_test_strutil.c.o' -c ../app/tests/test_strutil.c",
|
|
||||||
"file": "../app/tests/test_strutil.c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"directory": "/home/rom/projects/scrcpy/u",
|
|
||||||
"command": "ccache cc -Iapp/app@@test_strutil@exe -Iapp -I../app -I../app/src -I/usr/include/x86_64-linux-gnu -I/usr/include/SDL2 -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=c11 -g -D_REENTRANT -MD -MQ 'app/app@@test_strutil@exe/src_str_util.c.o' -MF 'app/app@@test_strutil@exe/src_str_util.c.o.d' -o 'app/app@@test_strutil@exe/src_str_util.c.o' -c ../app/src/str_util.c",
|
|
||||||
"file": "../app/src/str_util.c"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
Build started at 2018-11-12T14:04:31.900569
|
|
||||||
Main binary: /usr/bin/python3
|
|
||||||
Python system: Linux
|
|
||||||
The Meson build system
|
|
||||||
Version: 0.48.1
|
|
||||||
Source dir: /home/rom/projects/scrcpy
|
|
||||||
Build dir: /home/rom/projects/scrcpy/u
|
|
||||||
Build type: native build
|
|
||||||
Project name: scrcpy
|
|
||||||
Project version: 1.6
|
|
||||||
Native C compiler: ccache cc (gcc 8.2.0 "cc (Debian 8.2.0-9) 8.2.0")
|
|
||||||
Build machine cpu family: x86_64
|
|
||||||
Build machine cpu: x86_64
|
|
||||||
Dependency libavformat found: YES (cached)
|
|
||||||
Dependency libavcodec found: YES (cached)
|
|
||||||
Dependency libavutil found: YES (cached)
|
|
||||||
Dependency sdl2 found: YES (cached)
|
|
||||||
Configuring config.h using configuration
|
|
||||||
Adding test "test_control_event_queue"
|
|
||||||
Adding test "test_control_event_serialize"
|
|
||||||
Adding test "test_strutil"
|
|
||||||
Program ./scripts/build-wrapper.sh found: YES (/home/rom/projects/scrcpy/server/./scripts/build-wrapper.sh)
|
|
||||||
DEPRECATION: build_always is deprecated. Combine build_by_default and build_always_stale instead.
|
|
||||||
Build targets in project: 6
|
|
||||||
Found ninja-1.8.2 at /usr/bin/ninja
|
|
||||||
Running compile:
|
|
||||||
Working directory: /tmp/tmpk1bh9k5g
|
|
||||||
Command line: ccache cc /tmp/tmpk1bh9k5g/testfile.c -pipe -D_FILE_OFFSET_BITS=64 -c -o /tmp/tmpk1bh9k5g/output.obj -O0 --print-search-dirs
|
|
||||||
|
|
||||||
Code:
|
|
||||||
|
|
||||||
Compiler stdout:
|
|
||||||
install: /usr/lib/gcc/x86_64-linux-gnu/8/
|
|
||||||
programs: =/usr/lib/gcc/x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/bin/
|
|
||||||
libraries: =/usr/lib/gcc/x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/8/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../lib/:/lib/x86_64-linux-gnu/8/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/8/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib/:/usr/lib/gcc/x86_64-linux-gnu/8/../../../:/lib/:/usr/lib/
|
|
||||||
|
|
||||||
Compiler stderr:
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
int main(int argc, char **argv) { int class=0; return class; }
|
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user