Compare commits

..

3 Commits

Author SHA1 Message Date
14348f42b2 Terminate on controller error
This is particularly important to react to server socket disconnection
since video and audio may be disabled.

PR #4868 <https://github.com/Genymobile/scrcpy/pull/4868>
2024-05-11 17:01:17 +02:00
b5ed834012 Update documentation for --no-window
PR #4868 <https://github.com/Genymobile/scrcpy/pull/4868>
2024-05-11 16:12:57 +02:00
65b9f04f39 Add scrcpy window without video playback
Add the possibility to solely control the device with any keyboard and
mouse mode without screen mirroring:

    scrcpy -KM --no-video --no-audio

This is different from OTG mode, which does not require USB debugging at
all. Here, the standard mode is used but with the possibility to disable
video playback.

By default, always open a window (even without video playback), and add
an option --no-window.

Fixes #4727 <https://github.com/Genymobile/scrcpy/issues/4727>
Fixes #4793 <https://github.com/Genymobile/scrcpy/issues/4793>
PR #4868 <https://github.com/Genymobile/scrcpy/pull/4868>
2024-05-11 16:12:57 +02:00
7 changed files with 65 additions and 88 deletions

View File

@ -66,6 +66,8 @@ static void SDLCALL
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
struct sc_audio_player *ap = userdata;
// This callback is called with the lock used by SDL_LockAudioDevice()
assert(len_int > 0);
size_t len = len_int;
uint32_t count = TO_SAMPLES(len);
@ -179,19 +181,29 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
if (written < samples) {
uint32_t remaining = samples - written;
assert(remaining <= cap);
skipped_samples = sc_audiobuf_truncate(&ap->buf, cap - remaining);
// All samples that could be written without locking have been written,
// now we need to lock to drop/consume old samples
SDL_LockAudioDevice(ap->device);
LOGW("Audio buffer full, %" PRIu32 " samples dropped", skipped_samples);
// Retry with the lock
written += sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
if (written < samples) {
remaining = samples - written;
// Still insufficient, drop old samples to make space
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
assert(skipped_samples == remaining);
// Now there is enough space
uint32_t w = sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
assert(w == remaining);
(void) w;
// Now there is enough space
uint32_t w = sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
assert(w == remaining);
(void) w;
}
written = samples;
SDL_UnlockAudioDevice(ap->device);
}
uint32_t underflow = 0;
@ -213,22 +225,30 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
uint32_t can_read = sc_audiobuf_can_read(&ap->buf);
if (can_read > max_buffered_samples) {
uint32_t skipped = sc_audiobuf_truncate(&ap->buf, max_buffered_samples);
assert(skipped);
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skipped);
#ifndef SC_AUDIO_PLAYER_NDEBUG
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
skipped);
#endif
}
skipped_samples += skipped;
uint32_t skip_samples = 0;
SDL_LockAudioDevice(ap->device);
can_read = sc_audiobuf_can_read(&ap->buf);
if (can_read > max_buffered_samples) {
skip_samples = can_read - max_buffered_samples;
uint32_t r = sc_audiobuf_read(&ap->buf, NULL, skip_samples);
assert(r == skip_samples);
(void) r;
skipped_samples += skip_samples;
}
SDL_UnlockAudioDevice(ap->device);
if (skip_samples) {
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skip_samples);
#ifndef SC_AUDIO_PLAYER_NDEBUG
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32
" samples", skip_samples);
#endif
}
}
}
atomic_store_explicit(&ap->received, true, memory_order_relaxed);
@ -388,7 +408,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
// Use a ring-buffer of the target buffering size plus 1 second between the
// producer and the consumer. It's too big on purpose, to guarantee that
// the producer and the consumer will be able to access it in parallel
// without dropping samples.
// without locking.
uint32_t audiobuf_samples = ap->target_buffering + ap->sample_rate;
size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample;

View File

@ -2738,11 +2738,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
if (opts->record_filename) {
if (!opts->video && !opts->audio) {
LOGE("Video and audio disabled, nothing to record");
return false;
}
if (!opts->record_format) {
opts->record_format = guess_record_format(opts->record_filename);
if (!opts->record_format) {

View File

@ -37,10 +37,10 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
assert(samples_count);
uint8_t *to = to_;
assert(to);
// The tail cursor may be updated by the writer thread to drop samples
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
// Only the reader thread can write tail without synchronization, so
// memory_order_relaxed is sufficient
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_relaxed);
// The head cursor is updated after the data is written to the array
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
@ -50,19 +50,21 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
samples_count = can_read;
}
uint32_t right_count = buf->alloc_size - tail;
if (right_count > samples_count) {
right_count = samples_count;
}
memcpy(to,
buf->data + (tail * buf->sample_size),
right_count * buf->sample_size);
if (to) {
uint32_t right_count = buf->alloc_size - tail;
if (right_count > samples_count) {
right_count = samples_count;
}
memcpy(to,
buf->data + (tail * buf->sample_size),
right_count * buf->sample_size);
if (samples_count > right_count) {
uint32_t left_count = samples_count - right_count;
memcpy(to + (right_count * buf->sample_size),
buf->data,
left_count * buf->sample_size);
if (samples_count > right_count) {
uint32_t left_count = samples_count - right_count;
memcpy(to + (right_count * buf->sample_size),
buf->data,
left_count * buf->sample_size);
}
}
uint32_t new_tail = (tail + samples_count) % buf->alloc_size;
@ -108,27 +110,3 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
return samples_count;
}
uint32_t
sc_audiobuf_truncate(struct sc_audiobuf *buf, uint32_t samples_limit) {
for (;;) {
// 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_read = (buf->alloc_size + head - tail) % buf->alloc_size;
if (can_read <= samples_limit) {
// Nothing to truncate
return 0;
}
uint32_t skip = can_read - samples_limit;
uint32_t new_tail = (tail + skip) % buf->alloc_size;
if (atomic_compare_exchange_weak(&buf->tail, &tail, new_tail)) {
return skip;
}
}
}

View File

@ -49,16 +49,6 @@ uint32_t
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
uint32_t samples_count);
/**
* Drop old samples to keep at most sample_limit samples
*
* Must be called by the writer thread.
*
* \return the number of samples dropped
*/
uint32_t
sc_audiobuf_truncate(struct sc_audiobuf *buf, uint32_t samples_limit);
static inline uint32_t
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
assert(buf->alloc_size);

View File

@ -33,12 +33,6 @@ scrcpy --no-video --no-audio --keyboard=uhid
scrcpy --no-video --no-audio -K # short version
```
To use AOA instead (over USB only):
```bash
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
```
## Copy-paste

View File

@ -39,7 +39,7 @@ It only works if the device is connected over USB.
See [FAQ](/FAQ.md#otg-issues-on-windows).
## Control only
## Control-only
Note that the purpose of OTG is to control the device without USB debugging
(adb).

View File

@ -48,7 +48,7 @@ public class SurfaceEncoder implements AsyncProcessor {
this.downsizeOnError = downsizeOnError;
}
private void streamCapture() throws IOException, ConfigurationException {
private void streamScreen() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
@ -254,7 +254,7 @@ public class SurfaceEncoder implements AsyncProcessor {
Looper.prepare();
try {
streamCapture();
streamScreen();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {