Compare commits

..

7 Commits

Author SHA1 Message Date
Romain Vimont
1ef61c7037 Remove useless skip feature in audio buffer
After the refactor from the previous commit, sc_audiobuf_read() is never
called with a NULL destination (used to skip samples).
2024-05-27 14:04:42 +02:00
Romain Vimont
a5a3b518a0 Never lock in audio player
PR #4752 removed the need for locks except for corner cases. Now replace
the remaining lock sections by atomics.

Refs #4572 <https://github.com/Genymobile/scrcpy/pull/4572>
2024-05-27 14:04:42 +02:00
Romain Vimont
09e8c20168 Rename streamScreen() to streamCapture()
The capture source may be either the screen or the camera.
2024-05-14 08:23:57 +02:00
Romain Vimont
da484b7ab9 Reject recording with control only
If video and audio are disabled, there is nothing to record.
2024-05-12 10:44:27 +02:00
Romain Vimont
063a8339ed 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:12:00 +02:00
Romain Vimont
b5c8de08e0 Update documentation for --no-window
PR #4868 <https://github.com/Genymobile/scrcpy/pull/4868>
2024-05-11 17:12:00 +02:00
Romain Vimont
45fe6b602b Add scrcpy window without video playback
Add the possibility to solely control the device without screen
mirroring:

    scrcpy --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 17:06:16 +02:00
7 changed files with 86 additions and 63 deletions

View File

@@ -66,8 +66,6 @@ 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);
@@ -181,29 +179,19 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
if (written < samples) {
uint32_t remaining = samples - written;
// 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);
assert(remaining <= cap);
skipped_samples = sc_audiobuf_truncate(&ap->buf, cap - remaining);
// 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);
LOGW("Audio buffer full, %" PRIu32 " samples dropped", skipped_samples);
// 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;
SDL_UnlockAudioDevice(ap->device);
written = samples;
}
uint32_t underflow = 0;
@@ -225,30 +213,22 @@ 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 skip_samples = 0;
uint32_t skipped = sc_audiobuf_truncate(&ap->buf, max_buffered_samples);
assert(skipped);
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);
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", skip_samples);
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
skipped);
#endif
}
}
skipped_samples += skipped;
can_read = sc_audiobuf_can_read(&ap->buf);
}
atomic_store_explicit(&ap->received, true, memory_order_relaxed);
@@ -408,7 +388,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 locking.
// without dropping samples.
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,6 +2738,11 @@ 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);
// 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 tail cursor may be updated by the writer thread to drop samples
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
// 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,21 +50,19 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
samples_count = can_read;
}
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);
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;
@@ -110,3 +108,27 @@ 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,6 +49,16 @@ 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,6 +33,12 @@ 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 streamScreen() throws IOException, ConfigurationException {
private void streamCapture() 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 {
streamScreen();
streamCapture();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {