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:
Romain Vimont 2023-03-11 09:21:49 +01:00
parent 3a72f3fb4d
commit 238ab872ba
17 changed files with 104 additions and 69 deletions

View file

@ -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

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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;
} }

View file

@ -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);
SDL_DestroyTexture(screen->texture); if (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) {

View file

@ -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

View file

@ -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;
} }

View file

@ -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 {

View file

@ -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,

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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);
} }

View file

@ -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 {

View file

@ -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) {

View file

@ -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