mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-04 19:24:41 +01:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0025ef185 | ||
|
|
67bb26d62a | ||
|
|
34df0b476a | ||
|
|
6072409048 | ||
|
|
d16c9e3a26 | ||
|
|
f6dd00540e | ||
|
|
1d2d7d7965 | ||
|
|
79ab94c5a9 | ||
|
|
ae47396e27 |
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -80,8 +80,16 @@ jobs:
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
|
||||
# SDL3 is not available in Ubuntu yet
|
||||
- name: Install SDL3
|
||||
run: |
|
||||
app/deps/sdl.sh linux native shared
|
||||
|
||||
- name: Test
|
||||
run: release/test_client.sh
|
||||
run: |
|
||||
export PKG_CONFIG_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib/pkgconfig
|
||||
export LD_LIBRARY_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib
|
||||
release/test_client.sh
|
||||
|
||||
build-linux-x86_64:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=2.32.8
|
||||
VERSION=3.2.18
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=dd35e05644ae527848d02433bec24dd0ea65db59faecf1a0e5d1880c533dac2c
|
||||
SHA256SUM=51539fa13e546bc50c632beed3f34257de2baa38a4c642048de56377903b4265
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -35,45 +35,48 @@ else
|
||||
cd "$DIRNAME"
|
||||
|
||||
conf=(
|
||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
||||
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
|
||||
)
|
||||
|
||||
if [[ "$HOST" == linux ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-video-wayland
|
||||
--enable-video-x11
|
||||
-DSDL_WAYLAND=ON
|
||||
-DSDL_X11=ON
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$LINK_TYPE" == static ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-static
|
||||
--disable-shared
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
)
|
||||
else
|
||||
conf+=(
|
||||
--disable-static
|
||||
--enable-shared
|
||||
-DBUILD_SHARED_LIBS=ON
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
if [[ "$HOST" = win32 ]]
|
||||
then
|
||||
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-i686.cmake"
|
||||
elif [[ "$HOST" = win64 ]]
|
||||
then
|
||||
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-x86_64.cmake"
|
||||
else
|
||||
echo "Unsupported cross-build to host: $HOST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
conf+=(
|
||||
--host="$HOST_TRIPLET"
|
||||
-DCMAKE_TOOLCHAIN_FILE="$SOURCES_DIR/$PROJECT_DIR/build-scripts/$TOOLCHAIN_FILENAME"
|
||||
)
|
||||
fi
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
|
||||
cmake "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
|
||||
fi
|
||||
|
||||
make -j
|
||||
# There is no "make install-strip"
|
||||
make install
|
||||
# Strip manually
|
||||
if [[ "$LINK_TYPE" == shared && "$HOST" == win* ]]
|
||||
then
|
||||
${HOST_TRIPLET}-strip "$INSTALL_DIR/$DIRNAME/bin/SDL2.dll"
|
||||
fi
|
||||
cmake --build .
|
||||
cmake --install .
|
||||
|
||||
@@ -47,7 +47,10 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
|
||||
static bool
|
||||
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
|
||||
@@ -10,20 +10,30 @@
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
|
||||
|
||||
static bool
|
||||
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) {
|
||||
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
decoder->frame = av_frame_alloc();
|
||||
if (!decoder->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx, session)) {
|
||||
av_frame_free(&decoder->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
decoder->ctx = ctx;
|
||||
|
||||
// A video stream must have a session
|
||||
assert(session || ctx->codec_type != AVMEDIA_TYPE_VIDEO);
|
||||
|
||||
if (session) {
|
||||
decoder->session = *session;
|
||||
}
|
||||
|
||||
memset(&decoder->frame_size, 0, sizeof(decoder->frame_size));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,6 +71,32 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
// a frame was received
|
||||
|
||||
if (decoder->ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
assert(decoder->frame->width >= 0);
|
||||
assert(decoder->frame->height >= 0);
|
||||
struct sc_size frame_size = {
|
||||
.width = decoder->frame->width,
|
||||
.height = decoder->frame->height,
|
||||
};
|
||||
if (decoder->frame_size.width != frame_size.width
|
||||
|| decoder->frame_size.height != frame_size.height) {
|
||||
// The frame size has changed, check if it matches the session
|
||||
uint32_t sw = decoder->session.video.width;
|
||||
uint32_t sh = decoder->session.video.height;
|
||||
if (frame_size.width != sw || frame_size.height != sh) {
|
||||
LOGW("Unexpected video size: %" PRIu32 "x%" PRIu32
|
||||
" (expected %" PRIu32 "x%" PRIu32 ")",
|
||||
frame_size.width, frame_size.height, sw, sh);
|
||||
|
||||
LOGW("The encoder did not respect the requested size, "
|
||||
"please retry with a lower resolution (-m/--max-size)");
|
||||
}
|
||||
}
|
||||
|
||||
decoder->frame_size = frame_size;
|
||||
}
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
|
||||
decoder->frame);
|
||||
av_frame_unref(decoder->frame);
|
||||
@@ -74,9 +110,17 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) {
|
||||
sc_decoder_push_session(struct sc_decoder *decoder,
|
||||
const struct sc_stream_session *session) {
|
||||
decoder->session = *session;
|
||||
return sc_frame_source_sinks_push_session(&decoder->frame_source, session);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_open(decoder, ctx);
|
||||
return sc_decoder_open(decoder, ctx, session);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -92,6 +136,14 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
||||
return sc_decoder_push(decoder, packet);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_push_session(struct sc_packet_sink *sink,
|
||||
const struct sc_stream_session *session) {
|
||||
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_push_session(decoder, session);
|
||||
}
|
||||
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
decoder->name = name; // statically allocated
|
||||
@@ -101,6 +153,7 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
.open = sc_decoder_packet_sink_open,
|
||||
.close = sc_decoder_packet_sink_close,
|
||||
.push = sc_decoder_packet_sink_push,
|
||||
.push_session = sc_decoder_packet_sink_push_session,
|
||||
};
|
||||
|
||||
decoder->packet_sink.ops = &ops;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "trait/frame_source.h"
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
@@ -16,6 +17,9 @@ struct sc_decoder {
|
||||
|
||||
AVCodecContext *ctx;
|
||||
AVFrame *frame;
|
||||
|
||||
struct sc_stream_session session; // only initialized for video stream
|
||||
struct sc_size frame_size;
|
||||
};
|
||||
|
||||
// The name must be statically allocated (e.g. a string literal)
|
||||
|
||||
@@ -10,16 +10,18 @@
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
||||
|
||||
static bool
|
||||
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
dframe->frame = av_frame_alloc();
|
||||
if (!dframe->frame) {
|
||||
sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
|
||||
const AVFrame *frame) {
|
||||
dpacket->type = SC_DELAYED_PACKET_TYPE_FRAME;
|
||||
dpacket->frame = av_frame_alloc();
|
||||
if (!dpacket->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (av_frame_ref(dframe->frame, frame)) {
|
||||
if (av_frame_ref(dpacket->frame, frame)) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&dframe->frame);
|
||||
av_frame_free(&dpacket->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,9 +29,18 @@ sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
|
||||
av_frame_unref(dframe->frame);
|
||||
av_frame_free(&dframe->frame);
|
||||
sc_delayed_packet_init_session(struct sc_delayed_packet *dpacket,
|
||||
const struct sc_stream_session *session) {
|
||||
dpacket->type = SC_DELAYED_PACKET_TYPE_SESSION;
|
||||
dpacket->session = *session;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_packet_destroy(struct sc_delayed_packet *dpacket) {
|
||||
if (dpacket->type == SC_DELAYED_PACKET_TYPE_FRAME) {
|
||||
av_frame_unref(dpacket->frame);
|
||||
av_frame_free(&dpacket->frame);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -50,43 +61,52 @@ run_buffering(void *data) {
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
|
||||
struct sc_delayed_packet dpacket = sc_vecdeque_pop(&db->queue);
|
||||
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts);
|
||||
bool ok;
|
||||
if (dpacket.type == SC_DELAYED_PACKET_TYPE_FRAME) {
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_FROM_US(dpacket.frame->pts);
|
||||
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
if (stopped) {
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
goto stopped;
|
||||
}
|
||||
if (stopped) {
|
||||
sc_delayed_packet_destroy(&dpacket);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
#endif
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
ok = sc_frame_source_sinks_push(&db->frame_source, dpacket.frame);
|
||||
} else {
|
||||
assert(dpacket.type == SC_DELAYED_PACKET_TYPE_SESSION);
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
ok = sc_frame_source_sinks_push_session(&db->frame_source,
|
||||
&dpacket.session);
|
||||
}
|
||||
|
||||
sc_delayed_packet_destroy(&dpacket);
|
||||
if (!ok) {
|
||||
LOGE("Delayed frame could not be pushed, stopping");
|
||||
LOGE("Delayed packet could not be pushed, stopping");
|
||||
sc_mutex_lock(&db->mutex);
|
||||
// Prevent to push any new frame
|
||||
// Prevent to push any new packet
|
||||
db->stopped = true;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
goto stopped;
|
||||
@@ -98,8 +118,8 @@ stopped:
|
||||
|
||||
// Flush queue
|
||||
while (!sc_vecdeque_is_empty(&db->queue)) {
|
||||
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_frame_destroy(dframe);
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_packet_destroy(dpacket);
|
||||
}
|
||||
|
||||
LOGD("Buffering thread ended");
|
||||
@@ -109,9 +129,11 @@ stopped:
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
bool ok = sc_mutex_init(&db->mutex);
|
||||
if (!ok) {
|
||||
@@ -132,7 +154,7 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
sc_vecdeque_init(&db->queue);
|
||||
db->stopped = false;
|
||||
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx, session)) {
|
||||
goto error_destroy_wait_cond;
|
||||
}
|
||||
|
||||
@@ -196,24 +218,56 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
|
||||
return sc_frame_source_sinks_push(&db->frame_source, frame);
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe;
|
||||
bool ok = sc_delayed_frame_init(&dframe, frame);
|
||||
if (!ok) {
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
|
||||
if (!dpacket) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dframe.push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
ok = sc_vecdeque_push(&db->queue, dframe);
|
||||
bool ok = sc_delayed_packet_init_frame(dpacket, frame);
|
||||
if (!ok) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dpacket->push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_push_session(struct sc_frame_sink *sink,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
|
||||
sc_mutex_lock(&db->mutex);
|
||||
|
||||
if (db->stopped) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
|
||||
if (!dpacket) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_delayed_packet_init_session(dpacket, session);
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dpacket->push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
@@ -235,6 +289,7 @@ sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
|
||||
.open = sc_delay_buffer_frame_sink_open,
|
||||
.close = sc_delay_buffer_frame_sink_close,
|
||||
.push = sc_delay_buffer_frame_sink_push,
|
||||
.push_session = sc_delay_buffer_frame_sink_push_session,
|
||||
};
|
||||
|
||||
db->frame_sink.ops = &ops;
|
||||
|
||||
@@ -18,14 +18,23 @@
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
struct sc_delayed_frame {
|
||||
AVFrame *frame;
|
||||
enum sc_delayed_packet_type {
|
||||
SC_DELAYED_PACKET_TYPE_FRAME,
|
||||
SC_DELAYED_PACKET_TYPE_SESSION,
|
||||
};
|
||||
|
||||
struct sc_delayed_packet {
|
||||
enum sc_delayed_packet_type type;
|
||||
union {
|
||||
AVFrame *frame;
|
||||
struct sc_stream_session session;
|
||||
};
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
sc_tick push_date;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
|
||||
struct sc_delayed_packet_queue SC_VECDEQUE(struct sc_delayed_packet);
|
||||
|
||||
struct sc_delay_buffer {
|
||||
struct sc_frame_source frame_source; // frame source trait
|
||||
@@ -40,7 +49,7 @@ struct sc_delay_buffer {
|
||||
sc_cond wait_cond;
|
||||
|
||||
struct sc_clock clock;
|
||||
struct sc_delayed_frame_queue queue;
|
||||
struct sc_delayed_packet_queue queue;
|
||||
bool stopped;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
#define SC_PACKET_HEADER_SIZE 12
|
||||
|
||||
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
|
||||
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
|
||||
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 62)
|
||||
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 61)
|
||||
|
||||
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
||||
|
||||
@@ -63,48 +63,75 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
|
||||
uint32_t *height) {
|
||||
uint8_t data[8];
|
||||
ssize_t r = net_recv_all(demuxer->socket, data, 8);
|
||||
if (r < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*width = sc_read32be(data);
|
||||
*height = sc_read32be(data + 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
static inline bool
|
||||
sc_demuxer_recv_header(struct sc_demuxer *demuxer,
|
||||
uint8_t buf[static SC_PACKET_HEADER_SIZE]) {
|
||||
// The video and audio streams contain a sequence of raw packets (as
|
||||
// provided by MediaCodec), each prefixed with a "meta" header.
|
||||
//
|
||||
// The "meta" header length is 12 bytes:
|
||||
// The "meta" header length is 12 bytes.
|
||||
//
|
||||
//
|
||||
// If the MSB is 1, then it is a session packet (for a video stream only),
|
||||
// which only contains a 12-byte header:
|
||||
//
|
||||
// byte 0 byte 1 byte 2 byte 3
|
||||
// 10000000 00000000 00000000 00000000
|
||||
// ^<-------------------------------->
|
||||
// | padding
|
||||
// `- session packet flag
|
||||
//
|
||||
// byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
|
||||
// ........ ........ ........ ........ ........ ........ ........ ........
|
||||
// <---------------------------------> <--------------------------------->
|
||||
// video width video height
|
||||
//
|
||||
//
|
||||
// If the MSB is 0, then it is a media packet, comprised of a 12-byte header
|
||||
// followed by <packet_size> bytes containing the packet/frame:
|
||||
//
|
||||
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
||||
// <-------------> <-----> <-----------------------------...
|
||||
// PTS packet raw packet
|
||||
// size
|
||||
//
|
||||
// It is followed by <packet_size> bytes containing the packet/frame.
|
||||
//
|
||||
// The most significant bits of the PTS are used for packet flags:
|
||||
//
|
||||
// byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0
|
||||
// CK...... ........ ........ ........ ........ ........ ........ ........
|
||||
// ^^<------------------------------------------------------------------->
|
||||
// || PTS
|
||||
// | `- key frame
|
||||
// `-- config packet
|
||||
// byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
|
||||
// 0CK..... ........ ........ ........ ........ ........ ........ ........
|
||||
// ^^^<------------------------------------------------------------------>
|
||||
// ||| PTS
|
||||
// || `- key frame
|
||||
// | `-- config packet
|
||||
// `--- media packet flag
|
||||
//
|
||||
// byte 8 byte 9 byte 10 byte 11
|
||||
// ........ ........ ........ ........ ........ ........ . . .
|
||||
// <---------------------------------> <---------------- . . .
|
||||
// packet size raw packet
|
||||
//
|
||||
ssize_t r = net_recv_all(demuxer->socket, buf, SC_PACKET_HEADER_SIZE);
|
||||
assert(r <= SC_PACKET_HEADER_SIZE);
|
||||
return r == SC_PACKET_HEADER_SIZE;
|
||||
}
|
||||
|
||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
|
||||
if (r < SC_PACKET_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
sc_demuxer_is_session(const uint8_t *header) {
|
||||
return header[0] & 0x80;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_demuxer_parse_session(const uint8_t *header,
|
||||
struct sc_stream_session *session) {
|
||||
assert(sc_demuxer_is_session(header));
|
||||
session->video.width = sc_read32be(&header[4]);
|
||||
session->video.height = sc_read32be(&header[8]);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
|
||||
AVPacket *packet) {
|
||||
assert(!sc_demuxer_is_session(header));
|
||||
uint64_t pts_flags = sc_read64be(header);
|
||||
uint32_t len = sc_read32be(&header[8]);
|
||||
assert(len);
|
||||
@@ -114,7 +141,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r = net_recv_all(demuxer->socket, packet->data, len);
|
||||
ssize_t r = net_recv_all(demuxer->socket, packet->data, len);
|
||||
if (r < 0 || ((uint32_t) r) < len) {
|
||||
av_packet_unref(packet);
|
||||
return false;
|
||||
@@ -187,17 +214,28 @@ run_demuxer(void *data) {
|
||||
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||
struct sc_stream_session session_data;
|
||||
|
||||
struct sc_stream_session *session = NULL;
|
||||
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
|
||||
bool ok = sc_demuxer_recv_header(demuxer, header);
|
||||
if (!ok) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
codec_ctx->width = width;
|
||||
codec_ctx->height = height;
|
||||
if (!sc_demuxer_is_session(header)) {
|
||||
LOGE("Unexpected packet (not a session header)");
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
session = &session_data;
|
||||
sc_demuxer_parse_session(header, session);
|
||||
|
||||
codec_ctx->width = session_data.video.width;
|
||||
codec_ctx->height = session_data.video.height;
|
||||
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
} else {
|
||||
// Hardcoded audio properties
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
@@ -219,7 +257,8 @@ run_demuxer(void *data) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx,
|
||||
session)) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
@@ -241,27 +280,39 @@ run_demuxer(void *data) {
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
||||
bool ok = sc_demuxer_recv_header(demuxer, header);
|
||||
if (!ok) {
|
||||
// end of stream
|
||||
status = SC_DEMUXER_STATUS_EOS;
|
||||
break;
|
||||
}
|
||||
|
||||
if (must_merge_config_packet) {
|
||||
// Prepend any config packet to the next media packet
|
||||
ok = sc_packet_merger_merge(&merger, packet);
|
||||
if (sc_demuxer_is_session(header)) {
|
||||
sc_demuxer_parse_session(header, &session_data);
|
||||
ok = sc_packet_source_sinks_push_session(&demuxer->packet_source,
|
||||
&session_data);
|
||||
if (!ok) {
|
||||
av_packet_unref(packet);
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sc_demuxer_recv_packet(demuxer, header, packet);
|
||||
|
||||
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// The sink already logged its concrete error
|
||||
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_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ sc_display_to_sdl_color_space(enum AVColorSpace color_space,
|
||||
|
||||
switch (color_space) {
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_RGB:
|
||||
return full_range ? SDL_COLORSPACE_BT709_FULL
|
||||
: SDL_COLORSPACE_BT709_LIMITED;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
@@ -373,7 +374,9 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
SDL_RenderClear(display->renderer);
|
||||
bool always_ok = SDL_RenderClear(display->renderer);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
@@ -418,6 +421,8 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(display->renderer);
|
||||
always_ok = SDL_RenderPresent(display->renderer);
|
||||
assert(always_ok);
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -674,7 +674,9 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
|
||||
bool always_ok = SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
int32_t x = event->x * dw;
|
||||
|
||||
@@ -81,24 +81,6 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
|
||||
void
|
||||
sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
|
||||
#ifdef __APPLE__
|
||||
// Workaround for SDL bug on macOS:
|
||||
// <https://github.com/libsdl-org/SDL/issues/5340>
|
||||
if (capture) {
|
||||
float mouse_x, mouse_y;
|
||||
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
int x, y, w, h;
|
||||
SDL_GetWindowPosition(mc->window, &x, &y);
|
||||
SDL_GetWindowSize(mc->window, &w, &h);
|
||||
|
||||
bool outside_window = mouse_x < x || mouse_x >= x + w
|
||||
|| mouse_y < y || mouse_y >= y + h;
|
||||
if (outside_window) {
|
||||
SDL_WarpMouseInWindow(mc->window, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool ok = SDL_SetWindowRelativeMouseMode(mc->window, capture);
|
||||
if (!ok) {
|
||||
LOGE("Could not set relative mouse mode to %s: %s",
|
||||
|
||||
@@ -541,7 +541,10 @@ sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
|
||||
|
||||
static bool
|
||||
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
|
||||
AVCodecContext *ctx) {
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
||||
// only written from this thread, no need to lock
|
||||
assert(!recorder->video_init);
|
||||
@@ -635,7 +638,10 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
||||
|
||||
static bool
|
||||
sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
|
||||
AVCodecContext *ctx) {
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
||||
assert(recorder->audio);
|
||||
// only written from this thread, no need to lock
|
||||
|
||||
@@ -32,7 +32,9 @@ static struct sc_size
|
||||
get_window_size(const struct sc_screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(screen->window, &width, &height);
|
||||
bool always_ok = SDL_GetWindowSize(screen->window, &width, &height);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_size size;
|
||||
size.width = width;
|
||||
@@ -44,7 +46,9 @@ static struct sc_point
|
||||
get_window_position(const struct sc_screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
bool always_ok = SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_point point;
|
||||
point.x = x;
|
||||
@@ -58,7 +62,10 @@ set_window_size(struct sc_screen *screen, struct sc_size new_size) {
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
bool always_ok =
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
}
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
@@ -171,7 +178,9 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
bool always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
@@ -256,9 +265,11 @@ event_watcher(void *data, SDL_Event *event) {
|
||||
|
||||
static bool
|
||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
(void) screen;
|
||||
@@ -502,7 +513,12 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
screen->req.height);
|
||||
|
||||
set_window_size(screen, window_size);
|
||||
SDL_SetWindowPosition(screen->window, x, y);
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
always_ok = SDL_SetWindowPosition(screen->window, x, y);
|
||||
assert(always_ok);
|
||||
|
||||
if (screen->req.fullscreen) {
|
||||
sc_screen_toggle_fullscreen(screen);
|
||||
@@ -512,13 +528,17 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
sc_fps_counter_start(&screen->fps_counter);
|
||||
}
|
||||
|
||||
SDL_ShowWindow(screen->window);
|
||||
always_ok = SDL_ShowWindow(screen->window);
|
||||
assert(always_ok);
|
||||
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_hide_window(struct sc_screen *screen) {
|
||||
SDL_HideWindow(screen->window);
|
||||
bool always_ok = SDL_HideWindow(screen->window);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -764,8 +784,16 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
|
||||
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
|
||||
|
||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||
SDL_SetWindowPosition(screen->window, new_x, new_y);
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
always_ok = SDL_SetWindowSize(screen->window, optimal_size.width,
|
||||
optimal_size.height);
|
||||
assert(always_ok);
|
||||
|
||||
always_ok = SDL_SetWindowPosition(screen->window, new_x, new_y);
|
||||
assert(always_ok);
|
||||
|
||||
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
||||
optimal_size.height);
|
||||
}
|
||||
@@ -778,13 +806,20 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
always_ok = SDL_RestoreWindow(screen->window);
|
||||
assert(always_ok);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||
always_ok = SDL_SetWindowSize(screen->window, content_size.width,
|
||||
content_size.height);
|
||||
assert(always_ok);
|
||||
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
content_size.height);
|
||||
}
|
||||
@@ -913,10 +948,16 @@ sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
||||
|
||||
void
|
||||
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
||||
int ww, wh, dw, dh;
|
||||
SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||
SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
always_ok = SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||
assert(always_ok);
|
||||
|
||||
always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
assert(always_ok);
|
||||
|
||||
// scale for HiDPI (64 bits for intermediate multiplications)
|
||||
*x = (int64_t) *x * dw / ww;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
/**
|
||||
* Frame sink trait.
|
||||
*
|
||||
@@ -17,9 +19,16 @@ struct sc_frame_sink {
|
||||
|
||||
struct sc_frame_sink_ops {
|
||||
/* The codec context is valid until the sink is closed */
|
||||
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx);
|
||||
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
void (*close)(struct sc_frame_sink *sink);
|
||||
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Optional callback to be notified of a new stream session.
|
||||
*/
|
||||
bool (*push_session)(struct sc_frame_sink *sink,
|
||||
const struct sc_stream_session *session);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,11 +27,12 @@ sc_frame_source_sinks_close_firsts(struct sc_frame_source *source,
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_open(struct sc_frame_source *source,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
if (!sink->ops->open(sink, ctx, session)) {
|
||||
sc_frame_source_sinks_close_firsts(source, i);
|
||||
return false;
|
||||
}
|
||||
@@ -59,3 +60,18 @@ sc_frame_source_sinks_push(struct sc_frame_source *source,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = source->sinks[i];
|
||||
if (sink->ops->push_session &&
|
||||
!sink->ops->push_session(sink, session)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ sc_frame_source_add_sink(struct sc_frame_source *source,
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_open(struct sc_frame_source *source,
|
||||
const AVCodecContext *ctx);
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
void
|
||||
sc_frame_source_sinks_close(struct sc_frame_source *source);
|
||||
@@ -37,4 +38,8 @@ bool
|
||||
sc_frame_source_sinks_push(struct sc_frame_source *source,
|
||||
const AVFrame *frame);
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,12 +15,28 @@ struct sc_packet_sink {
|
||||
const struct sc_packet_sink_ops *ops;
|
||||
};
|
||||
|
||||
struct sc_stream_session_video {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct sc_stream_session {
|
||||
struct sc_stream_session_video video;
|
||||
};
|
||||
|
||||
struct sc_packet_sink_ops {
|
||||
/* The codec context is valid until the sink is closed */
|
||||
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx);
|
||||
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
void (*close)(struct sc_packet_sink *sink);
|
||||
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
||||
|
||||
/**
|
||||
* Optional callback to be notified of a new stream session.
|
||||
*/
|
||||
bool (*push_session)(struct sc_packet_sink *sink,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
/*/
|
||||
* Called when the input stream has been disabled at runtime.
|
||||
*
|
||||
|
||||
@@ -27,11 +27,12 @@ sc_packet_source_sinks_close_firsts(struct sc_packet_source *source,
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_open(struct sc_packet_source *source,
|
||||
AVCodecContext *ctx) {
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
if (!sink->ops->open(sink, ctx, session)) {
|
||||
sc_packet_source_sinks_close_firsts(source, i);
|
||||
return false;
|
||||
}
|
||||
@@ -60,6 +61,20 @@ sc_packet_source_sinks_push(struct sc_packet_source *source,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->push_session(sink, session)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_disable(struct sc_packet_source *source) {
|
||||
assert(source->sink_count);
|
||||
|
||||
@@ -28,7 +28,8 @@ sc_packet_source_add_sink(struct sc_packet_source *source,
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_open(struct sc_packet_source *source,
|
||||
AVCodecContext *ctx);
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_close(struct sc_packet_source *source);
|
||||
@@ -37,6 +38,10 @@ bool
|
||||
sc_packet_source_sinks_push(struct sc_packet_source *source,
|
||||
const AVPacket *packet);
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_disable(struct sc_packet_source *source);
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
|
||||
static void
|
||||
sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
SDL_RenderClear(screen->renderer);
|
||||
bool always_ok = SDL_RenderClear(screen->renderer);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
if (screen->texture) {
|
||||
bool ok =
|
||||
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
|
||||
@@ -19,7 +22,9 @@ sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
LOGW("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
SDL_RenderPresent(screen->renderer);
|
||||
|
||||
always_ok = SDL_RenderPresent(screen->renderer);
|
||||
assert(always_ok);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -146,9 +146,11 @@ run_v4l2_sink(void *data) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
bool ok = sc_frame_buffer_init(&vs->fb);
|
||||
if (!ok) {
|
||||
@@ -326,9 +328,10 @@ sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) {
|
||||
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||
return sc_v4l2_sink_open(vs, ctx);
|
||||
return sc_v4l2_sink_open(vs, ctx, session);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -409,12 +409,11 @@ with any client which uses the same protocol.
|
||||
|
||||
For simplicity, some [server-specific options] have been added to produce raw
|
||||
streams easily:
|
||||
- `send_device_meta=false`: disable the device metata (in practice, the device
|
||||
- `send_device_meta=false`: disable device metadata (in practice, the device
|
||||
name) sent on the _first_ socket
|
||||
- `send_frame_meta=false`: disable the 12-byte header for each packet
|
||||
- `send_dummy_byte`: disable the dummy byte sent on forward connections
|
||||
- `send_codec_meta`: disable the codec information (and initial device size for
|
||||
video)
|
||||
- `send_stream_meta`: disable codec and video size metadata
|
||||
- `raw_stream`: disable all the above
|
||||
|
||||
[server-specific options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Options.java#L309-L329
|
||||
|
||||
@@ -78,7 +78,7 @@ public class Options {
|
||||
private boolean sendDeviceMeta = true; // send device name and size
|
||||
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
||||
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
|
||||
private boolean sendCodecMeta = true; // write the codec metadata before the stream
|
||||
private boolean sendStreamMeta = true; // write the stream metadata (codec and session)
|
||||
|
||||
public Ln.Level getLogLevel() {
|
||||
return logLevel;
|
||||
@@ -284,8 +284,8 @@ public class Options {
|
||||
return sendDummyByte;
|
||||
}
|
||||
|
||||
public boolean getSendCodecMeta() {
|
||||
return sendCodecMeta;
|
||||
public boolean getSendStreamMeta() {
|
||||
return sendStreamMeta;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodLength")
|
||||
@@ -500,8 +500,8 @@ public class Options {
|
||||
case "send_dummy_byte":
|
||||
options.sendDummyByte = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_codec_meta":
|
||||
options.sendCodecMeta = Boolean.parseBoolean(value);
|
||||
case "send_stream_meta":
|
||||
options.sendStreamMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "raw_stream":
|
||||
boolean rawStream = Boolean.parseBoolean(value);
|
||||
@@ -509,7 +509,7 @@ public class Options {
|
||||
options.sendDeviceMeta = false;
|
||||
options.sendFrameMeta = false;
|
||||
options.sendDummyByte = false;
|
||||
options.sendCodecMeta = false;
|
||||
options.sendStreamMeta = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class Server {
|
||||
audioCapture = new AudioPlaybackCapture(options.getAudioDup());
|
||||
}
|
||||
|
||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendStreamMeta(), options.getSendFrameMeta());
|
||||
AsyncProcessor audioRecorder;
|
||||
if (audioCodec == AudioCodec.RAW) {
|
||||
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
||||
@@ -136,7 +136,7 @@ public final class Server {
|
||||
}
|
||||
|
||||
if (video) {
|
||||
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
|
||||
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendStreamMeta(),
|
||||
options.getSendFrameMeta());
|
||||
SurfaceCapture surfaceCapture;
|
||||
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
||||
|
||||
@@ -14,12 +14,13 @@ import java.util.Arrays;
|
||||
|
||||
public final class Streamer {
|
||||
|
||||
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_SESSION = 1L << 63;
|
||||
private static final long PACKET_FLAG_CONFIG = 1L << 62;
|
||||
private static final long PACKET_FLAG_KEY_FRAME = 1L << 61;
|
||||
|
||||
private final FileDescriptor fd;
|
||||
private final Codec codec;
|
||||
private final boolean sendCodecMeta;
|
||||
private final boolean sendStreamMeta;
|
||||
private final boolean sendFrameMeta;
|
||||
|
||||
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
||||
@@ -27,7 +28,7 @@ public final class Streamer {
|
||||
public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) {
|
||||
this.fd = fd;
|
||||
this.codec = codec;
|
||||
this.sendCodecMeta = sendCodecMeta;
|
||||
this.sendStreamMeta = sendCodecMeta;
|
||||
this.sendFrameMeta = sendFrameMeta;
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ public final class Streamer {
|
||||
}
|
||||
|
||||
public void writeAudioHeader() throws IOException {
|
||||
if (sendCodecMeta) {
|
||||
if (sendStreamMeta) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||
buffer.putInt(codec.getId());
|
||||
buffer.flip();
|
||||
@@ -44,12 +45,10 @@ public final class Streamer {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeVideoHeader(Size videoSize) throws IOException {
|
||||
if (sendCodecMeta) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(12);
|
||||
public void writeVideoHeader() throws IOException {
|
||||
if (sendStreamMeta) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||
buffer.putInt(codec.getId());
|
||||
buffer.putInt(videoSize.getWidth());
|
||||
buffer.putInt(videoSize.getHeight());
|
||||
buffer.flip();
|
||||
IO.writeFully(fd, buffer);
|
||||
}
|
||||
@@ -89,6 +88,18 @@ public final class Streamer {
|
||||
writePacket(codecBuffer, pts, config, keyFrame);
|
||||
}
|
||||
|
||||
public void writeSessionMeta(int width, int height) throws IOException {
|
||||
if (sendStreamMeta) {
|
||||
headerBuffer.clear();
|
||||
|
||||
headerBuffer.putInt((int) (PACKET_FLAG_SESSION >> 32)); // Set the first bit to 1
|
||||
headerBuffer.putInt(width);
|
||||
headerBuffer.putInt(height);
|
||||
headerBuffer.flip();
|
||||
IO.writeFully(fd, headerBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFrameMeta(FileDescriptor fd, int packetSize, long pts, boolean config, boolean keyFrame) throws IOException {
|
||||
headerBuffer.clear();
|
||||
|
||||
|
||||
@@ -71,16 +71,13 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
|
||||
try {
|
||||
boolean alive;
|
||||
boolean headerWritten = false;
|
||||
|
||||
streamer.writeVideoHeader();
|
||||
|
||||
do {
|
||||
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||
capture.prepare();
|
||||
Size size = capture.getSize();
|
||||
if (!headerWritten) {
|
||||
streamer.writeVideoHeader(size);
|
||||
headerWritten = true;
|
||||
}
|
||||
|
||||
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth());
|
||||
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
|
||||
@@ -107,6 +104,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
boolean resetRequested = reset.consumeReset();
|
||||
if (!resetRequested) {
|
||||
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
|
||||
streamer.writeSessionMeta(size.getWidth(), size.getHeight());
|
||||
encode(mediaCodec, streamer);
|
||||
}
|
||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||
|
||||
Reference in New Issue
Block a user