Compare commits

..

2 Commits

Author SHA1 Message Date
Romain Vimont
4f7869d130 Fix audio PTS when not monotonically increasing
Some decoders fail to guarantee that PTS must be monotonically
increasing. Fix the PTS when they're not.

Fixes #4054 <https://github.com/Genymobile/scrcpy/issues/4054>
2023-06-02 21:39:29 +02:00
Romain Vimont
0f28d39127 Do not add 1µS to the PTS in corner cases
The PTS must be monotonically increasing, but not strictly.
2023-06-02 21:38:30 +02:00
18 changed files with 70 additions and 111 deletions

View File

@@ -19,9 +19,8 @@ _scrcpy() {
-f --fullscreen
--force-adb-forward
--forward-all-clicks
-h --help
--kill-adb-on-close
-K --hid-keyboard
-h --help
--legacy-paste
--list-displays
--list-encoders

View File

@@ -26,9 +26,8 @@ arguments=(
{-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-h,--help}'[Print the help]'
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
{-h,--help}'[Print the help]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]'

View File

@@ -129,10 +129,6 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO
.B \-h, \-\-help
Print this help.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.TP
.B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2.

View File

@@ -77,7 +77,6 @@ enum {
OPT_NO_AUDIO_PLAYBACK,
OPT_NO_VIDEO_PLAYBACK,
OPT_AUDIO_SOURCE,
OPT_KILL_ADB_ON_CLOSE,
};
struct sc_option {
@@ -276,16 +275,6 @@ static const struct sc_option options[] = {
"middle-click triggers HOME. This option disables these "
"shortcuts and forwards the clicks to the device instead.",
},
{
.shortopt = 'h',
.longopt = "help",
.text = "Print this help.",
},
{
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
.longopt = "kill-adb-on-close",
.text = "Kill adb when scrcpy terminates.",
},
{
.shortopt = 'K',
.longopt = "hid-keyboard",
@@ -303,6 +292,11 @@ static const struct sc_option options[] = {
"is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.",
},
{
.shortopt = 'h',
.longopt = "help",
.text = "Print this help.",
},
{
.longopt_id = OPT_LEGACY_PASTE,
.longopt = "legacy-paste",
@@ -1950,9 +1944,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case OPT_KILL_ADB_ON_CLOSE:
opts->kill_adb_on_close = true;
break;
default:
// getopt prints the error message on stderr
return false;

View File

@@ -80,5 +80,4 @@ const struct scrcpy_options scrcpy_options_default = {
.require_audio = false,
.list_encoders = false,
.list_displays = false,
.kill_adb_on_close = false,
};

View File

@@ -180,7 +180,6 @@ struct scrcpy_options {
bool require_audio;
bool list_encoders;
bool list_displays;
bool kill_adb_on_close;
};
extern const struct scrcpy_options scrcpy_options_default;

View File

@@ -96,30 +96,23 @@ sc_recorder_rescale_packet(AVStream *stream, AVPacket *packet) {
}
static bool
sc_recorder_write_stream(struct sc_recorder *recorder,
struct sc_recorder_stream *st, AVPacket *packet) {
AVStream *stream = recorder->ctx->streams[st->index];
sc_recorder_write_stream(struct sc_recorder *recorder, int stream_index,
AVPacket *packet) {
AVStream *stream = recorder->ctx->streams[stream_index];
sc_recorder_rescale_packet(stream, packet);
if (st->last_pts != AV_NOPTS_VALUE && packet->pts <= st->last_pts) {
LOGW("Fixing PTS non monotonically increasing in stream %d "
"(%" PRIi64 " >= %" PRIi64 ")",
st->index, st->last_pts, packet->pts);
packet->pts = ++st->last_pts;
packet->dts = packet->pts;
} else {
st->last_pts = packet->pts;
}
return av_interleaved_write_frame(recorder->ctx, packet) >= 0;
}
static inline bool
sc_recorder_write_video(struct sc_recorder *recorder, AVPacket *packet) {
return sc_recorder_write_stream(recorder, &recorder->video_stream, packet);
return sc_recorder_write_stream(recorder, recorder->video_stream_index,
packet);
}
static inline bool
sc_recorder_write_audio(struct sc_recorder *recorder, AVPacket *packet) {
return sc_recorder_write_stream(recorder, &recorder->audio_stream, packet);
return sc_recorder_write_stream(recorder, recorder->audio_stream_index,
packet);
}
static bool
@@ -185,11 +178,10 @@ static bool
sc_recorder_process_header(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped &&
((recorder->video && !recorder->video_init)
|| (recorder->audio && !recorder->audio_init)
while (!recorder->stopped && (!recorder->video_init
|| !recorder->audio_init
|| sc_recorder_has_empty_queues(recorder))) {
sc_cond_wait(&recorder->cond, &recorder->mutex);
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
}
if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
@@ -222,9 +214,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
goto end;
}
assert(recorder->video_stream.index >= 0);
assert(recorder->video_stream_index >= 0);
AVStream *video_stream =
recorder->ctx->streams[recorder->video_stream.index];
recorder->ctx->streams[recorder->video_stream_index];
bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
if (!ok) {
goto end;
@@ -237,9 +229,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
goto end;
}
assert(recorder->audio_stream.index >= 0);
assert(recorder->audio_stream_index >= 0);
AVStream *audio_stream =
recorder->ctx->streams[recorder->audio_stream.index];
recorder->ctx->streams[recorder->audio_stream_index];
bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
if (!ok) {
goto end;
@@ -297,7 +289,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
// A new packet may be assigned to audio_pkt and be processed
break;
}
sc_cond_wait(&recorder->cond, &recorder->mutex);
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
}
// If stopped is set, continue to process the remaining events (to
@@ -512,10 +504,10 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
return false;
}
recorder->video_stream.index = stream->index;
recorder->video_stream_index = stream->index;
recorder->video_init = true;
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex);
return true;
@@ -530,7 +522,7 @@ sc_recorder_video_packet_sink_close(struct sc_packet_sink *sink) {
sc_mutex_lock(&recorder->mutex);
// EOS also stops the recorder
recorder->stopped = true;
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
}
@@ -556,7 +548,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
return false;
}
rec->stream_index = recorder->video_stream.index;
rec->stream_index = recorder->video_stream_index;
bool ok = sc_vecdeque_push(&recorder->video_queue, rec);
if (!ok) {
@@ -565,7 +557,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
return false;
}
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
return true;
@@ -593,10 +585,10 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
return false;
}
recorder->audio_stream.index = stream->index;
recorder->audio_stream_index = stream->index;
recorder->audio_init = true;
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex);
return true;
@@ -612,7 +604,7 @@ sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
sc_mutex_lock(&recorder->mutex);
// EOS also stops the recorder
recorder->stopped = true;
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
}
@@ -639,7 +631,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
return false;
}
rec->stream_index = recorder->audio_stream.index;
rec->stream_index = recorder->audio_stream_index;
bool ok = sc_vecdeque_push(&recorder->audio_queue, rec);
if (!ok) {
@@ -648,7 +640,7 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
return false;
}
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
return true;
@@ -666,16 +658,10 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
sc_mutex_lock(&recorder->mutex);
recorder->audio = false;
recorder->audio_init = true;
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex);
}
static void
sc_recorder_stream_init(struct sc_recorder_stream *stream) {
stream->index = -1;
stream->last_pts = AV_NOPTS_VALUE;
}
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
@@ -691,11 +677,16 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
goto error_free_filename;
}
ok = sc_cond_init(&recorder->cond);
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
goto error_mutex_destroy;
}
ok = sc_cond_init(&recorder->stream_cond);
if (!ok) {
goto error_queue_cond_destroy;
}
assert(video || audio);
recorder->video = video;
recorder->audio = audio;
@@ -707,8 +698,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->video_init = false;
recorder->audio_init = false;
sc_recorder_stream_init(&recorder->video_stream);
sc_recorder_stream_init(&recorder->audio_stream);
recorder->video_stream_index = -1;
recorder->audio_stream_index = -1;
recorder->format = format;
@@ -739,6 +730,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
return true;
error_queue_cond_destroy:
sc_cond_destroy(&recorder->queue_cond);
error_mutex_destroy:
sc_mutex_destroy(&recorder->mutex);
error_free_filename:
@@ -763,7 +756,8 @@ void
sc_recorder_stop(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex);
recorder->stopped = true;
sc_cond_signal(&recorder->cond);
sc_cond_signal(&recorder->queue_cond);
sc_cond_signal(&recorder->stream_cond);
sc_mutex_unlock(&recorder->mutex);
}
@@ -774,7 +768,8 @@ sc_recorder_join(struct sc_recorder *recorder) {
void
sc_recorder_destroy(struct sc_recorder *recorder) {
sc_cond_destroy(&recorder->cond);
sc_cond_destroy(&recorder->stream_cond);
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
}

View File

@@ -14,11 +14,6 @@
struct sc_recorder_queue SC_VECDEQUE(AVPacket *);
struct sc_recorder_stream {
int index;
int64_t last_pts;
};
struct sc_recorder {
struct sc_packet_sink video_packet_sink;
struct sc_packet_sink audio_packet_sink;
@@ -40,18 +35,19 @@ struct sc_recorder {
sc_thread thread;
sc_mutex mutex;
sc_cond cond;
sc_cond queue_cond;
// set on sc_recorder_stop(), packet_sink close or recording failure
bool stopped;
struct sc_recorder_queue video_queue;
struct sc_recorder_queue audio_queue;
// wake up the recorder thread once the video or audio codec is known
sc_cond stream_cond;
bool video_init;
bool audio_init;
struct sc_recorder_stream video_stream;
struct sc_recorder_stream audio_stream;
int video_stream_index;
int audio_stream_index;
const struct sc_recorder_callbacks *cbs;
void *cbs_userdata;

View File

@@ -364,7 +364,6 @@ scrcpy(struct scrcpy_options *options) {
.power_on = options->power_on,
.list_encoders = options->list_encoders,
.list_displays = options->list_displays,
.kill_adb_on_close = options->kill_adb_on_close,
};
static const struct sc_server_callbacks cbs = {

View File

@@ -794,15 +794,6 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
return sc_server_connect_to_tcpip(server, ip_port);
}
static void
sc_server_kill_adb_if_requested(struct sc_server *server) {
if (server->params.kill_adb_on_close) {
LOGI("Killing adb server...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
sc_adb_kill_server(&server->intr, flags);
}
}
static int
run_server(void *data) {
struct sc_server *server = data;
@@ -814,7 +805,7 @@ run_server(void *data) {
// is parsed, so it is not output)
bool ok = sc_adb_start_server(&server->intr, 0);
if (!ok) {
LOGE("Could not start adb server");
LOGE("Could not start adb daemon");
goto error_connection_failed;
}
@@ -1002,12 +993,9 @@ run_server(void *data) {
sc_process_close(pid);
sc_server_kill_adb_if_requested(server);
return 0;
error_connection_failed:
sc_server_kill_adb_if_requested(server);
server->cbs->on_connection_failed(server, server->cbs_userdata);
return -1;
}

View File

@@ -58,7 +58,6 @@ struct sc_server_params {
bool power_on;
bool list_encoders;
bool list_displays;
bool kill_adb_on_close;
};
struct sc_server {

View File

@@ -83,7 +83,7 @@ scrcpy_otg(struct scrcpy_options *options) {
#ifdef _WIN32
// On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773>
LOGI("Killing adb server (if any)...");
LOGI("Killing adb daemon (if any)...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags);

View File

@@ -56,7 +56,7 @@ For example, to use the device as a dictaphone and record a capture directly on
the computer:
```
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus
```

View File

@@ -17,7 +17,7 @@ To record only the audio:
```bash
scrcpy --no-video --record=file.opus
scrcpy --no-video --audio-codec=aac --record=file.aac
scrcpy --no-video --audio-codec=aac --record-file=file.aac
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
```

View File

@@ -163,7 +163,7 @@ public final class AudioCapture {
// - an estimation from the previous PTS and the packet size as a fallback.
//
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
pts = previousPts + 1;
pts = previousPts;
}
previousPts = pts;

View File

@@ -105,8 +105,17 @@ public final class AudioEncoder implements AsyncProcessor {
private void outputThread(MediaCodec mediaCodec) throws IOException, InterruptedException {
streamer.writeAudioHeader();
long lastPts = 0;
while (!Thread.currentThread().isInterrupted()) {
OutputTask task = outputTasks.take();
if (task.bufferInfo.presentationTimeUs < lastPts) {
// Fix PTS if not monotonically increasing
task.bufferInfo.presentationTimeUs = lastPts;
} else {
lastPts = task.bufferInfo.presentationTimeUs;
}
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
try {
streamer.writePacket(buffer, task.bufferInfo);

View File

@@ -14,13 +14,13 @@ public final class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final Object manager;
private final android.hardware.input.InputManager manager;
private Method injectInputEventMethod;
private static Method setDisplayIdMethod;
private static Method setActionButtonMethod;
public InputManager(Object manager) {
public InputManager(android.hardware.input.InputManager manager) {
this.manager = manager;
}

View File

@@ -62,21 +62,11 @@ public final class ServiceManager {
return displayManager;
}
public static Class<?> getInputManagerClass() {
try {
// Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview
return Class.forName("android.hardware.input.InputManagerGlobal");
} catch (ClassNotFoundException e) {
return android.hardware.input.InputManager.class;
}
}
public static InputManager getInputManager() {
if (inputManager == null) {
try {
Class<?> inputManagerClass = getInputManagerClass();
Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance");
Object im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
inputManager = new InputManager(im);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e);