Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
60b7281cbc Fix compatibility with old FFmpeg
V4L2 sink used a "url" field format AVFormatContext which has been
introduced in lavf 58.7.100.

Fixes #2382 <https://github.com/Genymobile/scrcpy/issues/2382>

Refs <ea3672b7d6>
Refs <fa8308d3d4>
2021-06-13 18:31:48 +02:00
12 changed files with 72 additions and 138 deletions

View File

@@ -303,8 +303,7 @@ To start scrcpy using a v4l2 sink:
```bash ```bash
scrcpy --v4l2-sink=/dev/videoN scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window
scrcpy --v4l2-sink=/dev/videoN -N # short version
``` ```
(replace `N` by the device ID, check with `ls /dev/video*`) (replace `N` by the device ID, check with `ls /dev/video*`)
@@ -710,15 +709,15 @@ There is no visual feedback, a log is printed to the console.
#### Push file to device #### Push file to device
To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK) To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
file to the _scrcpy_ window. _scrcpy_ window.
There is no visual feedback, a log is printed to the console. There is no visual feedback, a log is printed to the console.
The target directory can be changed on start: The target directory can be changed on start:
```bash ```bash
scrcpy --push-target=/sdcard/Movies/ scrcpy --push-target=/sdcard/Download/
``` ```

View File

@@ -133,7 +133,7 @@ but breaks the expected behavior of alpha keys in games (typically WASD).
.BI "\-\-push\-target " path .BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push". Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
Default is "/sdcard/Download/". Default is "/sdcard/".
.TP .TP
.BI "\-r, \-\-record " file .BI "\-r, \-\-record " file

View File

@@ -129,7 +129,7 @@ scrcpy_print_usage(const char *arg0) {
" --push-target path\n" " --push-target path\n"
" Set the target directory for pushing files to the device by\n" " Set the target directory for pushing files to the device by\n"
" drag & drop. It is passed as-is to \"adb push\".\n" " drag & drop. It is passed as-is to \"adb push\".\n"
" Default is \"/sdcard/Download/\".\n" " Default is \"/sdcard/\".\n"
"\n" "\n"
" -r, --record file.mp4\n" " -r, --record file.mp4\n"
" Record screen to file.\n" " Record screen to file.\n"

View File

@@ -6,7 +6,7 @@
#include "adb.h" #include "adb.h"
#include "util/log.h" #include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/" #define DEFAULT_PUSH_TARGET "/sdcard/"
static void static void
file_handler_request_destroy(struct file_handler_request *req) { file_handler_request_destroy(struct file_handler_request *req) {

View File

@@ -35,14 +35,11 @@ record_packet_new(const AVPacket *packet) {
return NULL; return NULL;
} }
rec->packet = av_packet_alloc(); // av_packet_ref() does not initialize all fields in old FFmpeg versions
if (!rec->packet) { // See <https://github.com/Genymobile/scrcpy/issues/707>
free(rec); av_init_packet(&rec->packet);
return NULL;
}
if (av_packet_ref(rec->packet, packet)) { if (av_packet_ref(&rec->packet, packet)) {
av_packet_free(&rec->packet);
free(rec); free(rec);
return NULL; return NULL;
} }
@@ -51,8 +48,7 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(rec->packet); av_packet_unref(&rec->packet);
av_packet_free(&rec->packet);
free(rec); free(rec);
} }
@@ -148,8 +144,8 @@ run_recorder(void *data) {
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
// assign an arbitrary duration to the last packet // assign an arbitrary duration to the last packet
last->packet->duration = 100000; last->packet.duration = 100000;
bool ok = recorder_write(recorder, last->packet); bool ok = recorder_write(recorder, &last->packet);
if (!ok) { if (!ok) {
// failing to write the last frame is not very serious, no // failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file // future frame may depend on it, so the resulting file
@@ -176,14 +172,13 @@ run_recorder(void *data) {
} }
// config packets have no PTS, we must ignore them // config packets have no PTS, we must ignore them
if (rec->packet->pts != AV_NOPTS_VALUE if (rec->packet.pts != AV_NOPTS_VALUE
&& previous->packet->pts != AV_NOPTS_VALUE) { && previous->packet.pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet // we now know the duration of the previous packet
previous->packet->duration = previous->packet.duration = rec->packet.pts - previous->packet.pts;
rec->packet->pts - previous->packet->pts;
} }
bool ok = recorder_write(recorder, previous->packet); bool ok = recorder_write(recorder, &previous->packet);
record_packet_delete(previous); record_packet_delete(previous);
if (!ok) { if (!ok) {
LOGE("Could not record packet"); LOGE("Could not record packet");

View File

@@ -13,7 +13,7 @@
#include "util/thread.h" #include "util/thread.h"
struct record_packet { struct record_packet {
AVPacket *packet; AVPacket packet;
struct record_packet *next; struct record_packet *next;
}; };

View File

@@ -41,18 +41,6 @@ get_window_size(const struct screen *screen) {
return size; return size;
} }
static struct point
get_window_position(const struct screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
struct point point;
point.x = x;
point.y = y;
return point;
}
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void
set_window_size(struct screen *screen, struct size new_size) { set_window_size(struct screen *screen, struct size new_size) {
@@ -134,6 +122,13 @@ get_optimal_size(struct size current_size, struct size content_size) {
return window_size; return window_size;
} }
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size content_size) {
struct size window_size = get_window_size(screen);
return get_optimal_size(window_size, content_size);
}
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user // req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size static inline struct size
@@ -667,20 +662,9 @@ screen_resize_to_fit(struct screen *screen) {
return; return;
} }
struct point point = get_window_position(screen);
struct size window_size = get_window_size(screen);
struct size optimal_size = struct size optimal_size =
get_optimal_size(window_size, screen->content_size); get_optimal_window_size(screen, screen->content_size);
// Center the window related to the device screen
assert(optimal_size.width <= window_size.width);
assert(optimal_size.height <= window_size.height);
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_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowPosition(screen->window, new_x, new_y);
LOGD("Resized to optimal size: %ux%u", optimal_size.width, LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height); optimal_size.height);
} }
@@ -742,7 +726,6 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
} }
screen->maximized = false; screen->maximized = false;
apply_pending_resize(screen); apply_pending_resize(screen);
screen_render(screen, true);
break; break;
} }
return true; return true;

View File

@@ -104,38 +104,33 @@ static bool
stream_push_packet(struct stream *stream, AVPacket *packet) { stream_push_packet(struct stream *stream, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE; bool is_config = packet->pts == AV_NOPTS_VALUE;
// A config packet must not be decoded immediately (it contains no // A config packet must not be decoded immetiately (it contains no
// frame); instead, it must be concatenated with the future data packet. // frame); instead, it must be concatenated with the future data packet.
if (stream->pending || is_config) { if (stream->has_pending || is_config) {
size_t offset; size_t offset;
if (stream->pending) { if (stream->has_pending) {
offset = stream->pending->size; offset = stream->pending.size;
if (av_grow_packet(stream->pending, packet->size)) { if (av_grow_packet(&stream->pending, packet->size)) {
LOGE("Could not grow packet"); LOGE("Could not grow packet");
return false; return false;
} }
} else { } else {
offset = 0; offset = 0;
stream->pending = av_packet_alloc(); if (av_new_packet(&stream->pending, packet->size)) {
if (!stream->pending) {
LOGE("Could not allocate packet");
return false;
}
if (av_new_packet(stream->pending, packet->size)) {
LOGE("Could not create packet"); LOGE("Could not create packet");
av_packet_free(&stream->pending);
return false; return false;
} }
stream->has_pending = true;
} }
memcpy(stream->pending->data + offset, packet->data, packet->size); memcpy(stream->pending.data + offset, packet->data, packet->size);
if (!is_config) { if (!is_config) {
// prepare the concat packet to send to the decoder // prepare the concat packet to send to the decoder
stream->pending->pts = packet->pts; stream->pending.pts = packet->pts;
stream->pending->dts = packet->dts; stream->pending.dts = packet->dts;
stream->pending->flags = packet->flags; stream->pending.flags = packet->flags;
packet = stream->pending; packet = &stream->pending;
} }
} }
@@ -149,10 +144,10 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
// data packet // data packet
bool ok = stream_parse(stream, packet); bool ok = stream_parse(stream, packet);
if (stream->pending) { if (stream->has_pending) {
// the pending packet must be discarded (consumed or error) // the pending packet must be discarded (consumed or error)
av_packet_unref(stream->pending); stream->has_pending = false;
av_packet_free(&stream->pending); av_packet_unref(&stream->pending);
} }
if (!ok) { if (!ok) {
@@ -220,21 +215,16 @@ run_stream(void *data) {
// It's more complicated, but this allows to reduce the latency by 1 frame! // It's more complicated, but this allows to reduce the latency by 1 frame!
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
goto finally_close_parser;
}
for (;;) { for (;;) {
bool ok = stream_recv_packet(stream, packet); AVPacket packet;
bool ok = stream_recv_packet(stream, &packet);
if (!ok) { if (!ok) {
// end of stream // end of stream
break; break;
} }
ok = stream_push_packet(stream, packet); ok = stream_push_packet(stream, &packet);
av_packet_unref(packet); av_packet_unref(&packet);
if (!ok) { if (!ok) {
// cannot process packet (error already logged) // cannot process packet (error already logged)
break; break;
@@ -243,13 +233,10 @@ run_stream(void *data) {
LOGD("End of frames"); LOGD("End of frames");
if (stream->pending) { if (stream->has_pending) {
av_packet_unref(stream->pending); av_packet_unref(&stream->pending);
av_packet_free(&stream->pending);
} }
av_packet_free(&packet);
finally_close_parser:
av_parser_close(stream->parser); av_parser_close(stream->parser);
finally_close_sinks: finally_close_sinks:
stream_close_sinks(stream); stream_close_sinks(stream);
@@ -265,7 +252,7 @@ void
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, socket_t socket,
const struct stream_callbacks *cbs, void *cbs_userdata) { const struct stream_callbacks *cbs, void *cbs_userdata) {
stream->socket = socket; stream->socket = socket;
stream->pending = NULL; stream->has_pending = false;
stream->sink_count = 0; stream->sink_count = 0;
assert(cbs && cbs->on_eos); assert(cbs && cbs->on_eos);

View File

@@ -24,7 +24,8 @@ struct stream {
AVCodecParserContext *parser; AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config // successive packets may need to be concatenated, until a non-config
// packet is available // packet is available
AVPacket *pending; bool has_pending;
AVPacket pending;
const struct stream_callbacks *cbs; const struct stream_callbacks *cbs;
void *cbs_userdata; void *cbs_userdata;

View File

@@ -86,7 +86,7 @@ encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
return false; return false;
} }
AVPacket *packet = vs->packet; AVPacket *packet = &vs->packet;
ret = avcodec_receive_packet(vs->encoder_ctx, packet); ret = avcodec_receive_packet(vs->encoder_ctx, packet);
if (ret == 0) { if (ret == 0) {
// A packet was received // A packet was received
@@ -181,16 +181,16 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd> // <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
vs->format_ctx->oformat = (AVOutputFormat *) format; vs->format_ctx->oformat = (AVOutputFormat *) format;
#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL #ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
vs->format_ctx->url = strdup(vs->device_name); # define CTX_URL_FIELD url
#else
# define CTX_URL_FIELD filename
#endif
vs->format_ctx->CTX_URL_FIELD = strdup(vs->device_name);
if (!vs->format_ctx->url) { if (!vs->format_ctx->url) {
LOGE("Could not strdup v4l2 device name"); LOGE("Could not strdup v4l2 device name");
goto error_avformat_free_context; goto error_avformat_free_context;
return false; return false;
} }
#else
strncpy(vs->format_ctx->filename, vs->device_name,
sizeof(vs->format_ctx->filename));
#endif
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder); AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
if (!ostream) { if (!ostream) {
@@ -235,17 +235,11 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_avcodec_close; goto error_avcodec_close;
} }
vs->packet = av_packet_alloc();
if (!vs->packet) {
LOGE("Could not allocate packet");
goto error_av_frame_free;
}
LOGD("Starting v4l2 thread"); LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs); ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
if (!ok) { if (!ok) {
LOGC("Could not start v4l2 thread"); LOGC("Could not start v4l2 thread");
goto error_av_packet_free; goto error_av_frame_free;
} }
vs->header_written = false; vs->header_written = false;
@@ -255,8 +249,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
return true; return true;
error_av_packet_free:
av_packet_free(&vs->packet);
error_av_frame_free: error_av_frame_free:
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
error_avcodec_close: error_avcodec_close:
@@ -286,7 +278,6 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
sc_thread_join(&vs->thread, NULL); sc_thread_join(&vs->thread, NULL);
av_packet_free(&vs->packet);
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
avcodec_close(vs->encoder_ctx); avcodec_close(vs->encoder_ctx);
avcodec_free_context(&vs->encoder_ctx); avcodec_free_context(&vs->encoder_ctx);

View File

@@ -26,7 +26,7 @@ struct sc_v4l2_sink {
bool header_written; bool header_written;
AVFrame *frame; AVFrame *frame;
AVPacket *packet; AVPacket packet;
}; };
bool bool

View File

@@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -38,8 +37,6 @@ public class ContentProvider implements Closeable {
private Method callMethod; private Method callMethod;
private int callMethodVersion; private int callMethodVersion;
private Object attributionSource;
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) { ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
this.manager = manager; this.manager = manager;
this.provider = provider; this.provider = provider;
@@ -49,61 +46,42 @@ public class ContentProvider implements Closeable {
private Method getCallMethod() throws NoSuchMethodException { private Method getCallMethod() throws NoSuchMethodException {
if (callMethod == null) { if (callMethod == null) {
try { try {
@SuppressLint("PrivateApi") callMethod = provider.getClass()
Class<?> attributionSourceClass = Class.forName("android.content.AttributionSource"); .getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
callMethod = provider.getClass().getMethod("call", attributionSourceClass, String.class, String.class, String.class, Bundle.class); callMethodVersion = 0;
} catch (NoSuchMethodException | ClassNotFoundException e0) { } catch (NoSuchMethodException e) {
// old versions // old versions
try { try {
callMethod = provider.getClass() callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 1; callMethodVersion = 1;
} catch (NoSuchMethodException e1) { } catch (NoSuchMethodException e2) {
try { callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class); callMethodVersion = 2;
callMethodVersion = 2;
} catch (NoSuchMethodException e2) {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethodVersion = 3;
}
} }
} }
} }
return callMethod; return callMethod;
} }
private Object getAttributionSource()
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (attributionSource == null) {
Class<?> cl = Class.forName("android.content.AttributionSource$Builder");
Object builder = cl.getConstructor(int.class).newInstance(ServiceManager.USER_ID);
cl.getDeclaredMethod("setPackageName", String.class).invoke(builder, ServiceManager.PACKAGE_NAME);
attributionSource = cl.getDeclaredMethod("build").invoke(builder);
}
return attributionSource;
}
private Bundle call(String callMethod, String arg, Bundle extras) { private Bundle call(String callMethod, String arg, Bundle extras) {
try { try {
Method method = getCallMethod(); Method method = getCallMethod();
Object[] args; Object[] args;
switch (callMethodVersion) { switch (callMethodVersion) {
case 0: case 0:
args = new Object[]{getAttributionSource(), "settings", callMethod, arg, extras};
break;
case 1:
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras}; args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
break; break;
case 2: case 1:
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras}; args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
break; break;
default: default:
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras}; args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
break; break;
} return (Bundle) method.invoke(provider, args); }
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) { return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
return null; return null;
} }