Pass video size as codec metadata
On initial connection, scrcpy sent some device metadata: - the device name (to be used as window title) - the initial video size (before any frame or even SPS/PPS) But it is better to provide the initial video size as part as the video stream, so that it can be demuxed and exposed via AVCodecContext to sinks. This avoids to pass an explicit "initial frame size" for the screen, the recorder and the v4l2 sink.
This commit is contained in:
parent
3a72f3fb4d
commit
238ab872ba
17 changed files with 104 additions and 69 deletions
|
@ -57,6 +57,20 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
||||||
return true;
|
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
|
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
|
||||||
|
@ -169,7 +183,15 @@ run_demuxer(void *data) {
|
||||||
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||||
|
|
||||||
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||||
// Hardcoded video properties
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
|
||||||
|
if (!ok) {
|
||||||
|
goto finally_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec_ctx->width = width;
|
||||||
|
codec_ctx->height = height;
|
||||||
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
} else {
|
} else {
|
||||||
// Hardcoded audio properties
|
// Hardcoded audio properties
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
||||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
||||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||||
|
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
||||||
|
|
|
@ -479,9 +479,6 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream->codecpar->width = recorder->declared_frame_size.width;
|
|
||||||
stream->codecpar->height = recorder->declared_frame_size.height;
|
|
||||||
|
|
||||||
recorder->video_stream_index = stream->index;
|
recorder->video_stream_index = stream->index;
|
||||||
|
|
||||||
recorder->video_init = true;
|
recorder->video_init = true;
|
||||||
|
@ -643,7 +640,6 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
|
||||||
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, bool audio,
|
||||||
struct sc_size declared_frame_size,
|
|
||||||
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
|
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
|
||||||
recorder->filename = strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
|
@ -679,7 +675,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||||
recorder->audio_stream_index = -1;
|
recorder->audio_stream_index = -1;
|
||||||
|
|
||||||
recorder->format = format;
|
recorder->format = format;
|
||||||
recorder->declared_frame_size = declared_frame_size;
|
|
||||||
|
|
||||||
assert(cbs && cbs->on_ended);
|
assert(cbs && cbs->on_ended);
|
||||||
recorder->cbs = cbs;
|
recorder->cbs = cbs;
|
||||||
|
|
|
@ -31,7 +31,6 @@ struct sc_recorder {
|
||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct sc_size declared_frame_size;
|
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
|
@ -61,7 +60,6 @@ struct sc_recorder_callbacks {
|
||||||
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, bool audio,
|
||||||
struct sc_size declared_frame_size,
|
|
||||||
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
|
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
|
@ -473,7 +473,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
};
|
};
|
||||||
if (!sc_recorder_init(&s->recorder, options->record_filename,
|
if (!sc_recorder_init(&s->recorder, options->record_filename,
|
||||||
options->record_format, options->audio,
|
options->record_format, options->audio,
|
||||||
info->frame_size, &recorder_cbs, NULL)) {
|
&recorder_cbs, NULL)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
recorder_initialized = true;
|
recorder_initialized = true;
|
||||||
|
@ -660,7 +660,6 @@ aoa_hid_end:
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
.shortcut_mods = &options->shortcut_mods,
|
.shortcut_mods = &options->shortcut_mods,
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.frame_size = info->frame_size,
|
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
.window_y = options->window_y,
|
.window_y = options->window_y,
|
||||||
|
@ -697,8 +696,7 @@ aoa_hid_end:
|
||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (options->v4l2_device) {
|
if (options->v4l2_device) {
|
||||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device)) {
|
||||||
info->frame_size)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline SDL_Texture *
|
static bool
|
||||||
create_texture(struct sc_screen *screen) {
|
create_texture(struct sc_screen *screen) {
|
||||||
SDL_Renderer *renderer = screen->renderer;
|
SDL_Renderer *renderer = screen->renderer;
|
||||||
struct sc_size size = screen->frame_size;
|
struct sc_size size = screen->frame_size;
|
||||||
|
@ -247,7 +247,8 @@ create_texture(struct sc_screen *screen) {
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
size.width, size.height);
|
size.width, size.height);
|
||||||
if (!texture) {
|
if (!texture) {
|
||||||
return NULL;
|
LOGE("Could not create texture: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screen->mipmaps) {
|
if (screen->mipmaps) {
|
||||||
|
@ -263,7 +264,8 @@ create_texture(struct sc_screen *screen) {
|
||||||
SDL_GL_UnbindTexture(texture);
|
SDL_GL_UnbindTexture(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
return texture;
|
screen->texture = texture;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the texture to the renderer
|
// render the texture to the renderer
|
||||||
|
@ -335,7 +337,25 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||||
(void) ctx;
|
(void) ctx;
|
||||||
|
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct sc_screen *screen = DOWNCAST(sink);
|
||||||
(void) screen;
|
|
||||||
|
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
|
||||||
|
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
|
||||||
|
// screen->frame_size is never used before the event is pushed, and the
|
||||||
|
// event acts as a memory barrier so it is safe without mutex
|
||||||
|
screen->frame_size.width = ctx->width;
|
||||||
|
screen->frame_size.height = ctx->height;
|
||||||
|
|
||||||
|
static SDL_Event event = {
|
||||||
|
.type = SC_EVENT_SCREEN_INIT_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Post the event on the UI thread (the texture must be created from there)
|
||||||
|
int ret = SDL_PushEvent(&event);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGW("Could not post init size event: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
screen->open = true;
|
screen->open = true;
|
||||||
#endif
|
#endif
|
||||||
|
@ -410,14 +430,10 @@ sc_screen_init(struct sc_screen *screen,
|
||||||
goto error_destroy_frame_buffer;
|
goto error_destroy_frame_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->frame_size = params->frame_size;
|
|
||||||
screen->rotation = params->rotation;
|
screen->rotation = params->rotation;
|
||||||
if (screen->rotation) {
|
if (screen->rotation) {
|
||||||
LOGI("Initial display rotation set to %u", screen->rotation);
|
LOGI("Initial display rotation set to %u", screen->rotation);
|
||||||
}
|
}
|
||||||
struct sc_size content_size =
|
|
||||||
get_rotated_size(screen->frame_size, screen->rotation);
|
|
||||||
screen->content_size = content_size;
|
|
||||||
|
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||||
| SDL_WINDOW_RESIZABLE
|
| SDL_WINDOW_RESIZABLE
|
||||||
|
@ -485,18 +501,10 @@ sc_screen_init(struct sc_screen *screen,
|
||||||
LOGW("Could not load icon");
|
LOGW("Could not load icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width,
|
|
||||||
params->frame_size.height);
|
|
||||||
screen->texture = create_texture(screen);
|
|
||||||
if (!screen->texture) {
|
|
||||||
LOGE("Could not create texture: %s", SDL_GetError());
|
|
||||||
goto error_destroy_renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
screen->frame = av_frame_alloc();
|
screen->frame = av_frame_alloc();
|
||||||
if (!screen->frame) {
|
if (!screen->frame) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto error_destroy_texture;
|
goto error_destroy_renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_input_manager_params im_params = {
|
struct sc_input_manager_params im_params = {
|
||||||
|
@ -531,8 +539,6 @@ sc_screen_init(struct sc_screen *screen,
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_destroy_texture:
|
|
||||||
SDL_DestroyTexture(screen->texture);
|
|
||||||
error_destroy_renderer:
|
error_destroy_renderer:
|
||||||
SDL_DestroyRenderer(screen->renderer);
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
error_destroy_window:
|
error_destroy_window:
|
||||||
|
@ -591,7 +597,9 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||||
assert(!screen->open);
|
assert(!screen->open);
|
||||||
#endif
|
#endif
|
||||||
av_frame_free(&screen->frame);
|
av_frame_free(&screen->frame);
|
||||||
|
if (screen->texture) {
|
||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyTexture(screen->texture);
|
||||||
|
}
|
||||||
SDL_DestroyRenderer(screen->renderer);
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
SDL_DestroyWindow(screen->window);
|
SDL_DestroyWindow(screen->window);
|
||||||
sc_fps_counter_destroy(&screen->fps_counter);
|
sc_fps_counter_destroy(&screen->fps_counter);
|
||||||
|
@ -655,6 +663,23 @@ sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
|
||||||
sc_screen_render(screen, true);
|
sc_screen_render(screen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_screen_init_size(struct sc_screen *screen) {
|
||||||
|
// Before first frame
|
||||||
|
assert(!screen->has_frame);
|
||||||
|
assert(!screen->texture);
|
||||||
|
|
||||||
|
// The requested size is passed via screen->frame_size
|
||||||
|
|
||||||
|
struct sc_size content_size =
|
||||||
|
get_rotated_size(screen->frame_size, screen->rotation);
|
||||||
|
screen->content_size = content_size;
|
||||||
|
|
||||||
|
LOGI("Initial texture: %" PRIu16 "x%" PRIu16,
|
||||||
|
screen->frame_size.width, screen->frame_size.height);
|
||||||
|
return create_texture(screen);
|
||||||
|
}
|
||||||
|
|
||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
static bool
|
static bool
|
||||||
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||||
|
@ -673,11 +698,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||||
|
|
||||||
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
||||||
screen->frame_size.width, screen->frame_size.height);
|
screen->frame_size.width, screen->frame_size.height);
|
||||||
screen->texture = create_texture(screen);
|
return create_texture(screen);
|
||||||
if (!screen->texture) {
|
|
||||||
LOGE("Could not create texture: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -795,6 +816,14 @@ 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_SCREEN_INIT_SIZE:
|
||||||
|
// The initial size is passed via screen->frame_size
|
||||||
|
bool ok = sc_screen_init_size(screen);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not initialize screen size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
case SC_EVENT_NEW_FRAME: {
|
case SC_EVENT_NEW_FRAME: {
|
||||||
bool ok = sc_screen_update_frame(screen);
|
bool ok = sc_screen_update_frame(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
|
|
@ -78,7 +78,6 @@ struct sc_screen_params {
|
||||||
const struct sc_shortcut_mods *shortcut_mods;
|
const struct sc_shortcut_mods *shortcut_mods;
|
||||||
|
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
struct sc_size frame_size;
|
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
|
||||||
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
|
|
@ -441,9 +441,9 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
||||||
static bool
|
static bool
|
||||||
device_read_info(struct sc_intr *intr, sc_socket device_socket,
|
device_read_info(struct sc_intr *intr, sc_socket device_socket,
|
||||||
struct sc_server_info *info) {
|
struct sc_server_info *info) {
|
||||||
unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH + 4];
|
unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH];
|
||||||
ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf));
|
ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf));
|
||||||
if (r < SC_DEVICE_NAME_FIELD_LENGTH + 4) {
|
if (r < SC_DEVICE_NAME_FIELD_LENGTH) {
|
||||||
LOGE("Could not retrieve device information");
|
LOGE("Could not retrieve device information");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -451,9 +451,6 @@ device_read_info(struct sc_intr *intr, sc_socket device_socket,
|
||||||
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||||
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
||||||
|
|
||||||
unsigned char *fields = &buf[SC_DEVICE_NAME_FIELD_LENGTH];
|
|
||||||
info->frame_size.width = sc_read16be(fields);
|
|
||||||
info->frame_size.height = sc_read16be(&fields[2]);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
#define SC_DEVICE_NAME_FIELD_LENGTH 64
|
#define SC_DEVICE_NAME_FIELD_LENGTH 64
|
||||||
struct sc_server_info {
|
struct sc_server_info {
|
||||||
char device_name[SC_DEVICE_NAME_FIELD_LENGTH];
|
char device_name[SC_DEVICE_NAME_FIELD_LENGTH];
|
||||||
struct sc_size frame_size;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server_params {
|
struct sc_server_params {
|
||||||
|
|
|
@ -210,9 +210,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||||
goto error_avformat_free_context;
|
goto error_avformat_free_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
ostream->codecpar->width = vs->frame_size.width;
|
|
||||||
ostream->codecpar->height = vs->frame_size.height;
|
|
||||||
|
|
||||||
int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE);
|
int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGE("Failed to open output device: %s", vs->device_name);
|
LOGE("Failed to open output device: %s", vs->device_name);
|
||||||
|
@ -226,8 +223,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||||
goto error_avio_close;
|
goto error_avio_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
vs->encoder_ctx->width = vs->frame_size.width;
|
vs->encoder_ctx->width = ctx->width;
|
||||||
vs->encoder_ctx->height = vs->frame_size.height;
|
vs->encoder_ctx->height = ctx->height;
|
||||||
vs->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
vs->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
vs->encoder_ctx->time_base.num = 1;
|
vs->encoder_ctx->time_base.num = 1;
|
||||||
vs->encoder_ctx->time_base.den = 1;
|
vs->encoder_ctx->time_base.den = 1;
|
||||||
|
@ -343,16 +340,13 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name) {
|
||||||
struct sc_size frame_size) {
|
|
||||||
vs->device_name = strdup(device_name);
|
vs->device_name = strdup(device_name);
|
||||||
if (!vs->device_name) {
|
if (!vs->device_name) {
|
||||||
LOGE("Could not strdup v4l2 device name");
|
LOGE("Could not strdup v4l2 device name");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
vs->frame_size = frame_size;
|
|
||||||
|
|
||||||
static const struct sc_frame_sink_ops ops = {
|
static const struct sc_frame_sink_ops ops = {
|
||||||
.open = sc_v4l2_frame_sink_open,
|
.open = sc_v4l2_frame_sink_open,
|
||||||
.close = sc_v4l2_frame_sink_close,
|
.close = sc_v4l2_frame_sink_close,
|
||||||
|
|
|
@ -19,7 +19,6 @@ struct sc_v4l2_sink {
|
||||||
AVCodecContext *encoder_ctx;
|
AVCodecContext *encoder_ctx;
|
||||||
|
|
||||||
char *device_name;
|
char *device_name;
|
||||||
struct sc_size frame_size;
|
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
|
@ -33,8 +32,7 @@ struct sc_v4l2_sink {
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name);
|
||||||
struct sc_size frame_size);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||||
|
|
|
@ -101,7 +101,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
|
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
|
||||||
streamer.writeHeader();
|
streamer.writeAudioHeader();
|
||||||
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
OutputTask task = outputTasks.take();
|
OutputTask task = outputTasks.take();
|
||||||
|
|
|
@ -26,7 +26,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
|
||||||
try {
|
try {
|
||||||
capture.start();
|
capture.start();
|
||||||
|
|
||||||
streamer.writeHeader();
|
streamer.writeAudioHeader();
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
buffer.position(0);
|
buffer.position(0);
|
||||||
int r = capture.read(buffer, READ_SIZE, bufferInfo);
|
int r = capture.read(buffer, READ_SIZE, bufferInfo);
|
||||||
|
|
|
@ -122,18 +122,14 @@ public final class DesktopConnection implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendDeviceMeta(String deviceName, int width, int height) throws IOException {
|
public void sendDeviceMeta(String deviceName) throws IOException {
|
||||||
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
|
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH];
|
||||||
|
|
||||||
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
||||||
int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1);
|
int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1);
|
||||||
System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
|
System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
|
||||||
// byte[] are always 0-initialized in java, no need to set '\0' explicitly
|
// byte[] are always 0-initialized in java, no need to set '\0' explicitly
|
||||||
|
|
||||||
buffer[DEVICE_NAME_FIELD_LENGTH] = (byte) (width >> 8);
|
|
||||||
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
|
|
||||||
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
|
|
||||||
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
|
|
||||||
IO.writeFully(videoFd, buffer, 0, buffer.length);
|
IO.writeFully(videoFd, buffer, 0, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
IBinder display = createDisplay();
|
IBinder display = createDisplay();
|
||||||
device.setRotationListener(this);
|
device.setRotationListener(this);
|
||||||
|
|
||||||
streamer.writeHeader();
|
streamer.writeVideoHeader(device.getScreenInfo().getVideoSize());
|
||||||
|
|
||||||
boolean alive;
|
boolean alive;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -96,8 +96,7 @@ public final class Server {
|
||||||
|
|
||||||
try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) {
|
try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) {
|
||||||
if (options.getSendDeviceMeta()) {
|
if (options.getSendDeviceMeta()) {
|
||||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
connection.sendDeviceMeta(Device.getDeviceName());
|
||||||
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (control) {
|
if (control) {
|
||||||
|
|
|
@ -30,8 +30,7 @@ public final class Streamer {
|
||||||
public Codec getCodec() {
|
public Codec getCodec() {
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
public void writeAudioHeader() throws IOException {
|
||||||
public void writeHeader() throws IOException {
|
|
||||||
if (sendCodecMeta) {
|
if (sendCodecMeta) {
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(4);
|
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||||
buffer.putInt(codec.getId());
|
buffer.putInt(codec.getId());
|
||||||
|
@ -40,6 +39,17 @@ public final class Streamer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void writeVideoHeader(Size videoSize) throws IOException {
|
||||||
|
if (sendCodecMeta) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(12);
|
||||||
|
buffer.putInt(codec.getId());
|
||||||
|
buffer.putInt(videoSize.getWidth());
|
||||||
|
buffer.putInt(videoSize.getHeight());
|
||||||
|
buffer.flip();
|
||||||
|
IO.writeFully(fd, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void writeDisableStream(boolean error) throws IOException {
|
public void writeDisableStream(boolean error) throws IOException {
|
||||||
// Writing a specific code as codec-id means that the device disables the stream
|
// Writing a specific code as codec-id means that the device disables the stream
|
||||||
// code 0: it explicitly disables the stream (because it could not capture audio), scrcpy should continue mirroring video only
|
// code 0: it explicitly disables the stream (because it could not capture audio), scrcpy should continue mirroring video only
|
||||||
|
|
Loading…
Reference in a new issue