mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-24 23:34:27 +01:00
Compare commits
23 Commits
audio_sour
...
tmp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db9dc6ae83 | ||
|
|
e0f37f834b | ||
|
|
89b624770c | ||
|
|
79227af89f | ||
|
|
5d12d9071d | ||
|
|
b7add42154 | ||
|
|
dd1bfae4e0 | ||
|
|
bef2d8473b | ||
|
|
609719bde0 | ||
|
|
3a0703f428 | ||
|
|
245981281e | ||
|
|
1d25338119 | ||
|
|
457c7fe5cf | ||
|
|
7998811fa5 | ||
|
|
7044122fc5 | ||
|
|
c63d9e1803 | ||
|
|
0ba9d35705 | ||
|
|
cac8e9c821 | ||
|
|
1c7680f689 | ||
|
|
5ae01749bf | ||
|
|
1fd57ede1f | ||
|
|
48fc18e380 | ||
|
|
ea6a94d355 |
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2024 Romain Vimont
|
||||
Copyright (C) 2018-2025 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
22
README.md
22
README.md
@@ -2,16 +2,16 @@
|
||||
source for the project. Do not download releases from random websites, even if
|
||||
their name contains `scrcpy`.**
|
||||
|
||||
# scrcpy (v3.1)
|
||||
# scrcpy (v3.2)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
_pronounced "**scr**een **c**o**py**"_
|
||||
|
||||
This application mirrors Android devices (video and audio) connected via
|
||||
USB or [over TCP/IP](doc/connection.md#tcpip-wireless), and allows to control the
|
||||
device with the keyboard and the mouse of the computer. It does not require any
|
||||
_root_ access. It works on _Linux_, _Windows_ and _macOS_.
|
||||
This application mirrors Android devices (video and audio) connected via USB or
|
||||
[TCP/IP](doc/connection.md#tcpip-wireless) and allows control using the
|
||||
computer's keyboard and mouse. It does not require _root_ access or an app
|
||||
installed on the device. It works on _Linux_, _Windows_, and _macOS_.
|
||||
|
||||

|
||||
|
||||
@@ -78,6 +78,16 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
|
||||
- [macOS](doc/macos.md)
|
||||
|
||||
|
||||
## Must-know tips
|
||||
|
||||
- [Reducing resolution](doc/video.md#size) may greatly improve performance
|
||||
(`scrcpy -m1024`)
|
||||
- [_Right-click_](doc/mouse.md#mouse-bindings) triggers `BACK`
|
||||
- [_Middle-click_](doc/mouse.md#mouse-bindings) triggers `HOME`
|
||||
- <kbd>Alt</kbd>+<kbd>f</kbd> toggles [fullscreen](doc/window.md#fullscreen)
|
||||
- There are many other [shortcuts](doc/shortcuts.md)
|
||||
|
||||
|
||||
## Usage examples
|
||||
|
||||
There are a lot of options, [documented](#user-documentation) in separate pages.
|
||||
@@ -200,7 +210,7 @@ work][donate]:
|
||||
## Licence
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2024 Romain Vimont
|
||||
Copyright (C) 2018-2025 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -122,7 +122,7 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--audio-source)
|
||||
COMPREPLY=($(compgen -W 'output mic playback' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'output playback mic mic-unprocessed mic-camcorder mic-voice-recognition mic-voice-communication voice-call voice-call-uplink voice-call-downlink voice-performance' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--camera-facing)
|
||||
|
||||
@@ -16,7 +16,7 @@ arguments=(
|
||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||
'--audio-dup=[Duplicate audio]'
|
||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
||||
'--audio-source=[Select the audio source]:source:(output playback mic mic-unprocessed mic-camcorder mic-voice-recognition mic-voice-communication voice-call voice-call-uplink voice-call-downlink voice-performance)'
|
||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
||||
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||
'--camera-ar=[Select the camera size by its aspect ratio]'
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=7.1
|
||||
VERSION=7.1.1
|
||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||
PROJECT_DIR=ffmpeg-$VERSION
|
||||
SHA256SUM=40973D44970DBC83EF302B0609F2E74982BE2D85916DD2EE7472D30678A7ABE6
|
||||
SHA256SUM=733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=1.0.27
|
||||
VERSION=1.0.28
|
||||
FILENAME=libusb-$VERSION.tar.gz
|
||||
PROJECT_DIR=libusb-$VERSION
|
||||
SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
|
||||
SHA256SUM=378b3709a405065f8f9fb9f35e82d666defde4d342c2a1b181a9ac134d23c6fe
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
From 6be87ceb33a9aad3bf5204bb13b3a5e8b498fd26 Mon Sep 17 00:00:00 2001
|
||||
From: Neal Gompa <neal@gompa.dev>
|
||||
Date: Mon, 10 Feb 2025 05:00:56 -0500
|
||||
Subject: [PATCH] pipewire: Ensure that the correct struct is used for
|
||||
enumeration APIs
|
||||
|
||||
PipeWire now requires the correct struct type is used, otherwise
|
||||
it will fail to compile.
|
||||
|
||||
Reference: https://gitlab.freedesktop.org/pipewire/pipewire/-/commit/188d920733f0791413d3386e5536ee7377f71b2f
|
||||
|
||||
Fixes: https://github.com/libsdl-org/SDL/issues/12224
|
||||
(cherry picked from commit d35bef64e913dd7d5dd3153a4b61f10ef837dad6)
|
||||
---
|
||||
src/audio/pipewire/SDL_pipewire.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
|
||||
index 889e05decb..5d1bfc28de 100644
|
||||
--- a/src/audio/pipewire/SDL_pipewire.c
|
||||
+++ b/src/audio/pipewire/SDL_pipewire.c
|
||||
@@ -590,7 +590,7 @@ static void node_event_info(void *object, const struct pw_node_info *info)
|
||||
|
||||
/* Need to parse the parameters to get the sample rate */
|
||||
for (i = 0; i < info->n_params; ++i) {
|
||||
- pw_node_enum_params(node->proxy, 0, info->params[i].id, 0, 0, NULL);
|
||||
+ pw_node_enum_params((struct pw_node*)node->proxy, 0, info->params[i].id, 0, 0, NULL);
|
||||
}
|
||||
|
||||
hotplug_core_sync(node);
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=2.30.10
|
||||
VERSION=2.32.2
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=35a8b9c4f3635d85762b904ac60ca4e0806bff89faeb269caafbe80860d67168
|
||||
SHA256SUM=f2c7297ae7b3d3910a8b131e1e2a558fdd6d1a4443d5e345374d45cadfcb05a4
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -18,6 +18,7 @@ then
|
||||
else
|
||||
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/SDL-pipewire-Ensure-that-the-correct-struct-is-used-for-.patch
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
@@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "3.1"
|
||||
VALUE "ProductVersion", "3.2"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
20
app/scrcpy.1
20
app/scrcpy.1
@@ -67,13 +67,19 @@ The available encoders can be listed by \fB\-\-list\-encoders\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-source " source
|
||||
Select the audio source (output, mic or playback).
|
||||
Select the audio source. Possible values are:
|
||||
|
||||
The "output" source forwards the whole audio output, and disables playback on the device.
|
||||
|
||||
The "playback" source captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
|
||||
The "mic" source captures the microphone.
|
||||
- "output": forwards the whole audio output, and disables playback on the device.
|
||||
- "playback": captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
- "mic": captures the microphone.
|
||||
- "mic-unprocessed": captures the microphone unprocessed (raw) sound.
|
||||
- "mic-camcorder": captures the microphone tuned for video recording, with the same orientation as the camera if available.
|
||||
- "mic-voice-recognition": captures the microphone tuned for voice recognition.
|
||||
- "mic-voice-communication": captures the microphone tuned for voice communications (it will for instance take advantage of echo cancellation or automatic gain control if available).
|
||||
- "voice-call": captures voice call.
|
||||
- "voice-call-uplink": captures voice call uplink only.
|
||||
- "voice-call-downlink": captures voice call downlink only.
|
||||
- "voice-performance": captures audio meant to be processed for live performance (karaoke), includes both the microphone and the device playback.
|
||||
|
||||
Default is output.
|
||||
|
||||
@@ -842,7 +848,7 @@ Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
|
||||
|
||||
Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
|
||||
Copyright \(co 2018\-2025 Romain Vimont <rom@rom1v.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
|
||||
|
||||
@@ -76,8 +76,10 @@ sc_audio_regulator_pull(struct sc_audio_regulator *ar, uint8_t *out,
|
||||
// Wait until the buffer is filled up to at least target_buffering
|
||||
// before playing
|
||||
if (buffered_samples < ar->target_buffering) {
|
||||
LOGV("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||
" samples", out_samples);
|
||||
#endif
|
||||
// Delay playback starting to reach the target buffering. Fill the
|
||||
// whole buffer with silence (len is small compared to the
|
||||
// arbitrary margin value).
|
||||
@@ -98,8 +100,10 @@ sc_audio_regulator_pull(struct sc_audio_regulator *ar, uint8_t *out,
|
||||
// dropped to keep the latency minimal. However, this would cause very
|
||||
// audible glitches, so let the clock compensation restore the target
|
||||
// latency.
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
|
||||
silence);
|
||||
#endif
|
||||
memset(out + TO_BYTES(read), 0, TO_BYTES(silence));
|
||||
|
||||
bool received = atomic_load_explicit(&ar->received,
|
||||
@@ -137,6 +141,36 @@ bool
|
||||
sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
SwrContext *swr_ctx = ar->swr_ctx;
|
||||
|
||||
uint32_t input_samples = frame->nb_samples;
|
||||
|
||||
assert(frame->pts >= 0);
|
||||
int64_t pts = frame->pts;
|
||||
if (ar->next_expected_pts && pts - ar->next_expected_pts > 100000) {
|
||||
LOGV("[Audio] Discontinuity detected: %" PRIi64 "µs",
|
||||
pts - ar->next_expected_pts);
|
||||
// More than 100ms: consider it as a discontinuity
|
||||
// (typically because silence packets were not captured)
|
||||
uint32_t can_read = sc_audiobuf_can_read(&ar->buf);
|
||||
if (input_samples + can_read < ar->target_buffering) {
|
||||
// Adjust buffering to the target value directly
|
||||
uint32_t silence = ar->target_buffering - can_read - input_samples;
|
||||
sc_audiobuf_write_silence(&ar->buf, silence);
|
||||
}
|
||||
|
||||
// Reset state
|
||||
ar->avg_buffering.avg = ar->target_buffering;
|
||||
int ret = swr_set_compensation(swr_ctx, 0, 0);
|
||||
(void) ret;
|
||||
assert(!ret); // disabling compensation should never fail
|
||||
ar->compensation_active = false;
|
||||
ar->samples_since_resync = 0;
|
||||
atomic_store_explicit(&ar->underflow, 0, memory_order_relaxed);
|
||||
}
|
||||
|
||||
int64_t packet_duration = input_samples * INT64_C(1000000)
|
||||
/ ar->sample_rate;
|
||||
ar->next_expected_pts = pts + packet_duration;
|
||||
|
||||
int64_t swr_delay = swr_get_delay(swr_ctx, ar->sample_rate);
|
||||
// No need to av_rescale_rnd(), input and output sample rates are the same.
|
||||
// Add more space (256) for clock compensation.
|
||||
@@ -209,6 +243,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
if (played) {
|
||||
underflow = atomic_exchange_explicit(&ar->underflow, 0,
|
||||
memory_order_relaxed);
|
||||
ar->underflow_report += underflow;
|
||||
|
||||
max_buffered_samples = ar->target_buffering * 11 / 10
|
||||
+ 60 * ar->sample_rate / 1000 /* 60 ms */;
|
||||
@@ -255,7 +290,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
// Number of samples added (or removed, if negative) for compensation
|
||||
int32_t instant_compensation = (int32_t) written - frame->nb_samples;
|
||||
int32_t instant_compensation = (int32_t) written - input_samples;
|
||||
// Inserting silence instantly increases buffering
|
||||
int32_t inserted_silence = (int32_t) underflow;
|
||||
// Dropping input samples instantly decreases buffering
|
||||
@@ -311,7 +346,9 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
int abs_max_diff = distance / 50;
|
||||
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
||||
" compensation=%d (underflow=%" PRIu32 ")",
|
||||
ar->target_buffering, avg, can_read, diff, ar->underflow_report);
|
||||
ar->underflow_report = 0;
|
||||
|
||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||
if (ret < 0) {
|
||||
@@ -394,7 +431,9 @@ sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
||||
atomic_init(&ar->played, false);
|
||||
atomic_init(&ar->received, false);
|
||||
atomic_init(&ar->underflow, 0);
|
||||
ar->underflow_report = 0;
|
||||
ar->compensation_active = false;
|
||||
ar->next_expected_pts = 0;
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ struct sc_audio_regulator {
|
||||
// Number of silence samples inserted since the last received packet
|
||||
atomic_uint_least32_t underflow;
|
||||
|
||||
// Number of silence samples inserted since the last log
|
||||
uint32_t underflow_report;
|
||||
|
||||
// Non-zero compensation applied (only used by the receiver thread)
|
||||
bool compensation_active;
|
||||
|
||||
@@ -54,6 +57,9 @@ struct sc_audio_regulator {
|
||||
|
||||
// Set to true the first time samples are pulled by the player
|
||||
atomic_bool played;
|
||||
|
||||
// PTS of the next expected packet (useful to detect discontinuities)
|
||||
int64_t next_expected_pts;
|
||||
};
|
||||
|
||||
bool
|
||||
|
||||
@@ -217,13 +217,31 @@ static const struct sc_option options[] = {
|
||||
.longopt_id = OPT_AUDIO_SOURCE,
|
||||
.longopt = "audio-source",
|
||||
.argdesc = "source",
|
||||
.text = "Select the audio source (output, mic or playback).\n"
|
||||
"The \"output\" source forwards the whole audio output, and "
|
||||
"disables playback on the device.\n"
|
||||
"The \"playback\" source captures the audio playback (Android "
|
||||
"apps can opt-out, so the whole output is not necessarily "
|
||||
.text = "Select the audio source. Possible values are:\n"
|
||||
" - \"output\": forwards the whole audio output, and disables "
|
||||
"playback on the device.\n"
|
||||
" - \"playback\": captures the audio playback (Android apps "
|
||||
"can opt-out, so the whole output is not necessarily "
|
||||
"captured).\n"
|
||||
"The \"mic\" source captures the microphone.\n"
|
||||
" - \"mic\": captures the microphone.\n"
|
||||
" - \"mic-unprocessed\": captures the microphone unprocessed "
|
||||
"(raw) sound.\n"
|
||||
" - \"mic-camcorder\": captures the microphone tuned for video "
|
||||
"recording, with the same orientation as the camera if "
|
||||
"available.\n"
|
||||
" - \"mic-voice-recognition\": captures the microphone tuned "
|
||||
"for voice recognition.\n"
|
||||
" - \"mic-voice-communication\": captures the microphone tuned "
|
||||
"for voice communications (it will for instance take advantage "
|
||||
"of echo cancellation or automatic gain control if "
|
||||
"available).\n"
|
||||
" - \"voice-call\": captures voice call.\n"
|
||||
" - \"voice-call-uplink\": captures voice call uplink only.\n"
|
||||
" - \"voice-call-downlink\": captures voice call downlink "
|
||||
"only.\n"
|
||||
" - \"voice-performance\": captures audio meant to be "
|
||||
"processed for live performance (karaoke), includes both the "
|
||||
"microphone and the device playback.\n"
|
||||
"Default is output.",
|
||||
},
|
||||
{
|
||||
@@ -2036,8 +2054,50 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported audio source: %s (expected output, mic or playback)",
|
||||
optarg);
|
||||
if (!strcmp(optarg, "mic-unprocessed")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_UNPROCESSED;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "mic-camcorder")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_CAMCORDER;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "mic-voice-recognition")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "mic-voice-communication")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-call")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_CALL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-call-uplink")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_CALL_UPLINK;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-call-downlink")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-performance")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_PERFORMANCE;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported audio source: %s (expected output, mic, playback, "
|
||||
"mic-unprocessed, mic-camcorder, mic-voice-recognition, "
|
||||
"mic-voice-communication, voice-call, voice-call-uplink, "
|
||||
"voice-call-downlink, voice-performance)", optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,14 @@ enum sc_audio_source {
|
||||
SC_AUDIO_SOURCE_OUTPUT,
|
||||
SC_AUDIO_SOURCE_MIC,
|
||||
SC_AUDIO_SOURCE_PLAYBACK,
|
||||
SC_AUDIO_SOURCE_MIC_UNPROCESSED,
|
||||
SC_AUDIO_SOURCE_MIC_CAMCORDER,
|
||||
SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION,
|
||||
SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL_UPLINK,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK,
|
||||
SC_AUDIO_SOURCE_VOICE_PERFORMANCE,
|
||||
};
|
||||
|
||||
enum sc_camera_facing {
|
||||
|
||||
@@ -149,6 +149,22 @@ sc_server_get_audio_source_name(enum sc_audio_source audio_source) {
|
||||
return "mic";
|
||||
case SC_AUDIO_SOURCE_PLAYBACK:
|
||||
return "playback";
|
||||
case SC_AUDIO_SOURCE_MIC_UNPROCESSED:
|
||||
return "mic-unprocessed";
|
||||
case SC_AUDIO_SOURCE_MIC_CAMCORDER:
|
||||
return "mic-camcorder";
|
||||
case SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION:
|
||||
return "mic-voice-recognition";
|
||||
case SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION:
|
||||
return "mic-voice-communication";
|
||||
case SC_AUDIO_SOURCE_VOICE_CALL:
|
||||
return "voice-call";
|
||||
case SC_AUDIO_SOURCE_VOICE_CALL_UPLINK:
|
||||
return "voice-call-uplink";
|
||||
case SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK:
|
||||
return "voice-call-downlink";
|
||||
case SC_AUDIO_SOURCE_VOICE_PERFORMANCE:
|
||||
return "voice-performance";
|
||||
default:
|
||||
assert(!"unexpected audio source");
|
||||
return NULL;
|
||||
|
||||
@@ -116,3 +116,38 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
||||
|
||||
return samples_count;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
sc_audiobuf_write_silence(struct sc_audiobuf *buf, uint32_t samples_count) {
|
||||
// Only the writer thread can write head, so memory_order_relaxed is
|
||||
// sufficient
|
||||
uint32_t head = atomic_load_explicit(&buf->head, memory_order_relaxed);
|
||||
|
||||
// The tail cursor is updated after the data is consumed by the reader
|
||||
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||
|
||||
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||
if (!can_write) {
|
||||
return 0;
|
||||
}
|
||||
if (samples_count > can_write) {
|
||||
samples_count = can_write;
|
||||
}
|
||||
|
||||
uint32_t right_count = buf->alloc_size - head;
|
||||
if (right_count > samples_count) {
|
||||
right_count = samples_count;
|
||||
}
|
||||
memset(buf->data + (head * buf->sample_size), 0,
|
||||
right_count * buf->sample_size);
|
||||
|
||||
if (samples_count > right_count) {
|
||||
uint32_t left_count = samples_count - right_count;
|
||||
memset(buf->data, 0, left_count * buf->sample_size);
|
||||
}
|
||||
|
||||
uint32_t new_head = (head + samples_count) % buf->alloc_size;
|
||||
atomic_store_explicit(&buf->head, new_head, memory_order_release);
|
||||
|
||||
return samples_count;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ uint32_t
|
||||
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
|
||||
uint32_t samples_count);
|
||||
|
||||
uint32_t
|
||||
sc_audiobuf_write_silence(struct sc_audiobuf *buf, uint32_t samples);
|
||||
|
||||
static inline uint32_t
|
||||
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
|
||||
assert(buf->alloc_size);
|
||||
|
||||
@@ -113,6 +113,14 @@ static void test_audiobuf_partial_read_write(void) {
|
||||
uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3};
|
||||
assert(!memcmp(data, expected2, 12));
|
||||
|
||||
w = sc_audiobuf_write_silence(&buf, 4);
|
||||
assert(w == 4);
|
||||
|
||||
r = sc_audiobuf_read(&buf, data, 4);
|
||||
assert(r == 4);
|
||||
uint32_t expected3[] = {0, 0, 0, 0};
|
||||
assert(!memcmp(data, expected3, 4));
|
||||
|
||||
sc_audiobuf_destroy(&buf);
|
||||
}
|
||||
|
||||
|
||||
14
doc/audio.md
14
doc/audio.md
@@ -66,6 +66,20 @@ the computer:
|
||||
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
|
||||
```
|
||||
|
||||
Many sources are available:
|
||||
|
||||
- `output` (default): forwards the whole audio output, and disables playback on the device (mapped to [`REMOTE_SUBMIX`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#REMOTE_SUBMIX)).
|
||||
- `playback`: captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
- `mic`: captures the microphone (mapped to [`MIC`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#MIC)).
|
||||
- `mic-unprocessed`: captures the microphone unprocessed (raw) sound (mapped to [`UNPROCESSED`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#UNPROCESSED)).
|
||||
- `mic-camcorder`: captures the microphone tuned for video recording, with the same orientation as the camera if available (mapped to [`CAMCORDER`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#CAMCORDER)).
|
||||
- `mic-voice-recognition`: captures the microphone tuned for voice recognition (mapped to [`VOICE_RECOGNITION`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_RECOGNITION)).
|
||||
- `mic-voice-communication`: captures the microphone tuned for voice communications (it will for instance take advantage of echo cancellation or automatic gain control if available) (mapped to [`VOICE_COMMUNICATION`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_COMMUNICATION)).
|
||||
- `voice-call`: captures voice call (mapped to [`VOICE_CALL`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_CALL)).
|
||||
- `voice-call-uplink`: captures voice call uplink only (mapped to [`VOICE_UPLINK`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_UPLINK)).
|
||||
- `voice-call-downlink`: captures voice call downlink only (mapped to [`VOICE_DOWNLINK`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_DOWNLINK)).
|
||||
- `voice-performance`: captures audio meant to be processed for live performance (karaoke), includes both the microphone and the device playback (mapped to [`VOICE_PERFORMANCE`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_PERFORMANCE)).
|
||||
|
||||
### Duplication
|
||||
|
||||
An alternative device audio capture method is also available (only for Android
|
||||
|
||||
@@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v3.1`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0`</sub>
|
||||
- [`scrcpy-server-v3.2`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `b920e0ea01936bf2482f4ba2fa985c22c13c621999e3d33b45baa5acfc1ea3d0`</sub>
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-server-v3.2
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
@@ -113,16 +113,17 @@ with the device IP address you found)_.
|
||||
7. Run `scrcpy` as usual.
|
||||
8. Run `adb disconnect` once you're done.
|
||||
|
||||
Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
|
||||
having to physically connect your device directly to your computer.
|
||||
Since Android 11, a [wireless debugging option][adb-wireless] allows you to
|
||||
bypass having to physically connect your device to your computer.
|
||||
|
||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
|
||||
|
||||
|
||||
## Autostart
|
||||
|
||||
A small tool (by the scrcpy author) allows to run arbitrary commands whenever a
|
||||
new Android device is connected: [AutoAdb]. It can be used to start scrcpy:
|
||||
A small tool (by the scrcpy author) allows you to run arbitrary commands
|
||||
whenever a new Android device is connected: [AutoAdb]. It can be used to start
|
||||
scrcpy:
|
||||
|
||||
```bash
|
||||
autoadb scrcpy -s '{}'
|
||||
|
||||
@@ -34,6 +34,31 @@ adb shell settings put global stay_on_while_plugged_in 0
|
||||
```
|
||||
|
||||
|
||||
## Screen off timeout
|
||||
|
||||
The Android screen automatically turns off after some delay.
|
||||
|
||||
To change this delay while scrcpy is running:
|
||||
|
||||
```bash
|
||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
||||
```
|
||||
|
||||
The initial value is restored on exit.
|
||||
|
||||
It is possible to change this setting manually:
|
||||
|
||||
```bash
|
||||
# get the current screen_off_timeout value
|
||||
adb shell settings get system screen_off_timeout
|
||||
# set a new value (in milliseconds)
|
||||
adb shell settings put system screen_off_timeout 30000
|
||||
```
|
||||
|
||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
||||
argument is in seconds.
|
||||
|
||||
|
||||
## Turn screen off
|
||||
|
||||
It is possible to turn the device screen off while mirroring on start with a
|
||||
@@ -71,31 +96,6 @@ adb shell cmd display power-on 0
|
||||
```
|
||||
|
||||
|
||||
## Screen off timeout
|
||||
|
||||
The Android screen automatically turns off after some delay.
|
||||
|
||||
To change this delay while scrcpy is running:
|
||||
|
||||
```bash
|
||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
||||
```
|
||||
|
||||
The initial value is restored on exit.
|
||||
|
||||
It is possible to change this setting manually:
|
||||
|
||||
```bash
|
||||
# get the current screen_off_timeout value
|
||||
adb shell settings get system screen_off_timeout
|
||||
# set a new value (in milliseconds)
|
||||
adb shell settings put system screen_off_timeout 30000
|
||||
```
|
||||
|
||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
||||
argument is in seconds.
|
||||
|
||||
|
||||
## Show touches
|
||||
|
||||
For presentations, it may be useful to show physical touches (on the physical
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
Download a static build of the [latest release]:
|
||||
|
||||
- [`scrcpy-linux-x86_64-v3.1.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `37dba54092ed9ec6b2f8f95432f61b8ea124aec9f1e9f2b3d22d4b10bb04c59a`</sub>
|
||||
- [`scrcpy-linux-x86_64-v3.2.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `df6cf000447428fcde322022848d655ff0211d98688d0f17cbbf21be9c1272be`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-linux-x86_64-v3.1.tar.gz
|
||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-linux-x86_64-v3.2.tar.gz
|
||||
|
||||
and extract it.
|
||||
|
||||
@@ -27,7 +27,7 @@ Scrcpy is packaged in several distributions and package managers:
|
||||
- Arch Linux: `pacman -S scrcpy`
|
||||
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
|
||||
- Gentoo: `emerge scrcpy`
|
||||
- Snap: `snap install scrcpy`
|
||||
- Snap: ~~`snap install scrcpy`~~ _(obsolete version)_
|
||||
- … (see [repology](https://repology.org/project/scrcpy/versions))
|
||||
|
||||
|
||||
|
||||
12
doc/macos.md
12
doc/macos.md
@@ -6,15 +6,15 @@
|
||||
|
||||
Download a static build of the [latest release]:
|
||||
|
||||
- [`scrcpy-macos-aarch64-v3.1.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||
<sub>SHA-256: `478618d940421e5f57942f5479d493ecbb38210682937a200f712aee5f235daf`</sub>
|
||||
- [`scrcpy-macos-aarch64-v3.2.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||
<sub>SHA-256: `f6d1f3c5f74d4d46f5080baa5b56b69f5edbf698d47e0cf4e2a1fd5058f9507b`</sub>
|
||||
|
||||
- [`scrcpy-macos-x86_64-v3.1.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `acde98e29c273710ffa469371dbca4a728a44c41c380381f8a54e5b5301b9e87`</sub>
|
||||
- [`scrcpy-macos-x86_64-v3.2.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `e337d5cf0ba4e1281699c338ce5f104aee96eb7b2893dc851399b6643eb4044e`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-aarch64-v3.1.tar.gz
|
||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-x86_64-v3.1.tar.gz
|
||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-macos-aarch64-v3.2.tar.gz
|
||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-macos-x86_64-v3.2.tar.gz
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
12
doc/mouse.md
12
doc/mouse.md
@@ -83,9 +83,9 @@ process like the _adb daemon_).
|
||||
## Mouse bindings
|
||||
|
||||
By default, with SDK mouse:
|
||||
- right-click triggers BACK (or POWER on)
|
||||
- middle-click triggers HOME
|
||||
- the 4th click triggers APP_SWITCH
|
||||
- right-click triggers `BACK` (or `POWER` on)
|
||||
- middle-click triggers `HOME`
|
||||
- the 4th click triggers `APP_SWITCH`
|
||||
- the 5th click expands the notification panel
|
||||
|
||||
The secondary clicks may be forwarded to the device instead by pressing the
|
||||
@@ -121,9 +121,9 @@ Each character must be one of the following:
|
||||
|
||||
- `+`: forward the click to the device
|
||||
- `-`: ignore the click
|
||||
- `b`: trigger shortcut BACK (or turn screen on if off)
|
||||
- `h`: trigger shortcut HOME
|
||||
- `s`: trigger shortcut APP_SWITCH
|
||||
- `b`: trigger shortcut `BACK` (or turn screen on if off)
|
||||
- `h`: trigger shortcut `HOME`
|
||||
- `s`: trigger shortcut `APP_SWITCH`
|
||||
- `n`: trigger shortcut "expand notification panel"
|
||||
|
||||
For example:
|
||||
|
||||
@@ -11,6 +11,8 @@ scrcpy --new-display # use the main display size and density
|
||||
scrcpy --new-display=/240 # use the main display size and 240 dpi
|
||||
```
|
||||
|
||||
The new virtual display is destroyed on exit.
|
||||
|
||||
## Start app
|
||||
|
||||
On some devices, a launcher is available in the virtual display.
|
||||
|
||||
@@ -6,20 +6,26 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`scrcpy-win64-v3.1.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `0c05ea395d95cfe36bee974eeb435a3db87ea5594ff738370d5dc3068a9538ca`</sub>
|
||||
- [`scrcpy-win32-v3.1.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `2b4674ef76719680ac5a9b482d1943bdde3fa25821ad2e98f3c40c347d00d560`</sub>
|
||||
- [`scrcpy-win64-v3.2.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `eaa27133e0520979873ba57ad651560a4cc2618373bd05450b23a84d32beafd0`</sub>
|
||||
- [`scrcpy-win32-v3.2.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `4a3407d7f0c2c8a03e22a12cf0b5e1e585a5056fe23c8e5cf3252207c6fa8357`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win64-v3.1.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win32-v3.1.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-win64-v3.2.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-win32-v3.2.zip
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
### From a package manager
|
||||
|
||||
From [WinGet] (ADB and other dependencies will be installed alongside scrcpy):
|
||||
|
||||
```bash
|
||||
winget install --exact Genymobile.scrcpy
|
||||
```
|
||||
|
||||
From [Chocolatey]:
|
||||
|
||||
```bash
|
||||
@@ -29,12 +35,12 @@ choco install adb # if you don't have it yet
|
||||
|
||||
From [Scoop]:
|
||||
|
||||
|
||||
```bash
|
||||
scoop install scrcpy
|
||||
scoop install adb # if you don't have it yet
|
||||
```
|
||||
|
||||
[WinGet]: https://github.com/microsoft/winget-cli
|
||||
[Chocolatey]: https://chocolatey.org/
|
||||
[Scoop]: https://scoop.sh
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
BUILDDIR=build-auto
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1
|
||||
PREBUILT_SERVER_SHA256=958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.2/scrcpy-server-v3.2
|
||||
PREBUILT_SERVER_SHA256=b920e0ea01936bf2482f4ba2fa985c22c13c621999e3d33b45baa5acfc1ea3d0
|
||||
|
||||
echo "[scrcpy] Downloading prebuilt server..."
|
||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '3.1',
|
||||
version: '3.2',
|
||||
meson_version: '>= 0.49',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode 30100
|
||||
versionName "3.1"
|
||||
versionCode 30200
|
||||
versionName "3.2"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=3.1
|
||||
SCRCPY_VERSION_NAME=3.2
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-35}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
@@ -32,18 +31,7 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
private AudioRecordReader reader;
|
||||
|
||||
public AudioDirectCapture(AudioSource audioSource) {
|
||||
this.audioSource = getAudioSourceValue(audioSource);
|
||||
}
|
||||
|
||||
private static int getAudioSourceValue(AudioSource audioSource) {
|
||||
switch (audioSource) {
|
||||
case OUTPUT:
|
||||
return MediaRecorder.AudioSource.REMOTE_SUBMIX;
|
||||
case MIC:
|
||||
return MediaRecorder.AudioSource.MIC;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported audio source: " + audioSource);
|
||||
}
|
||||
this.audioSource = audioSource.getDirectAudioSource();
|
||||
}
|
||||
|
||||
@TargetApi(AndroidVersions.API_23_ANDROID_6_0)
|
||||
|
||||
@@ -55,6 +55,9 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
private final List<CodecOption> codecOptions;
|
||||
private final String encoderName;
|
||||
|
||||
private boolean recreatePts;
|
||||
private long previousPts;
|
||||
|
||||
// Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4).
|
||||
// So many pending tasks would lead to an unacceptable delay anyway.
|
||||
private final BlockingQueue<InputTask> inputTasks = new ArrayBlockingQueue<>(64);
|
||||
@@ -118,6 +121,9 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
OutputTask task = outputTasks.take();
|
||||
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
|
||||
try {
|
||||
if (recreatePts) {
|
||||
fixTimestamp(task.bufferInfo);
|
||||
}
|
||||
streamer.writePacket(buffer, task.bufferInfo);
|
||||
} finally {
|
||||
mediaCodec.releaseOutputBuffer(task.index, false);
|
||||
@@ -125,6 +131,25 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private void fixTimestamp(MediaCodec.BufferInfo bufferInfo) {
|
||||
assert recreatePts;
|
||||
|
||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
||||
// Config packet, nothing to fix
|
||||
return;
|
||||
}
|
||||
|
||||
long pts = bufferInfo.presentationTimeUs;
|
||||
if (previousPts != 0) {
|
||||
long now = System.nanoTime() / 1000;
|
||||
// This specific encoder produces PTS matching the exact number of samples
|
||||
long duration = pts - previousPts;
|
||||
bufferInfo.presentationTimeUs = now - duration;
|
||||
}
|
||||
|
||||
previousPts = pts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(TerminationListener listener) {
|
||||
thread = new Thread(() -> {
|
||||
@@ -194,6 +219,12 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
Codec codec = streamer.getCodec();
|
||||
mediaCodec = createMediaCodec(codec, encoderName);
|
||||
|
||||
// The default OPUS and FLAC encoders overwrite the input PTS with a value that matches the number of samples. This is not the behavior
|
||||
// we want: it ignores any audio clock drift and hard silences (packets not produced on silence). To work around this behavior,
|
||||
// regenerate PTS based on the current time and the packet duration.
|
||||
String codecName = mediaCodec.getCanonicalName();
|
||||
recreatePts = "c2.android.opus.encoder".equals(codecName) || "c2.android.flac.encoder".equals(codecName);
|
||||
|
||||
mediaCodecThread = new HandlerThread("media-codec");
|
||||
mediaCodecThread.start();
|
||||
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
package com.genymobile.scrcpy.audio;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.MediaRecorder;
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
public enum AudioSource {
|
||||
OUTPUT("output"),
|
||||
MIC("mic"),
|
||||
PLAYBACK("playback");
|
||||
OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX),
|
||||
MIC("mic", MediaRecorder.AudioSource.MIC),
|
||||
PLAYBACK("playback", -1),
|
||||
MIC_UNPROCESSED("mic-unprocessed", MediaRecorder.AudioSource.UNPROCESSED),
|
||||
MIC_CAMCORDER("mic-camcorder", MediaRecorder.AudioSource.CAMCORDER),
|
||||
MIC_VOICE_RECOGNITION("mic-voice-recognition", MediaRecorder.AudioSource.VOICE_RECOGNITION),
|
||||
MIC_VOICE_COMMUNICATION("mic-voice-communication", MediaRecorder.AudioSource.VOICE_COMMUNICATION),
|
||||
VOICE_CALL("voice-call", MediaRecorder.AudioSource.VOICE_CALL),
|
||||
VOICE_CALL_UPLINK("voice-call-uplink", MediaRecorder.AudioSource.VOICE_UPLINK),
|
||||
VOICE_CALL_DOWNLINK("voice-call-downlink", MediaRecorder.AudioSource.VOICE_DOWNLINK),
|
||||
VOICE_PERFORMANCE("voice-performance", MediaRecorder.AudioSource.VOICE_PERFORMANCE);
|
||||
|
||||
private final String name;
|
||||
private final int directAudioSource;
|
||||
|
||||
AudioSource(String name) {
|
||||
AudioSource(String name, int directAudioSource) {
|
||||
this.name = name;
|
||||
this.directAudioSource = directAudioSource;
|
||||
}
|
||||
|
||||
public boolean isDirect() {
|
||||
return this != PLAYBACK;
|
||||
}
|
||||
|
||||
public int getDirectAudioSource() {
|
||||
return directAudioSource;
|
||||
}
|
||||
|
||||
public static AudioSource findByName(String name) {
|
||||
for (AudioSource audioSource : AudioSource.values()) {
|
||||
if (name.equals(audioSource.name)) {
|
||||
|
||||
@@ -23,7 +23,9 @@ public class DisplaySizeMonitor {
|
||||
|
||||
// On Android 14, DisplayListener may be broken (it never sends events). This is fixed in recent Android 14 upgrades, but we can't really
|
||||
// detect it directly, so register a DisplayWindowListener (introduced in Android 11) to listen to configuration changes instead.
|
||||
private static final boolean USE_DEFAULT_METHOD = Build.VERSION.SDK_INT != AndroidVersions.API_34_ANDROID_14;
|
||||
// It has been broken again after an Android 15 upgrade: <https://github.com/Genymobile/scrcpy/issues/5908>
|
||||
// So use the default method only before Android 14.
|
||||
private static final boolean USE_DEFAULT_METHOD = Build.VERSION.SDK_INT < AndroidVersions.API_34_ANDROID_14;
|
||||
|
||||
private DisplayManager.DisplayListenerHandle displayListenerHandle;
|
||||
private HandlerThread handlerThread;
|
||||
|
||||
Reference in New Issue
Block a user