Compare commits
12 Commits
display_ma
...
atomic.5
Author | SHA1 | Date | |
---|---|---|---|
1029835d75 | |||
6a58891e13 | |||
ec41896c85 | |||
4cd61b5a90 | |||
d2ed4510a7 | |||
604dfd7c6b | |||
af69689ec1 | |||
cbce42336d | |||
c9a4d2b38f | |||
1beec99f82 | |||
5ce8672ebc | |||
3001f8a2d5 |
@ -115,13 +115,12 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
|
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--orientation
|
--orientation|--display-orientation)
|
||||||
--display-orientation)
|
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||||
COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--record-orientation)
|
--record-orientation)
|
||||||
COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur"))
|
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--lock-video-orientation)
|
--lock-video-orientation)
|
||||||
|
@ -34,8 +34,8 @@ src = [
|
|||||||
'src/trait/frame_source.c',
|
'src/trait/frame_source.c',
|
||||||
'src/trait/packet_source.c',
|
'src/trait/packet_source.c',
|
||||||
'src/util/acksync.c',
|
'src/util/acksync.c',
|
||||||
|
'src/util/audiobuf.c',
|
||||||
'src/util/average.c',
|
'src/util/average.c',
|
||||||
'src/util/bytebuf.c',
|
|
||||||
'src/util/file.c',
|
'src/util/file.c',
|
||||||
'src/util/intmap.c',
|
'src/util/intmap.c',
|
||||||
'src/util/intr.c',
|
'src/util/intr.c',
|
||||||
@ -212,9 +212,10 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_binary', [
|
['test_binary', [
|
||||||
'tests/test_binary.c',
|
'tests/test_binary.c',
|
||||||
]],
|
]],
|
||||||
['test_bytebuf', [
|
['test_audiobuf', [
|
||||||
'tests/test_bytebuf.c',
|
'tests/test_audiobuf.c',
|
||||||
'src/util/bytebuf.c',
|
'src/util/audiobuf.c',
|
||||||
|
'src/util/memory.c',
|
||||||
]],
|
]],
|
||||||
['test_cli', [
|
['test_cli', [
|
||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
|
@ -124,7 +124,7 @@ Use USB device (if there is exactly one, like adb -d).
|
|||||||
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
|
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-disable-screensaver"
|
.BI "\-\-disable\-screensaver"
|
||||||
Disable screensaver while scrcpy is running.
|
Disable screensaver while scrcpy is running.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -642,7 +642,11 @@ Enable/disable FPS counter (print frames/second in logs)
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Ctrl+click-and-move
|
.B Ctrl+click-and-move
|
||||||
Pinch-to-zoom from the center of the screen
|
Pinch-to-zoom and rotate from the center of the screen
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Shift+click-and-move
|
||||||
|
Tilt (slide vertically with two fingers)
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Drag & drop APK file
|
.B Drag & drop APK file
|
||||||
|
@ -66,8 +66,7 @@ static void SDLCALL
|
|||||||
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||||
struct sc_audio_player *ap = userdata;
|
struct sc_audio_player *ap = userdata;
|
||||||
|
|
||||||
// This callback is called with the lock used by SDL_AudioDeviceLock(), so
|
// This callback is called with the lock used by SDL_AudioDeviceLock()
|
||||||
// the audiobuf is protected
|
|
||||||
|
|
||||||
assert(len_int > 0);
|
assert(len_int > 0);
|
||||||
size_t len = len_int;
|
size_t len = len_int;
|
||||||
@ -77,8 +76,9 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
|
||||||
if (!ap->played) {
|
if (!played) {
|
||||||
|
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
||||||
// Part of the buffering is handled by inserting initial silence. The
|
// Part of the buffering is handled by inserting initial silence. The
|
||||||
// remaining (margin) last samples will be handled by compensation.
|
// remaining (margin) last samples will be handled by compensation.
|
||||||
uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms
|
uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms
|
||||||
@ -93,10 +93,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t read = MIN(buffered_samples, count);
|
uint32_t read = sc_audiobuf_read(&ap->buf, stream, count);
|
||||||
if (read) {
|
|
||||||
sc_audiobuf_read(&ap->buf, stream, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read < count) {
|
if (read < count) {
|
||||||
uint32_t silence = count - read;
|
uint32_t silence = count - read;
|
||||||
@ -109,13 +106,16 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
silence);
|
silence);
|
||||||
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence));
|
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence));
|
||||||
|
|
||||||
if (ap->received) {
|
bool received = atomic_load_explicit(&ap->received,
|
||||||
|
memory_order_relaxed);
|
||||||
|
if (received) {
|
||||||
// Inserting additional samples immediately increases buffering
|
// Inserting additional samples immediately increases buffering
|
||||||
ap->underflow += silence;
|
atomic_fetch_add_explicit(&ap->underflow, silence,
|
||||||
|
memory_order_relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ap->played = true;
|
atomic_store_explicit(&ap->played, true, memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t *
|
static uint8_t *
|
||||||
@ -162,123 +162,120 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
|
|
||||||
// swr_convert() returns the number of samples which would have been
|
// swr_convert() returns the number of samples which would have been
|
||||||
// written if the buffer was big enough.
|
// written if the buffer was big enough.
|
||||||
uint32_t samples_written = MIN(ret, dst_nb_samples);
|
uint32_t samples = MIN(ret, dst_nb_samples);
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written);
|
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Since this function is the only writer, the current available space is
|
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
|
||||||
// at least the previous available space. In practice, it should almost
|
if (samples > cap) {
|
||||||
// always be possible to write without lock.
|
// Very very unlikely: a single resampled frame should never
|
||||||
bool lockless_write = samples_written <= ap->previous_can_write;
|
// exceed the audio buffer size (or something is very wrong).
|
||||||
if (lockless_write) {
|
// Ignore the first bytes in swr_buf to avoid memory corruption anyway.
|
||||||
sc_audiobuf_prepare_write(&ap->buf, swr_buf, samples_written);
|
//skipped_samples = samples - cap;
|
||||||
|
swr_buf += TO_BYTES(samples - cap);
|
||||||
|
samples = cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LockAudioDevice(ap->device);
|
uint32_t written = sc_audiobuf_write(&ap->buf, swr_buf, samples);
|
||||||
|
if (written < samples) {
|
||||||
|
uint32_t remaining = samples - written;
|
||||||
|
|
||||||
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
// 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);
|
||||||
|
|
||||||
if (lockless_write) {
|
// Retry with the lock
|
||||||
sc_audiobuf_commit_write(&ap->buf, samples_written);
|
written += sc_audiobuf_write(&ap->buf,
|
||||||
} else {
|
swr_buf + TO_BYTES(written),
|
||||||
uint32_t can_write = sc_audiobuf_can_write(&ap->buf);
|
remaining);
|
||||||
if (samples_written > can_write) {
|
if (written < samples) {
|
||||||
// Entering this branch is very unlikely, the audio buffer is
|
remaining = samples - written;
|
||||||
// allocated with a size sufficient to store 1 second more than the
|
// Still insufficient, drop old samples to make space
|
||||||
// target buffering. If this happens, though, we have to skip old
|
uint32_t skipped_samples =
|
||||||
// samples.
|
sc_audiobuf_read(&ap->buf, NULL, remaining);
|
||||||
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
|
assert(skipped_samples == remaining);
|
||||||
if (samples_written > cap) {
|
|
||||||
// Very very unlikely: a single resampled frame should never
|
|
||||||
// exceed the audio buffer size (or something is very wrong).
|
|
||||||
// Ignore the first bytes in swr_buf
|
|
||||||
swr_buf += TO_BYTES(samples_written - cap);
|
|
||||||
// This change in samples_written will impact the
|
|
||||||
// instant_compensation below
|
|
||||||
samples_written = cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(samples_written >= can_write);
|
// Dropping input samples instantly decreases buffering
|
||||||
if (samples_written > can_write) {
|
ap->avg_buffering.avg -= skipped_samples;
|
||||||
uint32_t skip_samples = samples_written - can_write;
|
|
||||||
assert(buffered_samples >= skip_samples);
|
|
||||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
|
||||||
buffered_samples -= skip_samples;
|
|
||||||
if (ap->played) {
|
|
||||||
// Dropping input samples instantly decreases buffering
|
|
||||||
ap->avg_buffering.avg -= skip_samples;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// It should remain exactly the expected size to write the new
|
// Now there is enough space
|
||||||
// samples.
|
uint32_t w = sc_audiobuf_write(&ap->buf,
|
||||||
assert(sc_audiobuf_can_write(&ap->buf) == samples_written);
|
swr_buf + TO_BYTES(written),
|
||||||
|
remaining);
|
||||||
|
assert(w == remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_audiobuf_write(&ap->buf, swr_buf, samples_written);
|
SDL_UnlockAudioDevice(ap->device);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffered_samples += samples_written;
|
uint32_t underflow = 0;
|
||||||
assert(buffered_samples == sc_audiobuf_can_read(&ap->buf));
|
uint32_t max_buffered_samples;
|
||||||
|
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
|
||||||
// Read with lock held, to be used after unlocking
|
|
||||||
bool played = ap->played;
|
|
||||||
uint32_t underflow = ap->underflow;
|
|
||||||
|
|
||||||
if (played) {
|
if (played) {
|
||||||
uint32_t max_buffered_samples = ap->target_buffering
|
underflow = atomic_exchange_explicit(&ap->underflow, 0,
|
||||||
+ 12 * ap->output_buffer
|
memory_order_relaxed);
|
||||||
+ ap->target_buffering / 10;
|
|
||||||
if (buffered_samples > max_buffered_samples) {
|
|
||||||
uint32_t skip_samples = buffered_samples - max_buffered_samples;
|
|
||||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
|
||||||
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
|
||||||
" samples", skip_samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset (the current value was copied to a local variable)
|
max_buffered_samples = ap->target_buffering
|
||||||
ap->underflow = 0;
|
+ 12 * ap->output_buffer
|
||||||
|
+ ap->target_buffering / 10;
|
||||||
} else {
|
} else {
|
||||||
// SDL playback not started yet, do not accumulate more than
|
// SDL playback not started yet, do not accumulate more than
|
||||||
// max_initial_buffering samples, this would cause unnecessary delay
|
// max_initial_buffering samples, this would cause unnecessary delay
|
||||||
// (and glitches to compensate) on start.
|
// (and glitches to compensate) on start.
|
||||||
uint32_t max_initial_buffering = ap->target_buffering
|
max_buffered_samples = ap->target_buffering + 2 * ap->output_buffer;
|
||||||
+ 2 * ap->output_buffer;
|
}
|
||||||
if (buffered_samples > max_initial_buffering) {
|
|
||||||
uint32_t skip_samples = buffered_samples - max_initial_buffering;
|
uint32_t can_read = sc_audiobuf_can_read(&ap->buf);
|
||||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
if (can_read > max_buffered_samples) {
|
||||||
|
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);
|
||||||
|
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||||
|
" samples", skip_samples);
|
||||||
|
}
|
||||||
|
SDL_UnlockAudioDevice(ap->device);
|
||||||
|
|
||||||
|
if (skip_samples) {
|
||||||
|
// The buffer contained too much data, ignore any reported underflow
|
||||||
|
underflow = 0;
|
||||||
|
|
||||||
|
if (played) {
|
||||||
|
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||||
|
" samples", skip_samples);
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
|
} else {
|
||||||
skip_samples);
|
LOGD("[Audio] Playback not started, skipping %" PRIu32
|
||||||
|
" samples", skip_samples);
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
|
atomic_store_explicit(&ap->received, true, memory_order_relaxed);
|
||||||
ap->received = true;
|
|
||||||
|
|
||||||
SDL_UnlockAudioDevice(ap->device);
|
|
||||||
|
|
||||||
if (played) {
|
if (played) {
|
||||||
// Number of samples added (or removed, if negative) for compensation
|
// Number of samples added (or removed, if negative) for compensation
|
||||||
int32_t instant_compensation =
|
int32_t instant_compensation = (int32_t) written - frame->nb_samples;
|
||||||
(int32_t) samples_written - frame->nb_samples;
|
|
||||||
int32_t inserted_silence = (int32_t) underflow;
|
int32_t inserted_silence = (int32_t) underflow;
|
||||||
|
|
||||||
// The compensation must apply instantly, it must not be smoothed
|
// The compensation must apply instantly, it must not be smoothed
|
||||||
ap->avg_buffering.avg += instant_compensation + inserted_silence;
|
ap->avg_buffering.avg += instant_compensation + inserted_silence;
|
||||||
|
|
||||||
|
|
||||||
// However, the buffering level must be smoothed
|
// However, the buffering level must be smoothed
|
||||||
sc_average_push(&ap->avg_buffering, buffered_samples);
|
sc_average_push(&ap->avg_buffering, can_read);
|
||||||
|
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f",
|
LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f",
|
||||||
buffered_samples, sc_average_get(&ap->avg_buffering));
|
buffered_samples, sc_average_get(&ap->avg_buffering));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ap->samples_since_resync += samples_written;
|
ap->samples_since_resync += written;
|
||||||
if (ap->samples_since_resync >= ap->sample_rate) {
|
if (ap->samples_since_resync >= ap->sample_rate) {
|
||||||
// Recompute compensation every second
|
// Recompute compensation every second
|
||||||
ap->samples_since_resync = 0;
|
ap->samples_since_resync = 0;
|
||||||
@ -288,7 +285,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
if (abs(diff) < (int) ap->sample_rate / 1000) {
|
if (abs(diff) < (int) ap->sample_rate / 1000) {
|
||||||
// Do not compensate for less than 1ms, the error is just noise
|
// Do not compensate for less than 1ms, the error is just noise
|
||||||
diff = 0;
|
diff = 0;
|
||||||
} else if (diff < 0 && buffered_samples < ap->target_buffering) {
|
} else if (diff < 0 && can_read < ap->target_buffering) {
|
||||||
// Do not accelerate if the instant buffering level is below
|
// Do not accelerate if the instant buffering level is below
|
||||||
// the average, this would increase underflow
|
// the average, this would increase underflow
|
||||||
diff = 0;
|
diff = 0;
|
||||||
@ -300,8 +297,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
int abs_max_diff = distance / 50;
|
int abs_max_diff = distance / 50;
|
||||||
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
||||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||||
" compensation=%d", ap->target_buffering, avg,
|
" compensation=%d", ap->target_buffering, avg, can_read, diff);
|
||||||
buffered_samples, diff);
|
|
||||||
|
|
||||||
if (diff != ap->compensation) {
|
if (diff != ap->compensation) {
|
||||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||||
@ -413,16 +409,14 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
}
|
}
|
||||||
ap->swr_buf_alloc_size = initial_swr_buf_size;
|
ap->swr_buf_alloc_size = initial_swr_buf_size;
|
||||||
|
|
||||||
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
|
|
||||||
|
|
||||||
// Samples are produced and consumed by blocks, so the buffering must be
|
// Samples are produced and consumed by blocks, so the buffering must be
|
||||||
// smoothed to get a relatively stable value.
|
// smoothed to get a relatively stable value.
|
||||||
sc_average_init(&ap->avg_buffering, 32);
|
sc_average_init(&ap->avg_buffering, 32);
|
||||||
ap->samples_since_resync = 0;
|
ap->samples_since_resync = 0;
|
||||||
|
|
||||||
ap->received = false;
|
ap->received = false;
|
||||||
ap->played = false;
|
atomic_init(&ap->played, false);
|
||||||
ap->underflow = 0;
|
atomic_init(&ap->underflow, 0);
|
||||||
ap->compensation = 0;
|
ap->compensation = 0;
|
||||||
|
|
||||||
// The thread calling open() is the thread calling push(), which fills the
|
// The thread calling open() is the thread calling push(), which fills the
|
||||||
|
@ -32,7 +32,7 @@ struct sc_audio_player {
|
|||||||
uint16_t output_buffer;
|
uint16_t output_buffer;
|
||||||
|
|
||||||
// Audio buffer to communicate between the receiver and the SDL audio
|
// Audio buffer to communicate between the receiver and the SDL audio
|
||||||
// callback (protected by SDL_AudioDeviceLock())
|
// callback
|
||||||
struct sc_audiobuf buf;
|
struct sc_audiobuf buf;
|
||||||
|
|
||||||
// The previous empty space in the buffer (only used by the receiver
|
// The previous empty space in the buffer (only used by the receiver
|
||||||
@ -61,19 +61,16 @@ struct sc_audio_player {
|
|||||||
uint32_t samples_since_resync;
|
uint32_t samples_since_resync;
|
||||||
|
|
||||||
// Number of silence samples inserted since the last received packet
|
// Number of silence samples inserted since the last received packet
|
||||||
// (protected by SDL_AudioDeviceLock())
|
atomic_uint_least32_t underflow;
|
||||||
uint32_t underflow;
|
|
||||||
|
|
||||||
// Current applied compensation value (only used by the receiver thread)
|
// Current applied compensation value (only used by the receiver thread)
|
||||||
int compensation;
|
int compensation;
|
||||||
|
|
||||||
// Set to true the first time a sample is received (protected by
|
// Set to true the first time a sample is received
|
||||||
// SDL_AudioDeviceLock())
|
atomic_bool received;
|
||||||
bool received;
|
|
||||||
|
|
||||||
// Set to true the first time the SDL callback is called (protected by
|
// Set to true the first time the SDL callback is called
|
||||||
// SDL_AudioDeviceLock())
|
atomic_bool played;
|
||||||
bool played;
|
|
||||||
|
|
||||||
const struct sc_audio_player_callbacks *cbs;
|
const struct sc_audio_player_callbacks *cbs;
|
||||||
void *cbs_userdata;
|
void *cbs_userdata;
|
||||||
|
@ -947,7 +947,11 @@ static const struct sc_shortcut shortcuts[] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortcuts = { "Ctrl+click-and-move" },
|
.shortcuts = { "Ctrl+click-and-move" },
|
||||||
.text = "Pinch-to-zoom from the center of the screen",
|
.text = "Pinch-to-zoom and rotate from the center of the screen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.shortcuts = { "Shift+click-and-move" },
|
||||||
|
.text = "Tilt (slide vertically with two fingers)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortcuts = { "Drag & drop APK file" },
|
.shortcuts = { "Drag & drop APK file" },
|
||||||
|
@ -76,6 +76,8 @@ sc_input_manager_init(struct sc_input_manager *im,
|
|||||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||||
|
|
||||||
im->vfinger_down = false;
|
im->vfinger_down = false;
|
||||||
|
im->vfinger_invert_x = false;
|
||||||
|
im->vfinger_invert_y = false;
|
||||||
|
|
||||||
im->last_keycode = SDLK_UNKNOWN;
|
im->last_keycode = SDLK_UNKNOWN;
|
||||||
im->last_mod = 0;
|
im->last_mod = 0;
|
||||||
@ -347,9 +349,14 @@ simulate_virtual_finger(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_point
|
static struct sc_point
|
||||||
inverse_point(struct sc_point point, struct sc_size size) {
|
inverse_point(struct sc_point point, struct sc_size size,
|
||||||
point.x = size.width - point.x;
|
bool invert_x, bool invert_y) {
|
||||||
point.y = size.height - point.y;
|
if (invert_x) {
|
||||||
|
point.x = size.width - point.x;
|
||||||
|
}
|
||||||
|
if (invert_y) {
|
||||||
|
point.y = size.height - point.y;
|
||||||
|
}
|
||||||
return point;
|
return point;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,7 +612,9 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
|||||||
struct sc_point mouse =
|
struct sc_point mouse =
|
||||||
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
|
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
|
||||||
|
im->vfinger_invert_x,
|
||||||
|
im->vfinger_invert_y);
|
||||||
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -726,7 +735,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pinch-to-zoom simulation.
|
// Pinch-to-zoom, rotate and tilt simulation.
|
||||||
//
|
//
|
||||||
// If Ctrl is hold when the left-click button is pressed, then
|
// If Ctrl is hold when the left-click button is pressed, then
|
||||||
// pinch-to-zoom mode is enabled: on every mouse event until the left-click
|
// pinch-to-zoom mode is enabled: on every mouse event until the left-click
|
||||||
@ -735,14 +744,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
//
|
//
|
||||||
// In other words, the center of the rotation/scaling is the center of the
|
// In other words, the center of the rotation/scaling is the center of the
|
||||||
// screen.
|
// screen.
|
||||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
//
|
||||||
|
// To simulate a tilt gesture (a vertical slide with two fingers), Shift
|
||||||
|
// can be used instead of Ctrl. The "virtual finger" has a position
|
||||||
|
// inverted with respect to the vertical axis of symmetry in the middle of
|
||||||
|
// the screen.
|
||||||
|
const SDL_Keymod keymod = SDL_GetModState();
|
||||||
|
const bool ctrl_pressed = keymod & KMOD_CTRL;
|
||||||
|
const bool shift_pressed = keymod & KMOD_SHIFT;
|
||||||
if (event->button == SDL_BUTTON_LEFT &&
|
if (event->button == SDL_BUTTON_LEFT &&
|
||||||
((down && !im->vfinger_down && CTRL_PRESSED) ||
|
((down && !im->vfinger_down &&
|
||||||
|
((ctrl_pressed && !shift_pressed) ||
|
||||||
|
(!ctrl_pressed && shift_pressed))) ||
|
||||||
(!down && im->vfinger_down))) {
|
(!down && im->vfinger_down))) {
|
||||||
struct sc_point mouse =
|
struct sc_point mouse =
|
||||||
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
|
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
if (down) {
|
||||||
|
im->vfinger_invert_x = ctrl_pressed || shift_pressed;
|
||||||
|
im->vfinger_invert_y = ctrl_pressed;
|
||||||
|
}
|
||||||
|
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
|
||||||
|
im->vfinger_invert_x,
|
||||||
|
im->vfinger_invert_y);
|
||||||
enum android_motionevent_action action = down
|
enum android_motionevent_action action = down
|
||||||
? AMOTION_EVENT_ACTION_DOWN
|
? AMOTION_EVENT_ACTION_DOWN
|
||||||
: AMOTION_EVENT_ACTION_UP;
|
: AMOTION_EVENT_ACTION_UP;
|
||||||
|
@ -32,6 +32,8 @@ struct sc_input_manager {
|
|||||||
} sdl_shortcut_mods;
|
} sdl_shortcut_mods;
|
||||||
|
|
||||||
bool vfinger_down;
|
bool vfinger_down;
|
||||||
|
bool vfinger_invert_x;
|
||||||
|
bool vfinger_invert_y;
|
||||||
|
|
||||||
// Tracks the number of identical consecutive shortcut key down events.
|
// Tracks the number of identical consecutive shortcut key down events.
|
||||||
// Not to be confused with event->repeat, which counts the number of
|
// Not to be confused with event->repeat, which counts the number of
|
||||||
|
109
app/src/util/audiobuf.c
Normal file
109
app/src/util/audiobuf.c
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#include "audiobuf.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <util/log.h>
|
||||||
|
#include <util/memory.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
||||||
|
uint32_t capacity) {
|
||||||
|
assert(sample_size);
|
||||||
|
assert(capacity);
|
||||||
|
|
||||||
|
// The actual capacity is (alloc_size - 1) so that head == tail is
|
||||||
|
// non-ambiguous
|
||||||
|
buf->alloc_size = capacity + 1;
|
||||||
|
buf->data = sc_allocarray(buf->alloc_size, sample_size);
|
||||||
|
if (!buf->data) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->sample_size = sample_size;
|
||||||
|
atomic_init(&buf->head, 0);
|
||||||
|
atomic_init(&buf->tail, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
|
||||||
|
free(buf->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
sc_audiobuf_read(struct sc_audiobuf *buf, void *to, uint32_t samples_count) {
|
||||||
|
assert(samples_count);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||||
|
if (samples_count > can_read) {
|
||||||
|
samples_count = can_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
uint32_t right_limit = tail < head ? head : buf->alloc_size;
|
||||||
|
uint32_t right_count = right_limit - 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t new_tail = (tail + samples_count) % buf->alloc_size;
|
||||||
|
atomic_store_explicit(&buf->tail, new_tail, memory_order_release);
|
||||||
|
|
||||||
|
return samples_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
|
memcpy(buf->data + (head * buf->sample_size),
|
||||||
|
from,
|
||||||
|
right_count * buf->sample_size);
|
||||||
|
|
||||||
|
if (samples_count > right_count) {
|
||||||
|
uint32_t left_count = samples_count - right_count;
|
||||||
|
memcpy(buf->data,
|
||||||
|
from + (right_count * buf->sample_size),
|
||||||
|
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;
|
||||||
|
}
|
@ -3,19 +3,25 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "util/bytebuf.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around bytebuf to read and write samples
|
* Wrapper around bytebuf to read and write samples
|
||||||
*
|
*
|
||||||
* Each sample takes sample_size bytes.
|
* Each sample takes sample_size bytes.
|
||||||
*/
|
*/
|
||||||
struct sc_audiobuf {
|
struct sc_audiobuf {
|
||||||
struct sc_bytebuf buf;
|
void *data;
|
||||||
size_t sample_size;
|
uint32_t sample_size;
|
||||||
|
uint32_t alloc_size; // in samples
|
||||||
|
|
||||||
|
atomic_uint_least32_t head; // writer cursor, in samples
|
||||||
|
atomic_uint_least32_t tail; // reader cursor, in samples
|
||||||
|
// empty: tail == head
|
||||||
|
// full: ((tail + 1) % alloc_size) == head
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline uint32_t
|
static inline uint32_t
|
||||||
@ -29,66 +35,31 @@ sc_audiobuf_to_bytes(struct sc_audiobuf *buf, uint32_t samples) {
|
|||||||
return samples * buf->sample_size;
|
return samples * buf->sample_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
bool
|
||||||
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
||||||
uint32_t capacity) {
|
uint32_t capacity);
|
||||||
buf->sample_size = sample_size;
|
|
||||||
return sc_bytebuf_init(&buf->buf, capacity * sample_size + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
void
|
||||||
sc_audiobuf_read(struct sc_audiobuf *buf, uint8_t *to, uint32_t samples) {
|
sc_audiobuf_destroy(struct sc_audiobuf *buf);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_read(&buf->buf, to, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
uint32_t
|
||||||
sc_audiobuf_skip(struct sc_audiobuf *buf, uint32_t samples) {
|
sc_audiobuf_read(struct sc_audiobuf *buf, void *to, uint32_t samples_count);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_skip(&buf->buf, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
uint32_t
|
||||||
sc_audiobuf_write(struct sc_audiobuf *buf, const uint8_t *from,
|
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
|
||||||
uint32_t samples) {
|
uint32_t samples_count);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_write(&buf->buf, from, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline uint32_t
|
||||||
sc_audiobuf_prepare_write(struct sc_audiobuf *buf, const uint8_t *from,
|
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
|
||||||
uint32_t samples) {
|
assert(buf->alloc_size);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
return buf->alloc_size - 1;
|
||||||
sc_bytebuf_prepare_write(&buf->buf, from, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_audiobuf_commit_write(struct sc_audiobuf *buf, uint32_t samples) {
|
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_commit_write(&buf->buf, bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint32_t
|
static inline uint32_t
|
||||||
sc_audiobuf_can_read(struct sc_audiobuf *buf) {
|
sc_audiobuf_can_read(struct sc_audiobuf *buf) {
|
||||||
size_t bytes = sc_bytebuf_can_read(&buf->buf);
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||||
return sc_audiobuf_to_samples(buf, bytes);
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||||
}
|
return (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
sc_audiobuf_can_write(struct sc_audiobuf *buf) {
|
|
||||||
size_t bytes = sc_bytebuf_can_write(&buf->buf);
|
|
||||||
return sc_audiobuf_to_samples(buf, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t
|
|
||||||
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
|
|
||||||
size_t bytes = sc_bytebuf_capacity(&buf->buf);
|
|
||||||
return sc_audiobuf_to_samples(buf, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
|
|
||||||
sc_bytebuf_destroy(&buf->buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
#include "bytebuf.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) {
|
|
||||||
assert(alloc_size);
|
|
||||||
buf->data = malloc(alloc_size);
|
|
||||||
if (!buf->data) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->alloc_size = alloc_size;
|
|
||||||
buf->head = 0;
|
|
||||||
buf->tail = 0;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_destroy(struct sc_bytebuf *buf) {
|
|
||||||
free(buf->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_read(buf));
|
|
||||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
|
||||||
|
|
||||||
size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size;
|
|
||||||
size_t right_len = right_limit - buf->tail;
|
|
||||||
if (len < right_len) {
|
|
||||||
right_len = len;
|
|
||||||
}
|
|
||||||
memcpy(to, buf->data + buf->tail, right_len);
|
|
||||||
|
|
||||||
if (len > right_len) {
|
|
||||||
memcpy(to + right_len, buf->data, len - right_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_read(buf));
|
|
||||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
|
||||||
|
|
||||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_bytebuf_write_step0(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len) {
|
|
||||||
size_t right_len = buf->alloc_size - buf->head;
|
|
||||||
if (len < right_len) {
|
|
||||||
right_len = len;
|
|
||||||
}
|
|
||||||
memcpy(buf->data + buf->head, from, right_len);
|
|
||||||
|
|
||||||
if (len > right_len) {
|
|
||||||
memcpy(buf->data, from + right_len, len - right_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
buf->head = (buf->head + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_write(buf));
|
|
||||||
|
|
||||||
sc_bytebuf_write_step0(buf, from, len);
|
|
||||||
sc_bytebuf_write_step1(buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len) {
|
|
||||||
// *This function MUST NOT access buf->tail (even in assert()).*
|
|
||||||
// The purpose of this function is to allow a reader and a writer to access
|
|
||||||
// different parts of the buffer in parallel simultaneously. It is intended
|
|
||||||
// to be called without lock (only sc_bytebuf_commit_write() is intended to
|
|
||||||
// be called with lock held).
|
|
||||||
|
|
||||||
assert(len < buf->alloc_size - 1);
|
|
||||||
sc_bytebuf_write_step0(buf, from, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
assert(len <= sc_bytebuf_can_write(buf));
|
|
||||||
sc_bytebuf_write_step1(buf, len);
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
#ifndef SC_BYTEBUF_H
|
|
||||||
#define SC_BYTEBUF_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
struct sc_bytebuf {
|
|
||||||
uint8_t *data;
|
|
||||||
// The actual capacity is (allocated - 1) so that head == tail is
|
|
||||||
// non-ambiguous
|
|
||||||
size_t alloc_size;
|
|
||||||
size_t head; // writter cursor
|
|
||||||
size_t tail; // reader cursor
|
|
||||||
// empty: tail == head
|
|
||||||
// full: ((tail + 1) % alloc_size) == head
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy from the bytebuf to a user-provided array
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_read_available() (it is an
|
|
||||||
* error to attempt to read more bytes than available).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->head.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop len bytes from the buffer
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_read_available() (it is an
|
|
||||||
* error to attempt to skip more bytes than available).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->head.
|
|
||||||
*
|
|
||||||
* It is equivalent to call sc_bytebuf_read() to some array and discard the
|
|
||||||
* array (but this function is more efficient since there is no copy).
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the user-provided array to the bytebuf
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_write_available() (it is an
|
|
||||||
* error to write more bytes than the remaining available space).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->tail.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the user-provided array to the bytebuf, but do not advance the cursor
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_write_available() (it is an
|
|
||||||
* error to write more bytes than the remaining available space).
|
|
||||||
*
|
|
||||||
* After this function is called, the write must be committed with
|
|
||||||
* sc_bytebuf_commit_write().
|
|
||||||
*
|
|
||||||
* The purpose of this mechanism is to acquire a lock only to commit the write,
|
|
||||||
* but not to perform the actual copy.
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to access buf->tail.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit a prepared write
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes which can be read
|
|
||||||
*
|
|
||||||
* It is an error to read more bytes than available.
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_can_read(struct sc_bytebuf *buf) {
|
|
||||||
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes which can be written
|
|
||||||
*
|
|
||||||
* It is an error to write more bytes than available.
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_can_write(struct sc_bytebuf *buf) {
|
|
||||||
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the actual capacity of the buffer (can_read() + can_write())
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_capacity(struct sc_bytebuf *buf) {
|
|
||||||
return buf->alloc_size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_destroy(struct sc_bytebuf *buf);
|
|
||||||
|
|
||||||
#endif
|
|
128
app/tests/test_audiobuf.c
Normal file
128
app/tests/test_audiobuf.c
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/audiobuf.h"
|
||||||
|
|
||||||
|
static void test_audiobuf_simple(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 5);
|
||||||
|
assert(w == 5);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 4);
|
||||||
|
assert(r == 4);
|
||||||
|
assert(!memcmp(data, samples, 16));
|
||||||
|
|
||||||
|
uint32_t samples2[] = {6, 7, 8};
|
||||||
|
w = sc_audiobuf_write(&buf, samples2, 3);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
uint32_t single = 9;
|
||||||
|
w = sc_audiobuf_write(&buf, &single, 1);
|
||||||
|
assert(w == 1);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, &data[4], 8);
|
||||||
|
assert(r == 5);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
|
assert(!memcmp(data, expected, 36));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_audiobuf_boundaries(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 9);
|
||||||
|
assert(r == 9);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected, 36));
|
||||||
|
|
||||||
|
uint32_t samples2[] = {7, 8, 9, 10, 11};
|
||||||
|
w = sc_audiobuf_write(&buf, samples2, 5);
|
||||||
|
assert(w == 5);
|
||||||
|
|
||||||
|
uint32_t single = 12;
|
||||||
|
w = sc_audiobuf_write(&buf, &single, 1);
|
||||||
|
assert(w == 1);
|
||||||
|
|
||||||
|
w = sc_audiobuf_read(&buf, NULL, 3);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
assert(sc_audiobuf_can_read(&buf) == 12);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, data, 12);
|
||||||
|
assert(r == 12);
|
||||||
|
|
||||||
|
uint32_t expected2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||||
|
assert(!memcmp(data, expected2, 48));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_audiobuf_partial_read_write(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[15];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 10);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 4);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 0);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 3);
|
||||||
|
assert(r == 3);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected, 12));
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, data, 15);
|
||||||
|
assert(r == 10);
|
||||||
|
uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected2, 12));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_audiobuf_simple();
|
||||||
|
test_audiobuf_boundaries();
|
||||||
|
test_audiobuf_partial_read_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,126 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/bytebuf.h"
|
|
||||||
|
|
||||||
static void test_bytebuf_simple(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 5);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 4);
|
|
||||||
assert(!strncmp((char *) data, "hell", 4));
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 7);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 8);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, &data[4], 8);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_bytebuf_boundaries(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 6);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 18);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 9);
|
|
||||||
assert(!strncmp((char *) data, "hello hel", 9));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 14);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 15);
|
|
||||||
|
|
||||||
sc_bytebuf_skip(&buf, 3);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 12);
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_bytebuf_two_steps_write(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 6);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12); // write not committed yet
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 9);
|
|
||||||
assert(!strncmp((char *) data, "hello hel", 3));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 3);
|
|
||||||
|
|
||||||
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9);
|
|
||||||
|
|
||||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9); // write not committed yet
|
|
||||||
|
|
||||||
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 14);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 15);
|
|
||||||
|
|
||||||
sc_bytebuf_skip(&buf, 3);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 12);
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_bytebuf_simple();
|
|
||||||
test_bytebuf_boundaries();
|
|
||||||
test_bytebuf_two_steps_write();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -85,7 +85,7 @@ way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
|||||||
To disable automatic clipboard synchronization, use
|
To disable automatic clipboard synchronization, use
|
||||||
`--no-clipboard-autosync`.
|
`--no-clipboard-autosync`.
|
||||||
|
|
||||||
## Pinch-to-zoom
|
## Pinch-to-zoom, rotate and tilt simulation
|
||||||
|
|
||||||
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
||||||
|
|
||||||
@ -93,8 +93,12 @@ More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
|
|||||||
Until the left-click button is released, all mouse movements scale and rotate
|
Until the left-click button is released, all mouse movements scale and rotate
|
||||||
the content (if supported by the app) relative to the center of the screen.
|
the content (if supported by the app) relative to the center of the screen.
|
||||||
|
|
||||||
|
To simulate a tilt gesture: <kbd>Shift</kbd>+_click-and-move-up-or-down_.
|
||||||
|
|
||||||
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
|
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
|
||||||
at a location inverted through the center of the screen.
|
at a location inverted through the center of the screen. When pressing
|
||||||
|
<kbd>Ctrl</kbd> the x and y coordinates are inverted. Using <kbd>Shift</kbd>
|
||||||
|
only inverts x.
|
||||||
|
|
||||||
|
|
||||||
## Key repeat
|
## Key repeat
|
||||||
|
@ -49,7 +49,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
| Pinch-to-zoom/rotate | <kbd>Ctrl</kbd>+_click-and-move_
|
||||||
|
| Tilt (slide vertically with 2 fingers) | <kbd>Shift</kbd>+_click-and-move_
|
||||||
| Drag & drop APK file | Install APK from computer
|
| Drag & drop APK file | Install APK from computer
|
||||||
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)
|
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)
|
||||||
|
|
||||||
|
@ -153,13 +153,14 @@ public final class AudioCapture {
|
|||||||
previousRecorderTimestamp = timestamp.nanoTime;
|
previousRecorderTimestamp = timestamp.nanoTime;
|
||||||
} else {
|
} else {
|
||||||
if (nextPts == 0) {
|
if (nextPts == 0) {
|
||||||
Ln.w("Could not get any audio timestamp");
|
Ln.w("Could not get initial audio timestamp");
|
||||||
|
nextPts = System.nanoTime() / 1000;
|
||||||
}
|
}
|
||||||
// compute from previous timestamp and packet size
|
// compute from previous timestamp and packet size
|
||||||
pts = nextPts;
|
pts = nextPts;
|
||||||
}
|
}
|
||||||
|
|
||||||
long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
|
long durationUs = r * 1000000L / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
|
||||||
nextPts = pts + durationUs;
|
nextPts = pts + durationUs;
|
||||||
|
|
||||||
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) {
|
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) {
|
||||||
|
@ -187,5 +187,7 @@ public final class CleanUp {
|
|||||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ public final class Device {
|
|||||||
void onClipboardTextChanged(String text);
|
void onClipboardTextChanged(String text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Size deviceSize;
|
|
||||||
private final Rect crop;
|
private final Rect crop;
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private final int lockVideoOrientation;
|
private final int lockVideoOrientation;
|
||||||
|
|
||||||
|
private Size deviceSize;
|
||||||
private ScreenInfo screenInfo;
|
private ScreenInfo screenInfo;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
private FoldListener foldListener;
|
private FoldListener foldListener;
|
||||||
@ -116,8 +116,8 @@ public final class Device {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
|
deviceSize = displayInfo.getSize();
|
||||||
options.getMaxSize(), options.getLockVideoOrientation());
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
|
||||||
// notify
|
// notify
|
||||||
if (foldListener != null) {
|
if (foldListener != null) {
|
||||||
foldListener.onFoldChanged(displayId, folded);
|
foldListener.onFoldChanged(displayId, folded);
|
||||||
|
@ -285,16 +285,28 @@ public final class Workarounds {
|
|||||||
Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel");
|
Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel");
|
||||||
Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState);
|
Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState);
|
||||||
|
|
||||||
// private native int native_setup(Object audiorecordThis,
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
// Object /*AudioAttributes*/ attributes,
|
// private native int native_setup(Object audiorecordThis,
|
||||||
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
|
// Object /*AudioAttributes*/ attributes,
|
||||||
// int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
|
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
|
||||||
// long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
|
// int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
|
||||||
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class,
|
// long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
|
||||||
int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class);
|
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class,
|
||||||
nativeSetupMethod.setAccessible(true);
|
int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class);
|
||||||
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes, sampleRateArray,
|
nativeSetupMethod.setAccessible(true);
|
||||||
channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, attributionSourceParcel, 0L, 0);
|
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes,
|
||||||
|
sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session,
|
||||||
|
attributionSourceParcel, 0L, 0);
|
||||||
|
} else {
|
||||||
|
// Android 14 added a new int parameter "halInputFlags"
|
||||||
|
// <https://github.com/aosp-mirror/platform_frameworks_base/commit/f6135d75db79b1d48fad3a3b3080d37be20a2313>
|
||||||
|
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class,
|
||||||
|
int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class, int.class);
|
||||||
|
nativeSetupMethod.setAccessible(true);
|
||||||
|
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes,
|
||||||
|
sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session,
|
||||||
|
attributionSourceParcel, 0L, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +41,14 @@ public final class ClipboardManager {
|
|||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
|
||||||
getMethodVersion = 2;
|
getMethodVersion = 2;
|
||||||
} catch (NoSuchMethodException e3) {
|
} catch (NoSuchMethodException e3) {
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
|
try {
|
||||||
getMethodVersion = 3;
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
|
||||||
|
getMethodVersion = 3;
|
||||||
|
} catch (NoSuchMethodException e4) {
|
||||||
|
getPrimaryClipMethod = manager.getClass()
|
||||||
|
.getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class);
|
||||||
|
getMethodVersion = 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,8 +93,11 @@ public final class ClipboardManager {
|
|||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
||||||
case 2:
|
case 2:
|
||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
|
||||||
default:
|
case 3:
|
||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
|
||||||
|
default:
|
||||||
|
// The last boolean parameter is "userOperate"
|
||||||
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user