Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b7a600bdd | ||
|
|
fea9ad9bf9 | ||
|
|
6a43a68800 | ||
|
|
f42a320690 | ||
|
|
711f7b7693 | ||
|
|
03ea4b6918 | ||
|
|
7f0feea155 | ||
|
|
c101ec598f | ||
|
|
ff3ec5dc5a | ||
|
|
3e06105f59 | ||
|
|
89c638282f | ||
|
|
06f68a1570 | ||
|
|
fe7207da49 | ||
|
|
19c13651b3 | ||
|
|
a1f39b0227 | ||
|
|
d9b2488880 | ||
|
|
b5304dc9d2 | ||
|
|
b26af71bfb | ||
|
|
7de4b8bea7 | ||
|
|
0ebbe4b268 | ||
|
|
bf8c6f9050 | ||
|
|
8fd61c1d93 | ||
|
|
e318aa1cb1 | ||
|
|
f3998c280b | ||
|
|
f6ae6865ed | ||
|
|
f0f277ba71 | ||
|
|
35689a73ab | ||
|
|
84751937f6 | ||
|
|
9896cf0f9a | ||
|
|
3c18cfb23b | ||
|
|
e9fc35e9b1 | ||
|
|
ffe3b87f3c | ||
|
|
5d8f891153 | ||
|
|
c2b3985f80 | ||
|
|
d81804359e | ||
|
|
96385b531c | ||
|
|
954c774894 | ||
|
|
1bdf0f1594 | ||
|
|
d358139656 | ||
|
|
f816558e7a | ||
|
|
c0fe77d0b4 | ||
|
|
74e380c8e0 | ||
|
|
67d9396db1 | ||
|
|
9ae632ca2f | ||
|
|
092b683402 | ||
|
|
b1ccbbea55 | ||
|
|
bcd51211f2 | ||
|
|
de2b17873a | ||
|
|
38e317f3b7 | ||
|
|
5ba37b0522 | ||
|
|
b6b178f6cf | ||
|
|
4d83cc3ec6 | ||
|
|
26e7d495d4 | ||
|
|
0dfa43a6a1 | ||
|
|
484c3dedc0 | ||
|
|
dfb3347633 | ||
|
|
9a89e21527 | ||
|
|
fd4fffa436 | ||
|
|
c54a087e44 | ||
|
|
f3da281ce8 | ||
|
|
8fc3e20cd7 | ||
|
|
0026ea4cd9 | ||
|
|
fa5e40c6b8 | ||
|
|
42b12a0ee8 | ||
|
|
e04add64d8 | ||
|
|
2ee2660ef7 | ||
|
|
2be2376cf7 | ||
|
|
4fa52d7983 | ||
|
|
369a56fcd8 | ||
|
|
8d319cbd67 | ||
|
|
72aa2ebd03 | ||
|
|
792d2f2c66 | ||
|
|
bebff7989f | ||
|
|
4f09051835 | ||
|
|
740a57ec4f | ||
|
|
f48963d450 | ||
|
|
3c3e743726 | ||
|
|
c0b26a90cb | ||
|
|
050fe55b6f | ||
|
|
4182b29a91 | ||
|
|
6c29c2a3c7 | ||
|
|
3b4727761e | ||
|
|
0c3ae37d86 | ||
|
|
fe0dd73835 | ||
|
|
51091f8465 | ||
|
|
93cc1fcff0 | ||
|
|
0f818e5d87 | ||
|
|
113eb864da | ||
|
|
72811e7b1f | ||
|
|
51365ca8d4 | ||
|
|
db7f27deb1 | ||
|
|
54e5cf1e24 | ||
|
|
5d67a9e7f3 | ||
|
|
97890db385 | ||
|
|
1d12fc2409 | ||
|
|
f2b23fc977 | ||
|
|
1b88ae4db0 | ||
|
|
67b22517f4 | ||
|
|
0e44b3158b | ||
|
|
6bf861ec2c | ||
|
|
251ea6dfff | ||
|
|
976978abe6 | ||
|
|
fd463a0220 | ||
|
|
2731c60896 | ||
|
|
389dd77b50 | ||
|
|
3c3c07db05 |
4
BUILD.md
4
BUILD.md
@@ -15,7 +15,7 @@ First, you need to install the required packages:
|
||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev
|
||||
libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
|
||||
```
|
||||
|
||||
Then clone the repo and execute the installation script
|
||||
@@ -94,7 +94,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
|
||||
# client build dependencies
|
||||
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libusb-1.0-0-dev
|
||||
libswresample-dev libusb-1.0-0-dev
|
||||
|
||||
# server build dependencies
|
||||
sudo apt install openjdk-11-jdk
|
||||
|
||||
@@ -11,6 +11,7 @@ src = [
|
||||
'src/control_msg.c',
|
||||
'src/controller.c',
|
||||
'src/decoder.c',
|
||||
'src/delay_buffer.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/icon.c',
|
||||
@@ -29,7 +30,8 @@ src = [
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/version.c',
|
||||
'src/video_buffer.c',
|
||||
'src/trait/frame_source.c',
|
||||
'src/trait/packet_source.c',
|
||||
'src/util/acksync.c',
|
||||
'src/util/average.c',
|
||||
'src/util/bytebuf.c',
|
||||
@@ -37,6 +39,7 @@ src = [
|
||||
'src/util/intmap.c',
|
||||
'src/util/intr.c',
|
||||
'src/util/log.c',
|
||||
'src/util/memory.c',
|
||||
'src/util/net.c',
|
||||
'src/util/net_intr.c',
|
||||
'src/util/process.c',
|
||||
@@ -180,6 +183,7 @@ check_functions = [
|
||||
'vasprintf',
|
||||
'nrand48',
|
||||
'jrand48',
|
||||
'reallocarray',
|
||||
]
|
||||
|
||||
foreach f : check_functions
|
||||
@@ -270,9 +274,6 @@ if get_option('buildtype') == 'debug'
|
||||
'tests/test_bytebuf.c',
|
||||
'src/util/bytebuf.c',
|
||||
]],
|
||||
['test_cbuf', [
|
||||
'tests/test_cbuf.c',
|
||||
]],
|
||||
['test_cli', [
|
||||
'tests/test_cli.c',
|
||||
'src/cli.c',
|
||||
@@ -297,9 +298,6 @@ if get_option('buildtype') == 'debug'
|
||||
'tests/test_device_msg_deserialize.c',
|
||||
'src/device_msg.c',
|
||||
]],
|
||||
['test_queue', [
|
||||
'tests/test_queue.c',
|
||||
]],
|
||||
['test_strbuf', [
|
||||
'tests/test_strbuf.c',
|
||||
'src/util/strbuf.c',
|
||||
@@ -309,6 +307,10 @@ if get_option('buildtype') == 'debug'
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_vecdeque', [
|
||||
'tests/test_vecdeque.c',
|
||||
'src/util/memory.c',
|
||||
]],
|
||||
['test_vector', [
|
||||
'tests/test_vector.c',
|
||||
]],
|
||||
|
||||
10
app/scrcpy.1
10
app/scrcpy.1
@@ -25,6 +25,12 @@ Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are
|
||||
|
||||
Default is 196K (196000).
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-buffer ms
|
||||
Add a buffering delay (in milliseconds) before playing audio. This increases latency to compensate for jitter.
|
||||
|
||||
Default is 0 (no buffering).
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-codec " name
|
||||
Select an audio codec (opus or aac).
|
||||
@@ -262,6 +268,10 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
|
||||
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
||||
.UE
|
||||
|
||||
.TP
|
||||
.B \-\-require\-audio
|
||||
By default, scrcpy mirrors only the video if audio capture fails on the device. This flag makes scrcpy fail if audio is enabled but does not work.
|
||||
|
||||
.TP
|
||||
.BI "\-\-rotation " value
|
||||
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
|
||||
//#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
|
||||
|
||||
/** Downcast frame_sink to sc_audio_player */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
|
||||
@@ -18,14 +18,22 @@
|
||||
// This value is directly use for compensation.
|
||||
#define SC_TARGET_BUFFERED_SAMPLES (3 * SC_AUDIO_OUTPUT_BUFFER_SAMPLES)
|
||||
|
||||
// If the consumer is too late, skip samples to keep at most this value
|
||||
#define SC_BUFFERED_SAMPLES_THRESHOLD 2400 // 50ms at 48000Hz
|
||||
|
||||
// Use a ring-buffer of 1 second (at 48000Hz) between the producer and the
|
||||
// consumer. It too big, but it guarantees that the producer and the consumer
|
||||
// will be able to access it in parallel without locking.
|
||||
#define SC_BYTEBUF_SIZE_IN_SAMPLES 48000
|
||||
|
||||
static inline size_t
|
||||
bytes_to_samples(struct sc_audio_player *ap, size_t bytes) {
|
||||
assert(bytes % (ap->nb_channels * ap->out_bytes_per_sample) == 0);
|
||||
return bytes / (ap->nb_channels * ap->out_bytes_per_sample);
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
samples_to_bytes(struct sc_audio_player *ap, size_t samples) {
|
||||
return samples * ap->nb_channels * ap->out_bytes_per_sample;
|
||||
}
|
||||
|
||||
void
|
||||
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
struct sc_audio_player *ap = userdata;
|
||||
@@ -38,32 +46,12 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] SDL callback requests %" SC_PRIsizet " samples",
|
||||
len / (ap->nb_channels * ap->out_bytes_per_sample));
|
||||
bytes_to_samples(ap, len));
|
||||
#endif
|
||||
|
||||
size_t read = sc_bytebuf_read_remaining(&ap->buf);
|
||||
size_t max_buffered_bytes = SC_BUFFERED_SAMPLES_THRESHOLD
|
||||
* ap->nb_channels * ap->out_bytes_per_sample;
|
||||
if (read > max_buffered_bytes + len) {
|
||||
size_t skip = read - (max_buffered_bytes + len);
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] Buffered samples threshold exceeded: %" SC_PRIsizet
|
||||
" bytes, skipping %" SC_PRIsizet " bytes", read, skip);
|
||||
#endif
|
||||
// After this callback, exactly max_buffered_bytes will remain
|
||||
sc_bytebuf_skip(&ap->buf, skip);
|
||||
read = max_buffered_bytes + len;
|
||||
}
|
||||
|
||||
// Number of buffered samples (may be negative on underflow)
|
||||
float buffered_samples = ((float) read - len_int)
|
||||
/ (ap->nb_channels * ap->out_bytes_per_sample);
|
||||
sc_average_push(&ap->avg_buffered_samples, buffered_samples);
|
||||
|
||||
size_t read_avail = sc_bytebuf_read_available(&ap->buf);
|
||||
size_t read = MIN(read_avail, len);
|
||||
if (read) {
|
||||
if (read > len) {
|
||||
read = len;
|
||||
}
|
||||
sc_bytebuf_read(&ap->buf, stream, read);
|
||||
}
|
||||
|
||||
@@ -71,22 +59,18 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
// Insert silence
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] Buffer underflow, inserting silence: %" SC_PRIsizet
|
||||
" bytes", len - read);
|
||||
" samples", bytes_to_samples(ap, len - read));
|
||||
#endif
|
||||
memset(stream + read, 0, len - read);
|
||||
ap->underflow += bytes_to_samples(ap, len - read);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
sc_audio_player_get_buf_size(struct sc_audio_player *ap, size_t samples) {
|
||||
assert(ap->nb_channels);
|
||||
assert(ap->out_bytes_per_sample);
|
||||
return samples * ap->nb_channels * ap->out_bytes_per_sample;
|
||||
ap->last_consumed = sc_tick_now();
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
sc_audio_player_get_swr_buf(struct sc_audio_player *ap, size_t min_samples) {
|
||||
size_t min_buf_size = sc_audio_player_get_buf_size(ap, min_samples);
|
||||
size_t min_buf_size = samples_to_bytes(ap, min_samples);
|
||||
if (min_buf_size < ap->swr_buf_alloc_size) {
|
||||
size_t new_size = min_buf_size + 4096;
|
||||
uint8_t *buf = realloc(ap->swr_buf, new_size);
|
||||
@@ -170,17 +154,14 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
ap->nb_channels = nb_channels;
|
||||
ap->out_bytes_per_sample = out_bytes_per_sample;
|
||||
|
||||
size_t bytebuf_size =
|
||||
sc_audio_player_get_buf_size(ap, SC_BYTEBUF_SIZE_IN_SAMPLES);
|
||||
size_t bytebuf_size = samples_to_bytes(ap, SC_BYTEBUF_SIZE_IN_SAMPLES);
|
||||
|
||||
bool ok = sc_bytebuf_init(&ap->buf, bytebuf_size);
|
||||
if (!ok) {
|
||||
goto error_free_swr_ctx;
|
||||
}
|
||||
|
||||
ap->safe_empty_buffer = sc_bytebuf_write_remaining(&ap->buf);
|
||||
|
||||
size_t initial_swr_buf_size = sc_audio_player_get_buf_size(ap, 4096);
|
||||
size_t initial_swr_buf_size = samples_to_bytes(ap, 4096);
|
||||
ap->swr_buf = malloc(initial_swr_buf_size);
|
||||
if (!ap->swr_buf) {
|
||||
LOG_OOM();
|
||||
@@ -188,9 +169,14 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
}
|
||||
ap->swr_buf_alloc_size = initial_swr_buf_size;
|
||||
|
||||
sc_average_init(&ap->avg_buffered_samples, 32);
|
||||
ap->previous_write_avail = sc_bytebuf_write_available(&ap->buf);
|
||||
|
||||
sc_average_init(&ap->avg_buffering, 8);
|
||||
ap->samples_since_resync = 0;
|
||||
|
||||
ap->last_consumed = 0;
|
||||
ap->underflow = 0;
|
||||
|
||||
SDL_PauseAudioDevice(ap->device, 0);
|
||||
|
||||
return true;
|
||||
@@ -219,16 +205,18 @@ sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
const AVFrame *frame) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
SwrContext *swr_ctx = ap->swr_ctx;
|
||||
|
||||
int64_t delay = swr_get_delay(swr_ctx, ap->sample_rate);
|
||||
// No need to av_rescale_rnd(), input and output sample rates are the same
|
||||
int dst_nb_samples = delay + frame->nb_samples;
|
||||
// Add more space (256) for clock compensation
|
||||
int dst_nb_samples = delay + frame->nb_samples + 256;
|
||||
|
||||
uint8_t *swr_buf = sc_audio_player_get_swr_buf(ap, frame->nb_samples);
|
||||
uint8_t *swr_buf = sc_audio_player_get_swr_buf(ap, dst_nb_samples);
|
||||
if (!swr_buf) {
|
||||
return false;
|
||||
}
|
||||
@@ -240,52 +228,130 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t samples_written = ret;
|
||||
size_t swr_buf_size = sc_audio_player_get_buf_size(ap, samples_written);
|
||||
// swr_convert() returns the number of samples which would have been
|
||||
// written if the buffer was big enough.
|
||||
size_t samples_written = MIN(ret, dst_nb_samples);
|
||||
size_t swr_buf_size = samples_to_bytes(ap, samples_written);
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGI("[Audio] %" SC_PRIsizet " samples written to buffer", samples_written);
|
||||
#endif
|
||||
|
||||
// It should almost always be possible to write without lock
|
||||
bool can_write_without_lock = swr_buf_size <= ap->safe_empty_buffer;
|
||||
if (can_write_without_lock) {
|
||||
// Since this function is the only writer, the current available space is
|
||||
// at least the previous available space. In practice, it should almost
|
||||
// always be possible to write without lock.
|
||||
bool lockless_write = swr_buf_size <= ap->previous_write_avail;
|
||||
if (lockless_write) {
|
||||
sc_bytebuf_prepare_write(&ap->buf, swr_buf, swr_buf_size);
|
||||
}
|
||||
|
||||
SDL_LockAudioDevice(ap->device);
|
||||
if (can_write_without_lock) {
|
||||
|
||||
// The consumer requests audio samples blocks (e.g. 480 samples).
|
||||
// Convert the duration since the last consumption into samples.
|
||||
size_t extrapolated = 0;
|
||||
if (ap->last_consumed) {
|
||||
sc_tick now = sc_tick_now();
|
||||
assert(now >= ap->last_consumed);
|
||||
extrapolated = (sc_tick_now() - ap->last_consumed) * ap->sample_rate
|
||||
/ SC_TICK_FREQ;
|
||||
}
|
||||
|
||||
size_t read_avail = sc_bytebuf_read_available(&ap->buf);
|
||||
|
||||
// The consumer may not increase underflow value if there are still samples
|
||||
// available
|
||||
assert(read_avail == 0 || ap->underflow == 0);
|
||||
|
||||
size_t buffered_samples = bytes_to_samples(ap, read_avail);
|
||||
// Underflow caused silence samples in excess (so it adds buffering).
|
||||
// Extrapolated samples must be considered consumed for smoothing (so it
|
||||
// removes buffering).
|
||||
float buffering = (float) buffered_samples + ap->underflow - extrapolated;
|
||||
sc_average_push(&ap->avg_buffering, buffering);
|
||||
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[AUDIO] buffered_samples=%" SC_PRIsizet
|
||||
" underflow=%" SC_PRIsizet
|
||||
" extrapolated=%" SC_PRIsizet
|
||||
" buffering=%f avg_buffering=%f",
|
||||
buffered_samples, ap->underflow, extrapolated, buffering,
|
||||
sc_average_get(&ap->avg_buffering));
|
||||
#endif
|
||||
|
||||
if (lockless_write) {
|
||||
sc_bytebuf_commit_write(&ap->buf, swr_buf_size);
|
||||
} else {
|
||||
// Take care to keep full samples
|
||||
size_t align = ap->nb_channels * ap->out_bytes_per_sample;
|
||||
size_t write_avail =
|
||||
sc_bytebuf_write_available(&ap->buf) / align * align;
|
||||
if (swr_buf_size > write_avail) {
|
||||
// Skip old samples
|
||||
size_t cap = sc_bytebuf_capacity(&ap->buf) / align * align;
|
||||
if (swr_buf_size > cap) {
|
||||
// Ignore the first bytes in swr_buf
|
||||
swr_buf += swr_buf_size - cap;
|
||||
swr_buf_size = cap;
|
||||
}
|
||||
assert(swr_buf_size > write_avail);
|
||||
if (swr_buf_size - write_avail > 0) {
|
||||
sc_bytebuf_skip(&ap->buf, swr_buf_size - write_avail);
|
||||
}
|
||||
}
|
||||
sc_bytebuf_write(&ap->buf, swr_buf, swr_buf_size);
|
||||
}
|
||||
|
||||
// The next time, it will remain at least the current empty space
|
||||
ap->safe_empty_buffer = sc_bytebuf_write_remaining(&ap->buf);
|
||||
// On buffer underflow, typically because a packet is late, silence is
|
||||
// inserted. In that case, the late samples must be ignored when they
|
||||
// arrive, otherwise they will delay playback.
|
||||
//
|
||||
// As an improvement, instead of naively skipping the silence duration, we
|
||||
// can absorb it if it helps clock compensation.
|
||||
if (ap->underflow) {
|
||||
size_t avg = sc_average_get(&ap->avg_buffering);
|
||||
if (avg > SC_TARGET_BUFFERED_SAMPLES) {
|
||||
size_t diff = SC_TARGET_BUFFERED_SAMPLES - avg;
|
||||
if (diff < ap->underflow) {
|
||||
// Partially absorb underflow for clock compensation (only keep
|
||||
// the diff with the target buffering level).
|
||||
ap->underflow = diff;
|
||||
}
|
||||
|
||||
// Read the value written by the SDL thread under lock
|
||||
float avg;
|
||||
bool has_avg = sc_average_get(&ap->avg_buffered_samples, &avg);
|
||||
size_t skip_samples = MIN(ap->underflow, buffered_samples);
|
||||
if (skip_samples) {
|
||||
size_t skip_bytes = samples_to_bytes(ap, skip_samples);
|
||||
sc_bytebuf_skip(&ap->buf, skip_bytes);
|
||||
read_avail -= skip_bytes;
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] Skipping %" SC_PRIsizet " samples", skip_samples);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Totally absorb underflow for clock compensation
|
||||
ap->underflow = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ap->previous_write_avail = sc_bytebuf_write_available(&ap->buf);
|
||||
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
|
||||
if (has_avg) {
|
||||
ap->samples_since_resync += samples_written;
|
||||
if (ap->samples_since_resync >= ap->sample_rate) {
|
||||
// Resync every second
|
||||
ap->samples_since_resync = 0;
|
||||
ap->samples_since_resync += samples_written;
|
||||
if (ap->samples_since_resync >= ap->sample_rate) {
|
||||
// Resync every second
|
||||
ap->samples_since_resync = 0;
|
||||
|
||||
int diff = SC_TARGET_BUFFERED_SAMPLES - avg;
|
||||
float avg = sc_average_get(&ap->avg_buffering);
|
||||
int diff = SC_TARGET_BUFFERED_SAMPLES - avg;
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGI("[Audio] Average buffered samples = %f, compensation %d",
|
||||
avg, diff);
|
||||
LOGI("[Audio] Average buffering=%f, compensation %d", avg, diff);
|
||||
#endif
|
||||
// Compensate the diff over 3 seconds (but will be recomputed after
|
||||
// 1 second)
|
||||
int ret = swr_set_compensation(swr_ctx, diff, 3 * ap->sample_rate);
|
||||
if (ret < 0) {
|
||||
LOGW("Resampling compensation failed: %d", ret);
|
||||
// not fatal
|
||||
}
|
||||
// Compensate the diff over 3 seconds (but will be recomputed after
|
||||
// 1 second)
|
||||
int ret = swr_set_compensation(swr_ctx, diff, 3 * ap->sample_rate);
|
||||
if (ret < 0) {
|
||||
LOGW("Resampling compensation failed: %d", ret);
|
||||
// not fatal
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@ struct sc_audio_player {
|
||||
|
||||
// protected by SDL_AudioDeviceLock()
|
||||
struct sc_bytebuf buf;
|
||||
// Number of bytes which could be written without locking
|
||||
size_t safe_empty_buffer;
|
||||
size_t previous_write_avail;
|
||||
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
@@ -29,7 +28,7 @@ struct sc_audio_player {
|
||||
unsigned sample_rate;
|
||||
// The number of channels is the same for input and output
|
||||
unsigned nb_channels;
|
||||
|
||||
// The number of bytes per sample for a single channel
|
||||
unsigned out_bytes_per_sample;
|
||||
|
||||
// Target buffer for resampling
|
||||
@@ -37,8 +36,15 @@ struct sc_audio_player {
|
||||
size_t swr_buf_alloc_size;
|
||||
|
||||
// Number of buffered samples (may be negative on underflow)
|
||||
struct sc_average avg_buffered_samples;
|
||||
unsigned samples_since_resync;
|
||||
struct sc_average avg_buffering;
|
||||
// Count the number of samples to trigger a compensation update regularly
|
||||
size_t samples_since_resync;
|
||||
|
||||
// The last date a sample has been consumed by the audio output
|
||||
sc_tick last_consumed;
|
||||
|
||||
// Number of silence samples inserted to be compensated
|
||||
size_t underflow;
|
||||
|
||||
const struct sc_audio_player_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
|
||||
@@ -69,6 +69,8 @@ enum {
|
||||
OPT_AUDIO_ENCODER,
|
||||
OPT_LIST_ENCODERS,
|
||||
OPT_LIST_DISPLAYS,
|
||||
OPT_REQUIRE_AUDIO,
|
||||
OPT_AUDIO_BUFFER,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@@ -118,6 +120,14 @@ static const struct sc_option options[] = {
|
||||
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||
"Default is 196K (196000).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_BUFFER,
|
||||
.longopt = "audio-buffer",
|
||||
.argdesc = "ms",
|
||||
.text = "Add a buffering delay (in milliseconds) before playing audio. "
|
||||
"This increases latency to compensate for jitter.\n"
|
||||
"Default is 0 (no buffering).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_CODEC,
|
||||
.longopt = "audio-codec",
|
||||
@@ -458,6 +468,13 @@ static const struct sc_option options[] = {
|
||||
.longopt_id = OPT_RENDER_EXPIRED_FRAMES,
|
||||
.longopt = "render-expired-frames",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_REQUIRE_AUDIO,
|
||||
.longopt = "require-audio",
|
||||
.text = "By default, scrcpy mirrors only the video when audio capture "
|
||||
"fails on the device. This flag makes scrcpy fail if audio is "
|
||||
"enabled but does not work."
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_ROTATION,
|
||||
.longopt = "rotation",
|
||||
@@ -1801,6 +1818,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_LIST_DISPLAYS:
|
||||
opts->list_displays = true;
|
||||
break;
|
||||
case OPT_REQUIRE_AUDIO:
|
||||
opts->require_audio = true;
|
||||
break;
|
||||
case OPT_AUDIO_BUFFER:
|
||||
if (!parse_buffering_time(optarg, &opts->audio_buffer)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@@ -1861,6 +1886,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opts->audio && !opts->display && !opts->record_filename) {
|
||||
LOGI("No display and no recording: audio disabled");
|
||||
opts->audio = false;
|
||||
}
|
||||
|
||||
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
||||
LOGI("Tunnel host/port is set, "
|
||||
"--force-adb-forward automatically enabled.");
|
||||
|
||||
@@ -18,7 +18,15 @@ sc_clock_init(struct sc_clock *clock) {
|
||||
static void
|
||||
sc_clock_estimate(struct sc_clock *clock,
|
||||
double *out_slope, sc_tick *out_offset) {
|
||||
assert(clock->count > 1); // two points are necessary
|
||||
assert(clock->count);
|
||||
|
||||
if (clock->count == 1) {
|
||||
// If there is only 1 point, we can't compute a slope. Assume it is 1.
|
||||
struct sc_clock_point *single_point = &clock->right_sum;
|
||||
*out_slope = 1;
|
||||
*out_offset = single_point->system - single_point->stream;
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_clock_point left_avg = {
|
||||
.system = clock->left_sum.system / (clock->count / 2),
|
||||
@@ -93,19 +101,17 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
|
||||
|
||||
clock->head = (clock->head + 1) % SC_CLOCK_RANGE;
|
||||
|
||||
if (clock->count > 1) {
|
||||
// Update estimation
|
||||
sc_clock_estimate(clock, &clock->slope, &clock->offset);
|
||||
// Update estimation
|
||||
sc_clock_estimate(clock, &clock->slope, &clock->offset);
|
||||
|
||||
#ifndef SC_CLOCK_NDEBUG
|
||||
LOGD("Clock estimation: %f * pts + %" PRItick,
|
||||
clock->slope, clock->offset);
|
||||
LOGD("Clock estimation: %f * pts + %" PRItick,
|
||||
clock->slope, clock->offset);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
sc_tick
|
||||
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) {
|
||||
assert(clock->count > 1); // sc_clock_update() must have been called
|
||||
assert(clock->count); // sc_clock_update() must have been called
|
||||
return (sc_tick) (stream * clock->slope) + clock->offset;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include "compat.h"
|
||||
|
||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
|
||||
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
|
||||
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
|
||||
|
||||
#define container_of(ptr, type, member) \
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
#ifndef HAVE_REALLOCARRAY
|
||||
# include <errno.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
@@ -93,5 +96,15 @@ long jrand48(unsigned short xsubi[3]) {
|
||||
return v.i;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_REALLOCARRAY
|
||||
void *reallocarray(void *ptr, size_t nmemb, size_t size) {
|
||||
size_t bytes;
|
||||
if (__builtin_mul_overflow(nmemb, size, &bytes)) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
return realloc(ptr, bytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -74,4 +74,8 @@ long nrand48(unsigned short xsubi[3]);
|
||||
long jrand48(unsigned short xsubi[3]);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_REALLOCARRAY
|
||||
void *reallocarray(void *ptr, size_t nmemb, size_t size);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,19 +4,28 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
struct sc_acksync *acksync) {
|
||||
cbuf_init(&controller->queue);
|
||||
sc_vecdeque_init(&controller->queue);
|
||||
|
||||
bool ok = sc_receiver_init(&controller->receiver, control_socket, acksync);
|
||||
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_receiver_init(&controller->receiver, control_socket, acksync);
|
||||
if (!ok) {
|
||||
sc_vecdeque_destroy(&controller->queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_mutex_init(&controller->mutex);
|
||||
if (!ok) {
|
||||
sc_receiver_destroy(&controller->receiver);
|
||||
sc_vecdeque_destroy(&controller->queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,6 +33,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
if (!ok) {
|
||||
sc_receiver_destroy(&controller->receiver);
|
||||
sc_mutex_destroy(&controller->mutex);
|
||||
sc_vecdeque_destroy(&controller->queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -38,10 +48,12 @@ sc_controller_destroy(struct sc_controller *controller) {
|
||||
sc_cond_destroy(&controller->msg_cond);
|
||||
sc_mutex_destroy(&controller->mutex);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
while (cbuf_take(&controller->queue, &msg)) {
|
||||
sc_control_msg_destroy(&msg);
|
||||
while (!sc_vecdeque_is_empty(&controller->queue)) {
|
||||
struct sc_control_msg *msg = sc_vecdeque_popref(&controller->queue);
|
||||
assert(msg);
|
||||
sc_control_msg_destroy(msg);
|
||||
}
|
||||
sc_vecdeque_destroy(&controller->queue);
|
||||
|
||||
sc_receiver_destroy(&controller->receiver);
|
||||
}
|
||||
@@ -54,13 +66,19 @@ sc_controller_push_msg(struct sc_controller *controller,
|
||||
}
|
||||
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
bool was_empty = cbuf_is_empty(&controller->queue);
|
||||
bool res = cbuf_push(&controller->queue, *msg);
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&controller->msg_cond);
|
||||
bool full = sc_vecdeque_is_full(&controller->queue);
|
||||
if (!full) {
|
||||
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
||||
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&controller->msg_cond);
|
||||
}
|
||||
}
|
||||
// Otherwise (if the queue is full), the msg is discarded
|
||||
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
return res;
|
||||
|
||||
return !full;
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -82,7 +100,8 @@ run_controller(void *data) {
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
|
||||
while (!controller->stopped
|
||||
&& sc_vecdeque_is_empty(&controller->queue)) {
|
||||
sc_cond_wait(&controller->msg_cond, &controller->mutex);
|
||||
}
|
||||
if (controller->stopped) {
|
||||
@@ -90,10 +109,9 @@ run_controller(void *data) {
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
break;
|
||||
}
|
||||
struct sc_control_msg msg;
|
||||
bool non_empty = cbuf_take(&controller->queue, &msg);
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
|
||||
assert(!sc_vecdeque_is_empty(&controller->queue));
|
||||
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
bool ok = process_msg(controller, &msg);
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#include "control_msg.h"
|
||||
#include "receiver.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/cbuf.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
struct sc_control_msg_queue CBUF(struct sc_control_msg, 64);
|
||||
struct sc_control_msg_queue SC_VECDEQUE(struct sc_control_msg);
|
||||
|
||||
struct sc_controller {
|
||||
sc_socket control_socket;
|
||||
|
||||
@@ -5,39 +5,12 @@
|
||||
#include <libavutil/channel_layout.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "video_buffer.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast packet_sink to decoder */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
|
||||
|
||||
static void
|
||||
sc_decoder_close_first_sinks(struct sc_decoder *decoder, unsigned count) {
|
||||
while (count) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[--count];
|
||||
sink->ops->close(sink);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_decoder_close_sinks(struct sc_decoder *decoder) {
|
||||
sc_decoder_close_first_sinks(decoder, decoder->sink_count);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_open_sinks(struct sc_decoder *decoder, const AVCodecContext *ctx) {
|
||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
sc_decoder_close_first_sinks(decoder, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
||||
@@ -77,7 +50,8 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_decoder_open_sinks(decoder, decoder->codec_ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source,
|
||||
decoder->codec_ctx)) {
|
||||
av_frame_free(&decoder->frame);
|
||||
avcodec_close(decoder->codec_ctx);
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
@@ -89,24 +63,12 @@ sc_decoder_open(struct sc_decoder *decoder, const AVCodec *codec) {
|
||||
|
||||
static void
|
||||
sc_decoder_close(struct sc_decoder *decoder) {
|
||||
sc_decoder_close_sinks(decoder);
|
||||
sc_frame_source_sinks_close(&decoder->frame_source);
|
||||
av_frame_free(&decoder->frame);
|
||||
avcodec_close(decoder->codec_ctx);
|
||||
avcodec_free_context(&decoder->codec_ctx);
|
||||
}
|
||||
|
||||
static bool
|
||||
push_frame_to_sinks(struct sc_decoder *decoder, const AVFrame *frame) {
|
||||
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||
if (!sink->ops->push(sink, frame)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||
@@ -121,20 +83,29 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
decoder->name, ret);
|
||||
return false;
|
||||
}
|
||||
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
|
||||
if (!ret) {
|
||||
// a frame was received
|
||||
bool ok = push_frame_to_sinks(decoder, decoder->frame);
|
||||
// A frame lost should not make the whole pipeline fail. The error, if
|
||||
// any, is already logged.
|
||||
(void) ok;
|
||||
|
||||
for (;;) {
|
||||
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
LOGE("Decoder '%s', could not receive video frame: %d",
|
||||
decoder->name, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// a frame was received
|
||||
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
|
||||
decoder->frame);
|
||||
av_frame_unref(decoder->frame);
|
||||
} else if (ret != AVERROR(EAGAIN)) {
|
||||
LOGE("Decoder '%s', could not receive video frame: %d",
|
||||
decoder->name, ret);
|
||||
return false;
|
||||
if (!ok) {
|
||||
// Error already logged
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -160,7 +131,7 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
decoder->name = name; // statically allocated
|
||||
decoder->sink_count = 0;
|
||||
sc_frame_source_init(&decoder->frame_source);
|
||||
|
||||
static const struct sc_packet_sink_ops ops = {
|
||||
.open = sc_decoder_packet_sink_open,
|
||||
@@ -170,11 +141,3 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
|
||||
decoder->packet_sink.ops = &ops;
|
||||
}
|
||||
|
||||
void
|
||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink) {
|
||||
assert(decoder->sink_count < SC_DECODER_MAX_SINKS);
|
||||
assert(sink);
|
||||
assert(sink->ops);
|
||||
decoder->sinks[decoder->sink_count++] = sink;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,19 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "trait/frame_source.h"
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#define SC_DECODER_MAX_SINKS 2
|
||||
|
||||
struct sc_decoder {
|
||||
struct sc_packet_sink packet_sink; // packet sink trait
|
||||
struct sc_frame_source frame_source; // frame source trait
|
||||
|
||||
const char *name; // must be statically allocated (e.g. a string literal)
|
||||
|
||||
struct sc_frame_sink *sinks[SC_DECODER_MAX_SINKS];
|
||||
unsigned sink_count;
|
||||
|
||||
AVCodecContext *codec_ctx;
|
||||
AVFrame *frame;
|
||||
};
|
||||
@@ -27,7 +24,4 @@ struct sc_decoder {
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder, const char *name);
|
||||
|
||||
void
|
||||
sc_decoder_add_sink(struct sc_decoder *decoder, struct sc_frame_sink *sink);
|
||||
|
||||
#endif
|
||||
|
||||
244
app/src/delay_buffer.c
Normal file
244
app/src/delay_buffer.c
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "delay_buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_BUFFERING_NDEBUG // comment to debug
|
||||
|
||||
/** Downcast frame_sink to sc_delay_buffer */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
||||
|
||||
static bool
|
||||
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
dframe->frame = av_frame_alloc();
|
||||
if (!dframe->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (av_frame_ref(dframe->frame, frame)) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&dframe->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
|
||||
av_frame_unref(dframe->frame);
|
||||
av_frame_free(&dframe->frame);
|
||||
}
|
||||
|
||||
static int
|
||||
run_buffering(void *data) {
|
||||
struct sc_delay_buffer *db = data;
|
||||
|
||||
assert(db->delay > 0);
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&db->mutex);
|
||||
|
||||
while (!db->stopped && sc_vecdeque_is_empty(&db->queue)) {
|
||||
sc_cond_wait(&db->queue_cond, &db->mutex);
|
||||
}
|
||||
|
||||
if (db->stopped) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
|
||||
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_TO_US(dframe.frame->pts);
|
||||
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
if (stopped) {
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
#endif
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
if (!ok) {
|
||||
LOGE("Delayed frame could not be pushed, stopping");
|
||||
sc_mutex_lock(&db->mutex);
|
||||
// Prevent to push any new frame
|
||||
db->stopped = true;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
goto stopped;
|
||||
}
|
||||
}
|
||||
|
||||
stopped:
|
||||
assert(db->stopped);
|
||||
|
||||
// Flush queue
|
||||
while (!sc_vecdeque_is_empty(&db->queue)) {
|
||||
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_frame_destroy(dframe);
|
||||
}
|
||||
|
||||
LOGD("Buffering thread ended");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
(void) ctx;
|
||||
|
||||
bool ok = sc_mutex_init(&db->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&db->queue_cond);
|
||||
if (!ok) {
|
||||
goto error_destroy_mutex;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&db->wait_cond);
|
||||
if (!ok) {
|
||||
goto error_destroy_queue_cond;
|
||||
}
|
||||
|
||||
sc_clock_init(&db->clock);
|
||||
sc_vecdeque_init(&db->queue);
|
||||
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
|
||||
goto error_destroy_wait_cond;
|
||||
}
|
||||
|
||||
ok = sc_thread_create(&db->thread, run_buffering, "scrcpy-dbuf", db);
|
||||
if (!ok) {
|
||||
LOGE("Could not start buffering thread");
|
||||
goto error_close_sinks;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error_close_sinks:
|
||||
sc_frame_source_sinks_close(&db->frame_source);
|
||||
error_destroy_wait_cond:
|
||||
sc_cond_destroy(&db->wait_cond);
|
||||
error_destroy_queue_cond:
|
||||
sc_cond_destroy(&db->queue_cond);
|
||||
error_destroy_mutex:
|
||||
sc_mutex_destroy(&db->mutex);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delay_buffer_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
|
||||
sc_mutex_lock(&db->mutex);
|
||||
db->stopped = true;
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
sc_cond_signal(&db->wait_cond);
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
sc_thread_join(&db->thread, NULL);
|
||||
|
||||
sc_frame_source_sinks_close(&db->frame_source);
|
||||
|
||||
sc_cond_destroy(&db->wait_cond);
|
||||
sc_cond_destroy(&db->queue_cond);
|
||||
sc_mutex_destroy(&db->mutex);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
|
||||
const AVFrame *frame) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
|
||||
sc_mutex_lock(&db->mutex);
|
||||
|
||||
if (db->stopped) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_tick pts = SC_TICK_FROM_US(frame->pts);
|
||||
sc_clock_update(&db->clock, sc_tick_now(), pts);
|
||||
sc_cond_signal(&db->wait_cond);
|
||||
|
||||
if (db->first_frame_asap && db->clock.count == 1) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
return sc_frame_source_sinks_push(&db->frame_source, frame);
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe;
|
||||
bool ok = sc_delayed_frame_init(&dframe, frame);
|
||||
if (!ok) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
dframe.push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
ok = sc_vecdeque_push(&db->queue, dframe);
|
||||
if (!ok) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
|
||||
bool first_frame_asap) {
|
||||
assert(delay > 0);
|
||||
|
||||
db->delay = delay;
|
||||
db->first_frame_asap = first_frame_asap;
|
||||
|
||||
sc_frame_source_init(&db->frame_source);
|
||||
|
||||
static const struct sc_frame_sink_ops ops = {
|
||||
.open = sc_delay_buffer_frame_sink_open,
|
||||
.close = sc_delay_buffer_frame_sink_close,
|
||||
.push = sc_delay_buffer_frame_sink_push,
|
||||
};
|
||||
|
||||
db->frame_sink.ops = &ops;
|
||||
}
|
||||
60
app/src/delay_buffer.h
Normal file
60
app/src/delay_buffer.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef SC_DELAY_BUFFER_H
|
||||
#define SC_DELAY_BUFFER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "trait/frame_source.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/tick.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
struct sc_delayed_frame {
|
||||
AVFrame *frame;
|
||||
#ifndef NDEBUG
|
||||
sc_tick push_date;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
|
||||
|
||||
struct sc_delay_buffer {
|
||||
struct sc_frame_source frame_source; // frame source trait
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
sc_tick delay;
|
||||
bool first_frame_asap;
|
||||
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond queue_cond;
|
||||
sc_cond wait_cond;
|
||||
|
||||
struct sc_clock clock;
|
||||
struct sc_delayed_frame_queue queue;
|
||||
bool stopped;
|
||||
};
|
||||
|
||||
struct sc_delay_buffer_callbacks {
|
||||
bool (*on_new_frame)(struct sc_delay_buffer *db, const AVFrame *frame,
|
||||
void *userdata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize a delay buffer.
|
||||
*
|
||||
* \param delay a (strictly) positive delay
|
||||
* \param first_frame_asap if true, do not delay the first frame (useful for
|
||||
a video stream).
|
||||
*/
|
||||
void
|
||||
sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
|
||||
bool first_frame_asap);
|
||||
|
||||
#endif
|
||||
@@ -112,86 +112,26 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
push_packet_to_sinks(struct sc_demuxer *demuxer, const AVPacket *packet) {
|
||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||
if (!sink->ops->push(sink, packet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_push_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
bool ok = push_packet_to_sinks(demuxer, packet);
|
||||
if (!ok) {
|
||||
LOGE("Demuxer '%s': could not process packet", demuxer->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_demuxer_close_first_sinks(struct sc_demuxer *demuxer, unsigned count) {
|
||||
while (count) {
|
||||
struct sc_packet_sink *sink = demuxer->sinks[--count];
|
||||
sink->ops->close(sink);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_demuxer_close_sinks(struct sc_demuxer *demuxer) {
|
||||
sc_demuxer_close_first_sinks(demuxer, demuxer->sink_count);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
|
||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||
if (!sink->ops->open(sink, codec)) {
|
||||
sc_demuxer_close_first_sinks(demuxer, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_demuxer_disable_sinks(struct sc_demuxer *demuxer) {
|
||||
for (unsigned i = 0; i < demuxer->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = demuxer->sinks[i];
|
||||
if (sink->ops->disable) {
|
||||
sink->ops->disable(sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
run_demuxer(void *data) {
|
||||
struct sc_demuxer *demuxer = data;
|
||||
|
||||
// Flag to report end-of-stream (i.e. device disconnected)
|
||||
bool eos = false;
|
||||
enum sc_demuxer_status status = SC_DEMUXER_STATUS_ERROR;
|
||||
|
||||
uint32_t raw_codec_id;
|
||||
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
|
||||
if (!ok) {
|
||||
LOGE("Demuxer '%s': stream disabled due to connection error",
|
||||
demuxer->name);
|
||||
eos = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (raw_codec_id == 0) {
|
||||
LOGW("Demuxer '%s': stream explicitly disabled by the device",
|
||||
demuxer->name);
|
||||
sc_demuxer_disable_sinks(demuxer);
|
||||
eos = true;
|
||||
sc_packet_source_sinks_disable(&demuxer->packet_source);
|
||||
status = SC_DEMUXER_STATUS_DISABLED;
|
||||
goto end;
|
||||
}
|
||||
|
||||
@@ -205,7 +145,7 @@ run_demuxer(void *data) {
|
||||
if (codec_id == AV_CODEC_ID_NONE) {
|
||||
LOGE("Demuxer '%s': stream disabled due to unsupported codec",
|
||||
demuxer->name);
|
||||
sc_demuxer_disable_sinks(demuxer);
|
||||
sc_packet_source_sinks_disable(&demuxer->packet_source);
|
||||
goto end;
|
||||
}
|
||||
|
||||
@@ -213,11 +153,11 @@ run_demuxer(void *data) {
|
||||
if (!codec) {
|
||||
LOGE("Demuxer '%s': stream disabled due to missing decoder",
|
||||
demuxer->name);
|
||||
sc_demuxer_disable_sinks(demuxer);
|
||||
sc_packet_source_sinks_disable(&demuxer->packet_source);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!sc_demuxer_open_sinks(demuxer, codec)) {
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
@@ -241,7 +181,7 @@ run_demuxer(void *data) {
|
||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
||||
if (!ok) {
|
||||
// end of stream
|
||||
eos = true;
|
||||
status = SC_DEMUXER_STATUS_EOS;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -254,10 +194,10 @@ run_demuxer(void *data) {
|
||||
}
|
||||
}
|
||||
|
||||
ok = sc_demuxer_push_packet(demuxer, packet);
|
||||
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// cannot process packet (error already logged)
|
||||
LOGE("Demuxer '%s': could not process packet", demuxer->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -270,9 +210,9 @@ run_demuxer(void *data) {
|
||||
|
||||
av_packet_free(&packet);
|
||||
finally_close_sinks:
|
||||
sc_demuxer_close_sinks(demuxer);
|
||||
sc_packet_source_sinks_close(&demuxer->packet_source);
|
||||
end:
|
||||
demuxer->cbs->on_ended(demuxer, eos, demuxer->cbs_userdata);
|
||||
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -284,7 +224,7 @@ sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
||||
|
||||
demuxer->name = name; // statically allocated
|
||||
demuxer->socket = socket;
|
||||
demuxer->sink_count = 0;
|
||||
sc_packet_source_init(&demuxer->packet_source);
|
||||
|
||||
assert(cbs && cbs->on_ended);
|
||||
|
||||
@@ -292,14 +232,6 @@ sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
||||
demuxer->cbs_userdata = cbs_userdata;
|
||||
}
|
||||
|
||||
void
|
||||
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink) {
|
||||
assert(demuxer->sink_count < SC_DEMUXER_MAX_SINKS);
|
||||
assert(sink);
|
||||
assert(sink->ops);
|
||||
demuxer->sinks[demuxer->sink_count++] = sink;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_demuxer_start(struct sc_demuxer *demuxer) {
|
||||
LOGD("Demuxer '%s': starting thread", demuxer->name);
|
||||
|
||||
@@ -8,27 +8,32 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "trait/packet_source.h"
|
||||
#include "trait/packet_sink.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
#define SC_DEMUXER_MAX_SINKS 2
|
||||
|
||||
struct sc_demuxer {
|
||||
struct sc_packet_source packet_source; // packet source trait
|
||||
|
||||
const char *name; // must be statically allocated (e.g. a string literal)
|
||||
|
||||
sc_socket socket;
|
||||
sc_thread thread;
|
||||
|
||||
struct sc_packet_sink *sinks[SC_DEMUXER_MAX_SINKS];
|
||||
unsigned sink_count;
|
||||
|
||||
const struct sc_demuxer_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
enum sc_demuxer_status {
|
||||
SC_DEMUXER_STATUS_EOS,
|
||||
SC_DEMUXER_STATUS_DISABLED,
|
||||
SC_DEMUXER_STATUS_ERROR,
|
||||
};
|
||||
|
||||
struct sc_demuxer_callbacks {
|
||||
void (*on_ended)(struct sc_demuxer *demuxer, bool eos, void *userdata);
|
||||
void (*on_ended)(struct sc_demuxer *demuxer, enum sc_demuxer_status,
|
||||
void *userdata);
|
||||
};
|
||||
|
||||
// The name must be statically allocated (e.g. a string literal)
|
||||
@@ -36,9 +41,6 @@ void
|
||||
sc_demuxer_init(struct sc_demuxer *demuxer, const char *name, sc_socket socket,
|
||||
const struct sc_demuxer_callbacks *cbs, void *cbs_userdata);
|
||||
|
||||
void
|
||||
sc_demuxer_add_sink(struct sc_demuxer *demuxer, struct sc_packet_sink *sink);
|
||||
|
||||
bool
|
||||
sc_demuxer_start(struct sc_demuxer *demuxer);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
|
||||
const char *push_target) {
|
||||
assert(serial);
|
||||
|
||||
cbuf_init(&fp->queue);
|
||||
sc_vecdeque_init(&fp->queue);
|
||||
|
||||
bool ok = sc_mutex_init(&fp->mutex);
|
||||
if (!ok) {
|
||||
@@ -65,9 +65,10 @@ sc_file_pusher_destroy(struct sc_file_pusher *fp) {
|
||||
sc_intr_destroy(&fp->intr);
|
||||
free(fp->serial);
|
||||
|
||||
struct sc_file_pusher_request req;
|
||||
while (cbuf_take(&fp->queue, &req)) {
|
||||
sc_file_pusher_request_destroy(&req);
|
||||
while (!sc_vecdeque_is_empty(&fp->queue)) {
|
||||
struct sc_file_pusher_request *req = sc_vecdeque_popref(&fp->queue);
|
||||
assert(req);
|
||||
sc_file_pusher_request_destroy(req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +92,20 @@ sc_file_pusher_request(struct sc_file_pusher *fp,
|
||||
};
|
||||
|
||||
sc_mutex_lock(&fp->mutex);
|
||||
bool was_empty = cbuf_is_empty(&fp->queue);
|
||||
bool res = cbuf_push(&fp->queue, req);
|
||||
bool was_empty = sc_vecdeque_is_empty(&fp->queue);
|
||||
bool res = sc_vecdeque_push(&fp->queue, req);
|
||||
if (!res) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&fp->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&fp->event_cond);
|
||||
}
|
||||
sc_mutex_unlock(&fp->mutex);
|
||||
return res;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -113,7 +121,7 @@ run_file_pusher(void *data) {
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&fp->mutex);
|
||||
while (!fp->stopped && cbuf_is_empty(&fp->queue)) {
|
||||
while (!fp->stopped && sc_vecdeque_is_empty(&fp->queue)) {
|
||||
sc_cond_wait(&fp->event_cond, &fp->mutex);
|
||||
}
|
||||
if (fp->stopped) {
|
||||
@@ -121,10 +129,9 @@ run_file_pusher(void *data) {
|
||||
sc_mutex_unlock(&fp->mutex);
|
||||
break;
|
||||
}
|
||||
struct sc_file_pusher_request req;
|
||||
bool non_empty = cbuf_take(&fp->queue, &req);
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
|
||||
assert(!sc_vecdeque_is_empty(&fp->queue));
|
||||
struct sc_file_pusher_request req = sc_vecdeque_pop(&fp->queue);
|
||||
sc_mutex_unlock(&fp->mutex);
|
||||
|
||||
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/cbuf.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/intr.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
enum sc_file_pusher_action {
|
||||
SC_FILE_PUSHER_ACTION_INSTALL_APK,
|
||||
@@ -19,7 +19,7 @@ struct sc_file_pusher_request {
|
||||
char *file;
|
||||
};
|
||||
|
||||
struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16);
|
||||
struct sc_file_pusher_request_queue SC_VECDEQUE(struct sc_file_pusher_request);
|
||||
|
||||
struct sc_file_pusher {
|
||||
char *serial;
|
||||
|
||||
@@ -43,6 +43,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.display_id = 0,
|
||||
.display_buffer = 0,
|
||||
.v4l2_buffer = 0,
|
||||
.audio_buffer = 0,
|
||||
#ifdef HAVE_USB
|
||||
.otg = false,
|
||||
#endif
|
||||
@@ -72,6 +73,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.start_fps_counter = false,
|
||||
.power_on = true,
|
||||
.audio = true,
|
||||
.require_audio = false,
|
||||
.list_encoders = false,
|
||||
.list_displays = false,
|
||||
};
|
||||
|
||||
@@ -125,6 +125,7 @@ struct scrcpy_options {
|
||||
uint32_t display_id;
|
||||
sc_tick display_buffer;
|
||||
sc_tick v4l2_buffer;
|
||||
sc_tick audio_buffer;
|
||||
#ifdef HAVE_USB
|
||||
bool otg;
|
||||
#endif
|
||||
@@ -154,6 +155,7 @@ struct scrcpy_options {
|
||||
bool start_fps_counter;
|
||||
bool power_on;
|
||||
bool audio;
|
||||
bool require_audio;
|
||||
bool list_encoders;
|
||||
bool list_displays;
|
||||
};
|
||||
|
||||
@@ -33,41 +33,27 @@ find_muxer(const char *name) {
|
||||
return oformat;
|
||||
}
|
||||
|
||||
static struct sc_record_packet *
|
||||
sc_record_packet_new(const AVPacket *packet) {
|
||||
struct sc_record_packet *rec = malloc(sizeof(*rec));
|
||||
if (!rec) {
|
||||
static AVPacket *
|
||||
sc_recorder_packet_ref(const AVPacket *packet) {
|
||||
AVPacket *p = av_packet_alloc();
|
||||
if (!p) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rec->packet = av_packet_alloc();
|
||||
if (!rec->packet) {
|
||||
LOG_OOM();
|
||||
free(rec);
|
||||
if (av_packet_ref(p, packet)) {
|
||||
av_packet_free(&p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (av_packet_ref(rec->packet, packet)) {
|
||||
av_packet_free(&rec->packet);
|
||||
free(rec);
|
||||
return NULL;
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_record_packet_delete(struct sc_record_packet *rec) {
|
||||
av_packet_free(&rec->packet);
|
||||
free(rec);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_recorder_queue_clear(struct sc_recorder_queue *queue) {
|
||||
while (!sc_queue_is_empty(queue)) {
|
||||
struct sc_record_packet *rec;
|
||||
sc_queue_take(queue, next, &rec);
|
||||
sc_record_packet_delete(rec);
|
||||
while (!sc_vecdeque_is_empty(queue)) {
|
||||
AVPacket *p = sc_vecdeque_pop(queue);
|
||||
av_packet_free(&p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,12 +218,12 @@ sc_recorder_wait_audio_stream(struct sc_recorder *recorder) {
|
||||
|
||||
static inline bool
|
||||
sc_recorder_has_empty_queues(struct sc_recorder *recorder) {
|
||||
if (sc_queue_is_empty(&recorder->video_queue)) {
|
||||
if (sc_vecdeque_is_empty(&recorder->video_queue)) {
|
||||
// The video queue is empty
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recorder->audio && sc_queue_is_empty(&recorder->audio_queue)) {
|
||||
if (recorder->audio && sc_vecdeque_is_empty(&recorder->audio_queue)) {
|
||||
// The audio queue is empty (when audio is enabled)
|
||||
return true;
|
||||
}
|
||||
@@ -254,27 +240,26 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
||||
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
||||
}
|
||||
|
||||
if (recorder->stopped && sc_queue_is_empty(&recorder->video_queue)) {
|
||||
if (recorder->stopped && sc_vecdeque_is_empty(&recorder->video_queue)) {
|
||||
// If the recorder is stopped, don't process anything if there are not
|
||||
// at least video packets
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_record_packet *video_pkt;
|
||||
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
||||
AVPacket *video_pkt = sc_vecdeque_pop(&recorder->video_queue);
|
||||
|
||||
struct sc_record_packet *audio_pkt = NULL;
|
||||
if (!sc_queue_is_empty(&recorder->audio_queue)) {
|
||||
AVPacket *audio_pkt = NULL;
|
||||
if (!sc_vecdeque_is_empty(&recorder->audio_queue)) {
|
||||
assert(recorder->audio);
|
||||
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
||||
audio_pkt = sc_vecdeque_pop(&recorder->audio_queue);
|
||||
}
|
||||
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
|
||||
int ret = false;
|
||||
|
||||
if (video_pkt->packet->pts != AV_NOPTS_VALUE) {
|
||||
if (video_pkt->pts != AV_NOPTS_VALUE) {
|
||||
LOGE("The first video packet is not a config packet");
|
||||
goto end;
|
||||
}
|
||||
@@ -282,13 +267,13 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
||||
assert(recorder->video_stream_index >= 0);
|
||||
AVStream *video_stream =
|
||||
recorder->ctx->streams[recorder->video_stream_index];
|
||||
bool ok = sc_recorder_set_extradata(video_stream, video_pkt->packet);
|
||||
bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (audio_pkt) {
|
||||
if (audio_pkt->packet->pts != AV_NOPTS_VALUE) {
|
||||
if (audio_pkt->pts != AV_NOPTS_VALUE) {
|
||||
LOGE("The first audio packet is not a config packet");
|
||||
goto end;
|
||||
}
|
||||
@@ -296,7 +281,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
||||
assert(recorder->audio_stream_index >= 0);
|
||||
AVStream *audio_stream =
|
||||
recorder->ctx->streams[recorder->audio_stream_index];
|
||||
ok = sc_recorder_set_extradata(audio_stream, audio_pkt->packet);
|
||||
ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
@@ -311,9 +296,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
sc_record_packet_delete(video_pkt);
|
||||
av_packet_free(&video_pkt);
|
||||
if (audio_pkt) {
|
||||
sc_record_packet_delete(audio_pkt);
|
||||
av_packet_free(&audio_pkt);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -328,12 +313,12 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_record_packet *video_pkt = NULL;
|
||||
struct sc_record_packet *audio_pkt = NULL;
|
||||
AVPacket *video_pkt = NULL;
|
||||
AVPacket *audio_pkt = NULL;
|
||||
|
||||
// We can write a video packet only once we received the next one so that
|
||||
// we can set its duration (next_pts - current_pts)
|
||||
struct sc_record_packet *video_pkt_previous = NULL;
|
||||
AVPacket *video_pkt_previous = NULL;
|
||||
|
||||
bool error = false;
|
||||
|
||||
@@ -341,12 +326,12 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
sc_mutex_lock(&recorder->mutex);
|
||||
|
||||
while (!recorder->stopped) {
|
||||
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
||||
if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) {
|
||||
// A new packet may be assigned to video_pkt and be processed
|
||||
break;
|
||||
}
|
||||
if (recorder->audio && !audio_pkt
|
||||
&& !sc_queue_is_empty(&recorder->audio_queue)) {
|
||||
&& !sc_vecdeque_is_empty(&recorder->audio_queue)) {
|
||||
// A new packet may be assigned to audio_pkt and be processed
|
||||
break;
|
||||
}
|
||||
@@ -358,20 +343,20 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
|
||||
// If there is no audio, then the audio_queue will remain empty forever
|
||||
// and audio_pkt will always be NULL.
|
||||
assert(recorder->audio
|
||||
|| (!audio_pkt && sc_queue_is_empty(&recorder->audio_queue)));
|
||||
assert(recorder->audio || (!audio_pkt
|
||||
&& sc_vecdeque_is_empty(&recorder->audio_queue)));
|
||||
|
||||
if (!video_pkt && !sc_queue_is_empty(&recorder->video_queue)) {
|
||||
sc_queue_take(&recorder->video_queue, next, &video_pkt);
|
||||
if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) {
|
||||
video_pkt = sc_vecdeque_pop(&recorder->video_queue);
|
||||
}
|
||||
|
||||
if (!audio_pkt && !sc_queue_is_empty(&recorder->audio_queue)) {
|
||||
sc_queue_take(&recorder->audio_queue, next, &audio_pkt);
|
||||
if (!audio_pkt && !sc_vecdeque_is_empty(&recorder->audio_queue)) {
|
||||
audio_pkt = sc_vecdeque_pop(&recorder->audio_queue);
|
||||
}
|
||||
|
||||
if (recorder->stopped && !video_pkt && !audio_pkt) {
|
||||
assert(sc_queue_is_empty(&recorder->video_queue));
|
||||
assert(sc_queue_is_empty(&recorder->audio_queue));
|
||||
assert(sc_vecdeque_is_empty(&recorder->video_queue));
|
||||
assert(sc_vecdeque_is_empty(&recorder->audio_queue));
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
break;
|
||||
}
|
||||
@@ -383,28 +368,27 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
// Ignore further config packets (e.g. on device orientation
|
||||
// change). The next non-config packet will have the config packet
|
||||
// data prepended.
|
||||
if (video_pkt && video_pkt->packet->pts == AV_NOPTS_VALUE) {
|
||||
sc_record_packet_delete(video_pkt);
|
||||
if (video_pkt && video_pkt->pts == AV_NOPTS_VALUE) {
|
||||
av_packet_free(&video_pkt);
|
||||
video_pkt = NULL;
|
||||
}
|
||||
|
||||
if (audio_pkt && audio_pkt->packet->pts == AV_NOPTS_VALUE) {
|
||||
sc_record_packet_delete(audio_pkt);
|
||||
audio_pkt= NULL;
|
||||
if (audio_pkt && audio_pkt->pts == AV_NOPTS_VALUE) {
|
||||
av_packet_free(&audio_pkt);
|
||||
audio_pkt = NULL;
|
||||
}
|
||||
|
||||
if (pts_origin == AV_NOPTS_VALUE) {
|
||||
if (!recorder->audio) {
|
||||
assert(video_pkt);
|
||||
pts_origin = video_pkt->packet->pts;
|
||||
pts_origin = video_pkt->pts;
|
||||
} else if (video_pkt && audio_pkt) {
|
||||
pts_origin =
|
||||
MIN(video_pkt->packet->pts, audio_pkt->packet->pts);
|
||||
pts_origin = MIN(video_pkt->pts, audio_pkt->pts);
|
||||
} else if (recorder->stopped) {
|
||||
if (video_pkt) {
|
||||
// The recorder is stopped without audio, record the video
|
||||
// packets
|
||||
pts_origin = video_pkt->packet->pts;
|
||||
pts_origin = video_pkt->pts;
|
||||
} else {
|
||||
// Fail if there is no video
|
||||
error = true;
|
||||
@@ -413,8 +397,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
// If the recorder is stopped while one of the streams has no
|
||||
// packets, then we must avoid a live-loop and correctly record
|
||||
// the stream having packets.
|
||||
pts_origin = video_pkt ? video_pkt->packet->pts
|
||||
: audio_pkt->packet->pts;
|
||||
pts_origin = video_pkt ? video_pkt->pts : audio_pkt->pts;
|
||||
} else {
|
||||
// We need both video and audio packets to initialize pts_origin
|
||||
continue;
|
||||
@@ -424,17 +407,16 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
assert(pts_origin != AV_NOPTS_VALUE);
|
||||
|
||||
if (video_pkt) {
|
||||
video_pkt->packet->pts -= pts_origin;
|
||||
video_pkt->packet->dts = video_pkt->packet->pts;
|
||||
video_pkt->pts -= pts_origin;
|
||||
video_pkt->dts = video_pkt->pts;
|
||||
|
||||
if (video_pkt_previous) {
|
||||
// we now know the duration of the previous packet
|
||||
video_pkt_previous->packet->duration =
|
||||
video_pkt->packet->pts - video_pkt_previous->packet->pts;
|
||||
video_pkt_previous->duration = video_pkt->pts
|
||||
- video_pkt_previous->pts;
|
||||
|
||||
bool ok = sc_recorder_write_video(recorder,
|
||||
video_pkt_previous->packet);
|
||||
sc_record_packet_delete(video_pkt_previous);
|
||||
bool ok = sc_recorder_write_video(recorder, video_pkt_previous);
|
||||
av_packet_free(&video_pkt_previous);
|
||||
if (!ok) {
|
||||
LOGE("Could not record video packet");
|
||||
error = true;
|
||||
@@ -447,34 +429,34 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
}
|
||||
|
||||
if (audio_pkt) {
|
||||
audio_pkt->packet->pts -= pts_origin;
|
||||
audio_pkt->packet->dts = audio_pkt->packet->pts;
|
||||
audio_pkt->pts -= pts_origin;
|
||||
audio_pkt->dts = audio_pkt->pts;
|
||||
|
||||
bool ok = sc_recorder_write_audio(recorder, audio_pkt->packet);
|
||||
bool ok = sc_recorder_write_audio(recorder, audio_pkt);
|
||||
if (!ok) {
|
||||
LOGE("Could not record audio packet");
|
||||
error = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
sc_record_packet_delete(audio_pkt);
|
||||
av_packet_free(&audio_pkt);
|
||||
audio_pkt = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the last video packet
|
||||
struct sc_record_packet *last = video_pkt_previous;
|
||||
AVPacket *last = video_pkt_previous;
|
||||
if (last) {
|
||||
// assign an arbitrary duration to the last packet
|
||||
last->packet->duration = 100000;
|
||||
bool ok = sc_recorder_write_video(recorder, last->packet);
|
||||
last->duration = 100000;
|
||||
bool ok = sc_recorder_write_video(recorder, last);
|
||||
if (!ok) {
|
||||
// failing to write the last frame is not very serious, no
|
||||
// future frame may depend on it, so the resulting file
|
||||
// will still be valid
|
||||
LOGW("Could not record last packet");
|
||||
}
|
||||
sc_record_packet_delete(last);
|
||||
av_packet_free(&last);
|
||||
}
|
||||
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
@@ -485,10 +467,10 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
|
||||
end:
|
||||
if (video_pkt) {
|
||||
sc_record_packet_delete(video_pkt);
|
||||
av_packet_free(&video_pkt);
|
||||
}
|
||||
if (audio_pkt) {
|
||||
sc_record_packet_delete(audio_pkt);
|
||||
av_packet_free(&audio_pkt);
|
||||
}
|
||||
|
||||
return !error;
|
||||
@@ -533,6 +515,7 @@ run_recorder(void *data) {
|
||||
recorder->stopped = true;
|
||||
// Discard pending packets
|
||||
sc_recorder_queue_clear(&recorder->video_queue);
|
||||
sc_recorder_queue_clear(&recorder->audio_queue);
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
|
||||
if (success) {
|
||||
@@ -593,16 +576,22 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
||||
AVPacket *rec = sc_recorder_packet_ref(packet);
|
||||
if (!rec) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
rec->packet->stream_index = 0;
|
||||
rec->stream_index = 0;
|
||||
|
||||
bool ok = sc_vecdeque_push(&recorder->video_queue, rec);
|
||||
if (!ok) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_queue_push(&recorder->video_queue, next, rec);
|
||||
sc_cond_signal(&recorder->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
@@ -656,16 +645,22 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_record_packet *rec = sc_record_packet_new(packet);
|
||||
AVPacket *rec = sc_recorder_packet_ref(packet);
|
||||
if (!rec) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
rec->packet->stream_index = 1;
|
||||
rec->stream_index = 1;
|
||||
|
||||
bool ok = sc_vecdeque_push(&recorder->audio_queue, rec);
|
||||
if (!ok) {
|
||||
LOG_OOM();
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_queue_push(&recorder->audio_queue, next, rec);
|
||||
sc_cond_signal(&recorder->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&recorder->mutex);
|
||||
@@ -716,8 +711,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
|
||||
|
||||
recorder->audio = audio;
|
||||
|
||||
sc_queue_init(&recorder->video_queue);
|
||||
sc_queue_init(&recorder->audio_queue);
|
||||
sc_vecdeque_init(&recorder->video_queue);
|
||||
sc_vecdeque_init(&recorder->audio_queue);
|
||||
recorder->stopped = false;
|
||||
|
||||
recorder->video_codec = NULL;
|
||||
|
||||
@@ -9,15 +9,10 @@
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
#include "trait/packet_sink.h"
|
||||
#include "util/queue.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
struct sc_record_packet {
|
||||
AVPacket *packet;
|
||||
struct sc_record_packet *next;
|
||||
};
|
||||
|
||||
struct sc_recorder_queue SC_QUEUE(struct sc_record_packet);
|
||||
struct sc_recorder_queue SC_VECDEQUE(AVPacket *);
|
||||
|
||||
struct sc_recorder {
|
||||
struct sc_packet_sink video_packet_sink;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "audio_player.h"
|
||||
#include "controller.h"
|
||||
#include "decoder.h"
|
||||
#include "delay_buffer.h"
|
||||
#include "demuxer.h"
|
||||
#include "events.h"
|
||||
#include "file_pusher.h"
|
||||
@@ -42,13 +43,16 @@ struct scrcpy {
|
||||
struct sc_server server;
|
||||
struct sc_screen screen;
|
||||
struct sc_audio_player audio_player;
|
||||
struct sc_delay_buffer audio_buffer;
|
||||
struct sc_demuxer video_demuxer;
|
||||
struct sc_demuxer audio_demuxer;
|
||||
struct sc_decoder video_decoder;
|
||||
struct sc_decoder audio_decoder;
|
||||
struct sc_recorder recorder;
|
||||
struct sc_delay_buffer display_buffer;
|
||||
#ifdef HAVE_V4L2
|
||||
struct sc_v4l2_sink v4l2_sink;
|
||||
struct sc_delay_buffer v4l2_buffer;
|
||||
#endif
|
||||
struct sc_controller controller;
|
||||
struct sc_file_pusher file_pusher;
|
||||
@@ -218,12 +222,15 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
||||
}
|
||||
|
||||
static void
|
||||
sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||
void *userdata) {
|
||||
sc_video_demuxer_on_ended(struct sc_demuxer *demuxer,
|
||||
enum sc_demuxer_status status, void *userdata) {
|
||||
(void) demuxer;
|
||||
(void) userdata;
|
||||
|
||||
if (eos) {
|
||||
// The device may not decide to disable the video
|
||||
assert(status != SC_DEMUXER_STATUS_DISABLED);
|
||||
|
||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
} else {
|
||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
||||
@@ -231,12 +238,14 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||
}
|
||||
|
||||
static void
|
||||
sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||
void *userdata) {
|
||||
sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
||||
enum sc_demuxer_status status, void *userdata) {
|
||||
(void) demuxer;
|
||||
(void) userdata;
|
||||
|
||||
// Contrary to the video demuxer, keep mirroring if only the audio fails.
|
||||
const struct scrcpy_options *options = userdata;
|
||||
|
||||
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
||||
// (unless --require-audio is set).
|
||||
// 'eos' is true on end-of-stream, including when audio capture is not
|
||||
// possible on the device (so that scrcpy continue to mirror video without
|
||||
// failing).
|
||||
@@ -244,7 +253,9 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, bool eos,
|
||||
// explicitly selected an unknown audio encoder), 'eos' is false and scrcpy
|
||||
// must exit.
|
||||
|
||||
if (!eos) {
|
||||
if (status == SC_DEMUXER_STATUS_ERROR
|
||||
|| (status == SC_DEMUXER_STATUS_DISABLED
|
||||
&& options->require_audio)) {
|
||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -443,7 +454,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.on_ended = sc_audio_demuxer_on_ended,
|
||||
};
|
||||
sc_demuxer_init(&s->audio_demuxer, "audio", s->server.audio_socket,
|
||||
&audio_demuxer_cbs, NULL);
|
||||
&audio_demuxer_cbs, options);
|
||||
}
|
||||
|
||||
bool needs_video_decoder = options->display;
|
||||
@@ -453,11 +464,13 @@ scrcpy(struct scrcpy_options *options) {
|
||||
#endif
|
||||
if (needs_video_decoder) {
|
||||
sc_decoder_init(&s->video_decoder, "video");
|
||||
sc_demuxer_add_sink(&s->video_demuxer, &s->video_decoder.packet_sink);
|
||||
sc_packet_source_add_sink(&s->video_demuxer.packet_source,
|
||||
&s->video_decoder.packet_sink);
|
||||
}
|
||||
if (needs_audio_decoder) {
|
||||
sc_decoder_init(&s->audio_decoder, "audio");
|
||||
sc_demuxer_add_sink(&s->audio_demuxer, &s->audio_decoder.packet_sink);
|
||||
sc_packet_source_add_sink(&s->audio_demuxer.packet_source,
|
||||
&s->audio_decoder.packet_sink);
|
||||
}
|
||||
|
||||
if (options->record_filename) {
|
||||
@@ -476,10 +489,11 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
recorder_started = true;
|
||||
|
||||
sc_demuxer_add_sink(&s->video_demuxer, &s->recorder.video_packet_sink);
|
||||
sc_packet_source_add_sink(&s->video_demuxer.packet_source,
|
||||
&s->recorder.video_packet_sink);
|
||||
if (options->audio) {
|
||||
sc_demuxer_add_sink(&s->audio_demuxer,
|
||||
&s->recorder.audio_packet_sink);
|
||||
sc_packet_source_add_sink(&s->audio_demuxer.packet_source,
|
||||
&s->recorder.audio_packet_sink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +677,6 @@ aoa_hid_end:
|
||||
.mipmaps = options->mipmaps,
|
||||
.fullscreen = options->fullscreen,
|
||||
.start_fps_counter = options->start_fps_counter,
|
||||
.buffering_time = options->display_buffer,
|
||||
};
|
||||
|
||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||
@@ -671,22 +684,45 @@ aoa_hid_end:
|
||||
}
|
||||
screen_initialized = true;
|
||||
|
||||
sc_decoder_add_sink(&s->video_decoder, &s->screen.frame_sink);
|
||||
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
||||
if (options->display_buffer) {
|
||||
sc_delay_buffer_init(&s->display_buffer, options->display_buffer,
|
||||
true);
|
||||
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||
src = &s->display_buffer.frame_source;
|
||||
}
|
||||
|
||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||
|
||||
if (options->audio) {
|
||||
struct sc_frame_source *src = &s->audio_decoder.frame_source;
|
||||
if (options->audio_buffer) {
|
||||
sc_delay_buffer_init(&s->audio_buffer, options->audio_buffer,
|
||||
false);
|
||||
sc_frame_source_add_sink(src, &s->audio_buffer.frame_sink);
|
||||
src = &s->audio_buffer.frame_source;
|
||||
}
|
||||
|
||||
sc_audio_player_init(&s->audio_player);
|
||||
sc_decoder_add_sink(&s->audio_decoder, &s->audio_player.frame_sink);
|
||||
sc_frame_source_add_sink(src, &s->audio_player.frame_sink);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_V4L2
|
||||
if (options->v4l2_device) {
|
||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
||||
info->frame_size, options->v4l2_buffer)) {
|
||||
info->frame_size)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
sc_decoder_add_sink(&s->video_decoder, &s->v4l2_sink.frame_sink);
|
||||
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
||||
if (options->v4l2_buffer) {
|
||||
sc_delay_buffer_init(&s->v4l2_buffer, options->v4l2_buffer, true);
|
||||
sc_frame_source_add_sink(src, &s->v4l2_buffer.frame_sink);
|
||||
src = &s->v4l2_buffer.frame_source;
|
||||
}
|
||||
|
||||
sc_frame_source_add_sink(src, &s->v4l2_sink.frame_sink);
|
||||
|
||||
v4l2_sink_initialized = true;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "events.h"
|
||||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "video_buffer.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define DISPLAY_MARGINS 96
|
||||
@@ -359,30 +358,18 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
static bool
|
||||
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
return sc_video_buffer_push(&screen->vb, frame);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
void *userdata) {
|
||||
(void) vb;
|
||||
struct sc_screen *screen = userdata;
|
||||
bool previous_skipped;
|
||||
bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// event_failed implies previous_skipped (the previous frame may not have
|
||||
// been consumed if the event was not sent)
|
||||
assert(!screen->event_failed || previous_skipped);
|
||||
|
||||
bool need_new_event;
|
||||
if (previous_skipped) {
|
||||
sc_fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||
// this new frame instead, unless the previous event failed
|
||||
need_new_event = screen->event_failed;
|
||||
// this new frame instead
|
||||
} else {
|
||||
need_new_event = true;
|
||||
}
|
||||
|
||||
if (need_new_event) {
|
||||
static SDL_Event new_frame_event = {
|
||||
.type = SC_EVENT_NEW_FRAME,
|
||||
};
|
||||
@@ -391,11 +378,11 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
int ret = SDL_PushEvent(&new_frame_event);
|
||||
if (ret < 0) {
|
||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
||||
screen->event_failed = true;
|
||||
} else {
|
||||
screen->event_failed = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -405,7 +392,6 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->has_frame = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->event_failed = false;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
|
||||
screen->req.x = params->window_x;
|
||||
@@ -415,23 +401,13 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->req.fullscreen = params->fullscreen;
|
||||
screen->req.start_fps_counter = params->start_fps_counter;
|
||||
|
||||
static const struct sc_video_buffer_callbacks cbs = {
|
||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||
};
|
||||
|
||||
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
|
||||
screen);
|
||||
bool ok = sc_frame_buffer_init(&screen->fb);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_video_buffer_start(&screen->vb);
|
||||
if (!ok) {
|
||||
goto error_destroy_video_buffer;
|
||||
}
|
||||
|
||||
if (!sc_fps_counter_init(&screen->fps_counter)) {
|
||||
goto error_stop_and_join_video_buffer;
|
||||
goto error_destroy_frame_buffer;
|
||||
}
|
||||
|
||||
screen->frame_size = params->frame_size;
|
||||
@@ -563,11 +539,8 @@ error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_fps_counter:
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
error_stop_and_join_video_buffer:
|
||||
sc_video_buffer_stop(&screen->vb);
|
||||
sc_video_buffer_join(&screen->vb);
|
||||
error_destroy_video_buffer:
|
||||
sc_video_buffer_destroy(&screen->vb);
|
||||
error_destroy_frame_buffer:
|
||||
sc_frame_buffer_destroy(&screen->fb);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -604,13 +577,11 @@ sc_screen_hide_window(struct sc_screen *screen) {
|
||||
|
||||
void
|
||||
sc_screen_interrupt(struct sc_screen *screen) {
|
||||
sc_video_buffer_stop(&screen->vb);
|
||||
sc_fps_counter_interrupt(&screen->fps_counter);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_join(struct sc_screen *screen) {
|
||||
sc_video_buffer_join(&screen->vb);
|
||||
sc_fps_counter_join(&screen->fps_counter);
|
||||
}
|
||||
|
||||
@@ -624,7 +595,7 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
sc_video_buffer_destroy(&screen->vb);
|
||||
sc_frame_buffer_destroy(&screen->fb);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -730,7 +701,7 @@ update_texture(struct sc_screen *screen, const AVFrame *frame) {
|
||||
static bool
|
||||
sc_screen_update_frame(struct sc_screen *screen) {
|
||||
av_frame_unref(screen->frame);
|
||||
sc_video_buffer_consume(&screen->vb, screen->frame);
|
||||
sc_frame_buffer_consume(&screen->fb, screen->frame);
|
||||
AVFrame *frame = screen->frame;
|
||||
|
||||
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "fps_counter.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "input_manager.h"
|
||||
#include "opengl.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
#include "video_buffer.h"
|
||||
|
||||
struct sc_screen {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
@@ -25,7 +25,7 @@ struct sc_screen {
|
||||
#endif
|
||||
|
||||
struct sc_input_manager im;
|
||||
struct sc_video_buffer vb;
|
||||
struct sc_frame_buffer fb;
|
||||
struct sc_fps_counter fps_counter;
|
||||
|
||||
// The initial requested window properties
|
||||
@@ -59,8 +59,6 @@ struct sc_screen {
|
||||
bool maximized;
|
||||
bool mipmaps;
|
||||
|
||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
||||
|
||||
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
|
||||
// RGUI) must be pressed. This variable tracks the pressed capture key.
|
||||
SDL_Keycode mouse_capture_key_pressed;
|
||||
@@ -95,8 +93,6 @@ struct sc_screen_params {
|
||||
|
||||
bool fullscreen;
|
||||
bool start_fps_counter;
|
||||
|
||||
sc_tick buffering_time;
|
||||
};
|
||||
|
||||
// initialize screen, create window, renderer and texture (window is hidden)
|
||||
|
||||
@@ -752,6 +752,11 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
|
||||
if (is_already_tcpip) {
|
||||
// Nothing to do
|
||||
LOGI("Device already connected via TCP/IP: %s", serial);
|
||||
server->serial = strdup(serial);
|
||||
if (!server->serial) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
59
app/src/trait/frame_source.c
Normal file
59
app/src/trait/frame_source.c
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "frame_source.h"
|
||||
|
||||
void
|
||||
sc_frame_source_init(struct sc_frame_source *source) {
|
||||
source->sink_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
sc_frame_source_add_sink(struct sc_frame_source *source,
|
||||
struct sc_frame_sink *sink) {
|
||||
assert(source->sink_count < SC_FRAME_SOURCE_MAX_SINKS);
|
||||
assert(sink);
|
||||
assert(sink->ops);
|
||||
source->sinks[source->sink_count++] = sink;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_frame_source_sinks_close_firsts(struct sc_frame_source *source,
|
||||
unsigned count) {
|
||||
while (count) {
|
||||
struct sc_frame_sink *sink = source->sinks[--count];
|
||||
sink->ops->close(sink);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_open(struct sc_frame_source *source,
|
||||
const AVCodecContext *ctx) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
sc_frame_source_sinks_close_firsts(source, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_frame_source_sinks_close(struct sc_frame_source *source) {
|
||||
assert(source->sink_count);
|
||||
sc_frame_source_sinks_close_firsts(source, source->sink_count);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_push(struct sc_frame_source *source,
|
||||
const AVFrame *frame) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->push(sink, frame)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
38
app/src/trait/frame_source.h
Normal file
38
app/src/trait/frame_source.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef SC_FRAME_SOURCE_H
|
||||
#define SC_FRAME_SOURCE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "frame_sink.h"
|
||||
|
||||
#define SC_FRAME_SOURCE_MAX_SINKS 2
|
||||
|
||||
/**
|
||||
* Frame source trait
|
||||
*
|
||||
* Component able to send AVFrames should implement this trait.
|
||||
*/
|
||||
struct sc_frame_source {
|
||||
struct sc_frame_sink *sinks[SC_FRAME_SOURCE_MAX_SINKS];
|
||||
unsigned sink_count;
|
||||
};
|
||||
|
||||
void
|
||||
sc_frame_source_init(struct sc_frame_source *source);
|
||||
|
||||
void
|
||||
sc_frame_source_add_sink(struct sc_frame_source *source,
|
||||
struct sc_frame_sink *sink);
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_open(struct sc_frame_source *source,
|
||||
const AVCodecContext *ctx);
|
||||
|
||||
void
|
||||
sc_frame_source_sinks_close(struct sc_frame_source *source);
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_push(struct sc_frame_source *source,
|
||||
const AVFrame *frame);
|
||||
|
||||
#endif
|
||||
70
app/src/trait/packet_source.c
Normal file
70
app/src/trait/packet_source.c
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "packet_source.h"
|
||||
|
||||
void
|
||||
sc_packet_source_init(struct sc_packet_source *source) {
|
||||
source->sink_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
sc_packet_source_add_sink(struct sc_packet_source *source,
|
||||
struct sc_packet_sink *sink) {
|
||||
assert(source->sink_count < SC_PACKET_SOURCE_MAX_SINKS);
|
||||
assert(sink);
|
||||
assert(sink->ops);
|
||||
source->sinks[source->sink_count++] = sink;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_packet_source_sinks_close_firsts(struct sc_packet_source *source,
|
||||
unsigned count) {
|
||||
while (count) {
|
||||
struct sc_packet_sink *sink = source->sinks[--count];
|
||||
sink->ops->close(sink);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_open(struct sc_packet_source *source,
|
||||
const AVCodec *codec) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->open(sink, codec)) {
|
||||
sc_packet_source_sinks_close_firsts(source, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_close(struct sc_packet_source *source) {
|
||||
assert(source->sink_count);
|
||||
sc_packet_source_sinks_close_firsts(source, source->sink_count);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_push(struct sc_packet_source *source,
|
||||
const AVPacket *packet) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->push(sink, packet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_disable(struct sc_packet_source *source) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (sink->ops->disable) {
|
||||
sink->ops->disable(sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
app/src/trait/packet_source.h
Normal file
41
app/src/trait/packet_source.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef SC_PACKET_SOURCE_H
|
||||
#define SC_PACKET_SOURCE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "packet_sink.h"
|
||||
|
||||
#define SC_PACKET_SOURCE_MAX_SINKS 2
|
||||
|
||||
/**
|
||||
* Packet source trait
|
||||
*
|
||||
* Component able to send AVPackets should implement this trait.
|
||||
*/
|
||||
struct sc_packet_source {
|
||||
struct sc_packet_sink *sinks[SC_PACKET_SOURCE_MAX_SINKS];
|
||||
unsigned sink_count;
|
||||
};
|
||||
|
||||
void
|
||||
sc_packet_source_init(struct sc_packet_source *source);
|
||||
|
||||
void
|
||||
sc_packet_source_add_sink(struct sc_packet_source *source,
|
||||
struct sc_packet_sink *sink);
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_open(struct sc_packet_source *source,
|
||||
const AVCodec *codec);
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_close(struct sc_packet_source *source);
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_push(struct sc_packet_source *source,
|
||||
const AVPacket *packet);
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_disable(struct sc_packet_source *source);
|
||||
|
||||
#endif
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#define DEFAULT_TIMEOUT 1000
|
||||
|
||||
#define SC_HID_EVENT_QUEUE_MAX 64
|
||||
|
||||
static void
|
||||
sc_hid_event_log(const struct sc_hid_event *event) {
|
||||
// HID Event: [00] FF FF FF FF...
|
||||
@@ -48,14 +50,20 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event) {
|
||||
bool
|
||||
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||
struct sc_acksync *acksync) {
|
||||
cbuf_init(&aoa->queue);
|
||||
sc_vecdeque_init(&aoa->queue);
|
||||
|
||||
if (!sc_vecdeque_reserve(&aoa->queue, SC_HID_EVENT_QUEUE_MAX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_mutex_init(&aoa->mutex)) {
|
||||
sc_vecdeque_destroy(&aoa->queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_cond_init(&aoa->event_cond)) {
|
||||
sc_mutex_destroy(&aoa->mutex);
|
||||
sc_vecdeque_destroy(&aoa->queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,9 +77,10 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||
void
|
||||
sc_aoa_destroy(struct sc_aoa *aoa) {
|
||||
// Destroy remaining events
|
||||
struct sc_hid_event event;
|
||||
while (cbuf_take(&aoa->queue, &event)) {
|
||||
sc_hid_event_destroy(&event);
|
||||
while (!sc_vecdeque_is_empty(&aoa->queue)) {
|
||||
struct sc_hid_event *event = sc_vecdeque_popref(&aoa->queue);
|
||||
assert(event);
|
||||
sc_hid_event_destroy(event);
|
||||
}
|
||||
|
||||
sc_cond_destroy(&aoa->event_cond);
|
||||
@@ -212,13 +221,19 @@ sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
||||
}
|
||||
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
bool was_empty = cbuf_is_empty(&aoa->queue);
|
||||
bool res = cbuf_push(&aoa->queue, *event);
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
bool full = sc_vecdeque_is_full(&aoa->queue);
|
||||
if (!full) {
|
||||
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||
sc_vecdeque_push_noresize(&aoa->queue, *event);
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
}
|
||||
}
|
||||
// Otherwise (if the queue is full), the event is discarded
|
||||
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
return res;
|
||||
|
||||
return !full;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -227,7 +242,7 @@ run_aoa_thread(void *data) {
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) {
|
||||
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
|
||||
sc_cond_wait(&aoa->event_cond, &aoa->mutex);
|
||||
}
|
||||
if (aoa->stopped) {
|
||||
@@ -235,11 +250,9 @@ run_aoa_thread(void *data) {
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
break;
|
||||
}
|
||||
struct sc_hid_event event;
|
||||
bool non_empty = cbuf_take(&aoa->queue, &event);
|
||||
assert(non_empty);
|
||||
(void) non_empty;
|
||||
|
||||
assert(!sc_vecdeque_is_empty(&aoa->queue));
|
||||
struct sc_hid_event event = sc_vecdeque_pop(&aoa->queue);
|
||||
uint64_t ack_to_wait = event.ack_to_wait;
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#include "usb.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/cbuf.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/tick.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
struct sc_hid_event {
|
||||
uint16_t accessory_id;
|
||||
@@ -27,7 +27,7 @@ sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
|
||||
void
|
||||
sc_hid_event_destroy(struct sc_hid_event *hid_event);
|
||||
|
||||
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
|
||||
struct sc_hid_event_queue SC_VECDEQUE(struct sc_hid_event);
|
||||
|
||||
struct sc_aoa {
|
||||
struct sc_usb *usb;
|
||||
|
||||
@@ -19,8 +19,8 @@ sc_average_push(struct sc_average *avg, float value) {
|
||||
avg->avg = ((avg->count - 1) * avg->avg + value) / avg->count;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_average_get(struct sc_average *avg, float *value) {
|
||||
*value = avg->avg;
|
||||
return avg->count;
|
||||
float
|
||||
sc_average_get(struct sc_average *avg) {
|
||||
assert(avg->count);
|
||||
return avg->avg;
|
||||
}
|
||||
|
||||
@@ -22,15 +22,19 @@ struct sc_average {
|
||||
void
|
||||
sc_average_init(struct sc_average *avg, unsigned range);
|
||||
|
||||
/* Push a new value to update the "rolling" average */
|
||||
/**
|
||||
* Push a new value to update the "rolling" average
|
||||
*/
|
||||
void
|
||||
sc_average_push(struct sc_average *avg, float value);
|
||||
|
||||
/* Get the current average value (if available)
|
||||
/**
|
||||
* Get the current average value
|
||||
*
|
||||
* An average is available if sc_average_push() has been called at least once.
|
||||
* It is an error to call this function if sc_average_push() has not been
|
||||
* called at least once.
|
||||
*/
|
||||
bool
|
||||
sc_average_get(struct sc_average *avg, float *value);
|
||||
float
|
||||
sc_average_get(struct sc_average *avg);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,7 +31,7 @@ sc_bytebuf_destroy(struct sc_bytebuf *buf) {
|
||||
void
|
||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
||||
assert(len);
|
||||
assert(sc_bytebuf_read_remaining(buf) >= len);
|
||||
assert(len <= sc_bytebuf_read_available(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;
|
||||
@@ -51,42 +51,40 @@ sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
||||
void
|
||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
|
||||
assert(len);
|
||||
assert(sc_bytebuf_read_remaining(buf) >= len);
|
||||
assert(len <= sc_bytebuf_read_available(buf));
|
||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
||||
|
||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
|
||||
assert(len);
|
||||
|
||||
size_t max_len = buf->alloc_size - 1;
|
||||
if (len >= max_len) {
|
||||
// Copy only the right-most bytes
|
||||
memcpy(buf->data, from + len - max_len, max_len);
|
||||
buf->tail = 0;
|
||||
buf->head = max_len;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t right_limit = buf->head < buf->tail ? buf->tail : buf->alloc_size;
|
||||
size_t right_len = right_limit - buf->head;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
size_t empty_space = sc_bytebuf_write_remaining(buf);
|
||||
if (len > empty_space) {
|
||||
buf->tail = (buf->tail + len - empty_space) % buf->alloc_size;
|
||||
}
|
||||
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_write_available(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) {
|
||||
@@ -97,20 +95,11 @@ sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
||||
// be called with lock held).
|
||||
|
||||
assert(len < buf->alloc_size - 1);
|
||||
|
||||
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);
|
||||
}
|
||||
sc_bytebuf_write_step0(buf, from, len);
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
|
||||
assert(len <= sc_bytebuf_write_remaining(buf));
|
||||
buf->head = (buf->head + len) % buf->alloc_size;
|
||||
assert(len <= sc_bytebuf_write_available(buf));
|
||||
sc_bytebuf_write_step1(buf, len);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ struct sc_bytebuf {
|
||||
// The actual capacity is (allocated - 1) so that head == tail is
|
||||
// non-ambiguous
|
||||
size_t alloc_size;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
size_t head; // writter cursor
|
||||
size_t tail; // reader cursor
|
||||
// empty: tail == head
|
||||
// full: (tail + 1) % allocated == head
|
||||
};
|
||||
@@ -23,10 +23,10 @@ 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 <= buf->len (it is an error to attempt to read
|
||||
* more bytes than available).
|
||||
* 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 to not change the head.
|
||||
* This function is guaranteed not to write to buf->head.
|
||||
*/
|
||||
void
|
||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
|
||||
@@ -34,8 +34,10 @@ 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 <= buf->len (it is an error to attempt to skip
|
||||
* more bytes than available).
|
||||
* 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 change the head.
|
||||
*
|
||||
* This function is guaranteed to not change the head.
|
||||
*
|
||||
@@ -48,9 +50,10 @@ sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
|
||||
/**
|
||||
* Copy the user-provided array to the bytebuf
|
||||
*
|
||||
* The length of the input array is not restricted:
|
||||
* if len >= sc_bytebuf_write_remaining(buf), then the excessive input bytes
|
||||
* will overwrite the oldest bytes in the buffer.
|
||||
* 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);
|
||||
@@ -58,14 +61,16 @@ 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 <= buf->len (it is an error to attempt to
|
||||
* write more bytes than available).
|
||||
* 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,
|
||||
@@ -83,21 +88,28 @@ sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
|
||||
* It is an error to read more bytes than available.
|
||||
*/
|
||||
static inline size_t
|
||||
sc_bytebuf_read_remaining(struct sc_bytebuf *buf) {
|
||||
sc_bytebuf_read_available(struct sc_bytebuf *buf) {
|
||||
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes which can be written without overwriting
|
||||
* Return the number of bytes which can be written
|
||||
*
|
||||
* It is not an error to write more bytes than the available space, but this
|
||||
* would overwrite the oldest bytes in the buffer.
|
||||
* It is an error to write more bytes than available.
|
||||
*/
|
||||
static inline size_t
|
||||
sc_bytebuf_write_remaining(struct sc_bytebuf *buf) {
|
||||
sc_bytebuf_write_available(struct sc_bytebuf *buf) {
|
||||
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual capacity of the buffer (read available + write available)
|
||||
*/
|
||||
static inline size_t
|
||||
sc_bytebuf_capacity(struct sc_bytebuf *buf) {
|
||||
return buf->alloc_size - 1;
|
||||
}
|
||||
|
||||
void
|
||||
sc_bytebuf_destroy(struct sc_bytebuf *buf);
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// generic circular buffer (bounded queue) implementation
|
||||
#ifndef SC_CBUF_H
|
||||
#define SC_CBUF_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// To define a circular buffer type of 20 ints:
|
||||
// struct cbuf_int CBUF(int, 20);
|
||||
//
|
||||
// data has length CAP + 1 to distinguish empty vs full.
|
||||
#define CBUF(TYPE, CAP) { \
|
||||
TYPE data[(CAP) + 1]; \
|
||||
size_t head; \
|
||||
size_t tail; \
|
||||
}
|
||||
|
||||
#define cbuf_size_(PCBUF) \
|
||||
(sizeof((PCBUF)->data) / sizeof(*(PCBUF)->data))
|
||||
|
||||
#define cbuf_is_empty(PCBUF) \
|
||||
((PCBUF)->head == (PCBUF)->tail)
|
||||
|
||||
#define cbuf_is_full(PCBUF) \
|
||||
(((PCBUF)->head + 1) % cbuf_size_(PCBUF) == (PCBUF)->tail)
|
||||
|
||||
#define cbuf_init(PCBUF) \
|
||||
(void) ((PCBUF)->head = (PCBUF)->tail = 0)
|
||||
|
||||
#define cbuf_push(PCBUF, ITEM) \
|
||||
({ \
|
||||
bool ok = !cbuf_is_full(PCBUF); \
|
||||
if (ok) { \
|
||||
(PCBUF)->data[(PCBUF)->head] = (ITEM); \
|
||||
(PCBUF)->head = ((PCBUF)->head + 1) % cbuf_size_(PCBUF); \
|
||||
} \
|
||||
ok; \
|
||||
})
|
||||
|
||||
#define cbuf_take(PCBUF, PITEM) \
|
||||
({ \
|
||||
bool ok = !cbuf_is_empty(PCBUF); \
|
||||
if (ok) { \
|
||||
*(PITEM) = (PCBUF)->data[(PCBUF)->tail]; \
|
||||
(PCBUF)->tail = ((PCBUF)->tail + 1) % cbuf_size_(PCBUF); \
|
||||
} \
|
||||
ok; \
|
||||
})
|
||||
|
||||
#endif
|
||||
14
app/src/util/memory.c
Normal file
14
app/src/util/memory.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "memory.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
void *
|
||||
sc_allocarray(size_t nmemb, size_t size) {
|
||||
size_t bytes;
|
||||
if (__builtin_mul_overflow(nmemb, size, &bytes)) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
return malloc(bytes);
|
||||
}
|
||||
12
app/src/util/memory.h
Normal file
12
app/src/util/memory.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef SC_MEMORY_H
|
||||
#define SC_MEMORY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* Like calloc(), but without initialization.
|
||||
* Like reallocarray(), but without reallocation.
|
||||
*/
|
||||
void *
|
||||
sc_allocarray(size_t nmemb, size_t size);
|
||||
|
||||
#endif
|
||||
@@ -1,77 +0,0 @@
|
||||
// generic intrusive FIFO queue
|
||||
#ifndef SC_QUEUE_H
|
||||
#define SC_QUEUE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// To define a queue type of "struct foo":
|
||||
// struct queue_foo QUEUE(struct foo);
|
||||
#define SC_QUEUE(TYPE) { \
|
||||
TYPE *first; \
|
||||
TYPE *last; \
|
||||
}
|
||||
|
||||
#define sc_queue_init(PQ) \
|
||||
(void) ((PQ)->first = (PQ)->last = NULL)
|
||||
|
||||
#define sc_queue_is_empty(PQ) \
|
||||
!(PQ)->first
|
||||
|
||||
// NEXTFIELD is the field in the ITEM type used for intrusive linked-list
|
||||
//
|
||||
// For example:
|
||||
// struct foo {
|
||||
// int value;
|
||||
// struct foo *next;
|
||||
// };
|
||||
//
|
||||
// // define the type "struct my_queue"
|
||||
// struct my_queue SC_QUEUE(struct foo);
|
||||
//
|
||||
// struct my_queue queue;
|
||||
// sc_queue_init(&queue);
|
||||
//
|
||||
// struct foo v1 = { .value = 42 };
|
||||
// struct foo v2 = { .value = 27 };
|
||||
//
|
||||
// sc_queue_push(&queue, next, v1);
|
||||
// sc_queue_push(&queue, next, v2);
|
||||
//
|
||||
// struct foo *foo;
|
||||
// sc_queue_take(&queue, next, &foo);
|
||||
// assert(foo->value == 42);
|
||||
// sc_queue_take(&queue, next, &foo);
|
||||
// assert(foo->value == 27);
|
||||
// assert(sc_queue_is_empty(&queue));
|
||||
//
|
||||
|
||||
// push a new item into the queue
|
||||
#define sc_queue_push(PQ, NEXTFIELD, ITEM) \
|
||||
(void) ({ \
|
||||
(ITEM)->NEXTFIELD = NULL; \
|
||||
if (sc_queue_is_empty(PQ)) { \
|
||||
(PQ)->first = (PQ)->last = (ITEM); \
|
||||
} else { \
|
||||
(PQ)->last->NEXTFIELD = (ITEM); \
|
||||
(PQ)->last = (ITEM); \
|
||||
} \
|
||||
})
|
||||
|
||||
// take the next item and remove it from the queue (the queue must not be empty)
|
||||
// the result is stored in *(PITEM)
|
||||
// (without typeof(), we could not store a local variable having the correct
|
||||
// type so that we can "return" it)
|
||||
#define sc_queue_take(PQ, NEXTFIELD, PITEM) \
|
||||
(void) ({ \
|
||||
assert(!sc_queue_is_empty(PQ)); \
|
||||
*(PITEM) = (PQ)->first; \
|
||||
(PQ)->first = (PQ)->first->NEXTFIELD; \
|
||||
})
|
||||
// no need to update (PQ)->last if the queue is left empty:
|
||||
// (PQ)->last is undefined if !(PQ)->first anyway
|
||||
|
||||
#endif
|
||||
379
app/src/util/vecdeque.h
Normal file
379
app/src/util/vecdeque.h
Normal file
@@ -0,0 +1,379 @@
|
||||
#ifndef SC_VECDEQUE_H
|
||||
#define SC_VECDEQUE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/memory.h"
|
||||
|
||||
/**
|
||||
* A double-ended queue implemented with a growable ring buffer.
|
||||
*
|
||||
* Inspired from the Rust VecDeque type:
|
||||
* <https://doc.rust-lang.org/std/collections/struct.VecDeque.html>
|
||||
*/
|
||||
|
||||
/**
|
||||
* VecDeque struct body
|
||||
*
|
||||
* A VecDeque is a dynamic ring-buffer, managed by the sc_vecdeque_* helpers.
|
||||
*
|
||||
* It is generic over the type of its items, so it is implemented via macros.
|
||||
*
|
||||
* To use a VecDeque, a new type must be defined:
|
||||
*
|
||||
* struct vecdeque_int SC_VECDEQUE(int);
|
||||
*
|
||||
* The struct may be anonymous:
|
||||
*
|
||||
* struct SC_VECDEQUE(const char *) names;
|
||||
*
|
||||
* Functions and macros having name ending with '_' are private.
|
||||
*/
|
||||
#define SC_VECDEQUE(type) { \
|
||||
size_t cap; \
|
||||
size_t origin; \
|
||||
size_t size; \
|
||||
type *data; \
|
||||
}
|
||||
|
||||
/**
|
||||
* Static initializer for a VecDeque
|
||||
*/
|
||||
#define SC_VECDEQUE_INITIALIZER { 0, 0, 0, NULL }
|
||||
|
||||
/**
|
||||
* Initialize an empty VecDeque
|
||||
*/
|
||||
#define sc_vecdeque_init(pv) \
|
||||
({ \
|
||||
(pv)->data = NULL; \
|
||||
(pv)->cap = 0; \
|
||||
(pv)->origin = 0; \
|
||||
(pv)->size = 0; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Destroy a VecDeque
|
||||
*/
|
||||
#define sc_vecdeque_destroy(pv) \
|
||||
free((pv)->data)
|
||||
|
||||
/**
|
||||
* Clear a VecDeque
|
||||
*
|
||||
* Remove all items.
|
||||
*/
|
||||
#define sc_vecdeque_clear(pv) \
|
||||
(void) ({ \
|
||||
sc_vecdeque_destroy(pv); \
|
||||
sc_vecdeque_init(pv); \
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns the content size
|
||||
*/
|
||||
#define sc_vecdeque_size(pv) \
|
||||
(pv)->size
|
||||
|
||||
/**
|
||||
* Return whether the VecDeque is empty (i.e. its size is 0)
|
||||
*/
|
||||
#define sc_vecdeque_is_empty(pv) \
|
||||
((pv)->size == 0)
|
||||
|
||||
/**
|
||||
* Return whether the VecDeque is full
|
||||
*
|
||||
* A VecDeque is full when its size equals its current capacity. However, it
|
||||
* does not prevent to push a new item (with sc_vecdeque_push()), since this
|
||||
* will increase its capacity.
|
||||
*/
|
||||
#define sc_vecdeque_is_full(pv) \
|
||||
((pv)->size == (pv)->cap)
|
||||
|
||||
/**
|
||||
* The minimal allocation size, in number of items
|
||||
*
|
||||
* Private.
|
||||
*/
|
||||
#define SC_VECDEQUE_MINCAP_ ((size_t) 10)
|
||||
|
||||
/**
|
||||
* The maximal allocation size, in number of items
|
||||
*
|
||||
* Use SIZE_MAX/2 to fit in ssize_t, and so that cap*1.5 does not overflow.
|
||||
*
|
||||
* Private.
|
||||
*/
|
||||
#define sc_vecdeque_max_cap_(pv) (SIZE_MAX / 2 / sizeof(*(pv)->data))
|
||||
|
||||
/**
|
||||
* Realloc the internal array to a specific capacity
|
||||
*
|
||||
* On reallocation success, update the VecDeque capacity (`*pcap`) and origin
|
||||
* (`*porigin`), and return the reallocated data.
|
||||
*
|
||||
* On reallocation failure, return NULL without any change.
|
||||
*
|
||||
* Private.
|
||||
*
|
||||
* \param ptr the current `data` field of the SC_VECDEQUE to realloc
|
||||
* \param newcap the requested capacity, in number of items
|
||||
* \param item_size the size of one item (the generic type is unknown from this
|
||||
* function)
|
||||
* \param pcap a pointer to the `cap` field of the SC_VECDEQUE [IN/OUT]
|
||||
* \param porigin a pointer to pv->origin (will be read and updated)
|
||||
* \param size the `size` field of the SC_VECDEQUE
|
||||
* \return the new array to assign to the `data` field of the SC_VECDEQUE (if
|
||||
* not NULL)
|
||||
*/
|
||||
static inline void *
|
||||
sc_vecdeque_reallocdata_(void *ptr, size_t newcap, size_t item_size,
|
||||
size_t *pcap, size_t *porigin, size_t size) {
|
||||
|
||||
size_t oldcap = *pcap;
|
||||
size_t oldorigin = *porigin;
|
||||
|
||||
assert(newcap > oldcap); // Could only grow
|
||||
|
||||
if (oldorigin + size <= oldcap) {
|
||||
// The current content will stay in place, just realloc
|
||||
//
|
||||
// As an example, here is the content of a ring-buffer (oldcap=10)
|
||||
// before the realloc:
|
||||
//
|
||||
// _ _ 2 3 4 5 6 7 _ _
|
||||
// ^
|
||||
// origin
|
||||
//
|
||||
// It is resized (newcap=15), e.g. with sc_vecdeque_reserve():
|
||||
//
|
||||
// _ _ 2 3 4 5 6 7 _ _ _ _ _ _ _
|
||||
// ^
|
||||
// origin
|
||||
|
||||
void *newptr = reallocarray(ptr, newcap, item_size);
|
||||
if (!newptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*pcap = newcap;
|
||||
return newptr;
|
||||
}
|
||||
|
||||
// Copy the current content to the new array
|
||||
//
|
||||
// As an example, here is the content of a ring-buffer (oldcap=10) before
|
||||
// the realloc:
|
||||
//
|
||||
// 5 6 7 _ _ 0 1 2 3 4
|
||||
// ^
|
||||
// origin
|
||||
//
|
||||
// It is resized (newcap=15), e.g. with sc_vecdeque_reserve():
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 _ _ _ _ _ _ _
|
||||
// ^
|
||||
// origin
|
||||
|
||||
assert(size);
|
||||
void *newptr = sc_allocarray(newcap, item_size);
|
||||
if (!newptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t right_len = MIN(size, oldcap - oldorigin);
|
||||
assert(right_len);
|
||||
memcpy(newptr, ptr + (oldorigin * item_size), right_len * item_size);
|
||||
|
||||
if (size > right_len) {
|
||||
memcpy(newptr + (right_len * item_size), ptr,
|
||||
(size - right_len) * item_size);
|
||||
}
|
||||
|
||||
free(ptr);
|
||||
|
||||
*pcap = newcap;
|
||||
*porigin = 0;
|
||||
return newptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro to realloc the internal data to a new capacity
|
||||
*
|
||||
* Private.
|
||||
*
|
||||
* \retval true on success
|
||||
* \retval false on allocation failure (the VecDeque is left untouched)
|
||||
*/
|
||||
#define sc_vecdeque_realloc_(pv, newcap) \
|
||||
({ \
|
||||
void *p = sc_vecdeque_reallocdata_((pv)->data, newcap, \
|
||||
sizeof(*(pv)->data), &(pv)->cap, \
|
||||
&(pv)->origin, (pv)->size); \
|
||||
if (p) { \
|
||||
(pv)->data = p; \
|
||||
} \
|
||||
(bool) p; \
|
||||
});
|
||||
|
||||
static inline size_t
|
||||
sc_vecdeque_growsize_(size_t value)
|
||||
{
|
||||
/* integer multiplication by 1.5 */
|
||||
return value + (value >> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the capacity of the VecDeque to at least `mincap`
|
||||
*
|
||||
* \param pv a pointer to the VecDeque
|
||||
* \param mincap (`size_t`) the requested capacity
|
||||
* \retval true on success
|
||||
* \retval false on allocation failure (the VecDeque is left untouched)
|
||||
*/
|
||||
#define sc_vecdeque_reserve(pv, mincap) \
|
||||
({ \
|
||||
assert(mincap <= sc_vecdeque_max_cap_(pv)); \
|
||||
bool ok; \
|
||||
/* avoid to allocate tiny arrays (< SC_VECDEQUE_MINCAP_) */ \
|
||||
size_t mincap_ = MAX(mincap, SC_VECDEQUE_MINCAP_); \
|
||||
if (mincap_ <= (pv)->cap) { \
|
||||
/* nothing to do */ \
|
||||
ok = true; \
|
||||
} else if (mincap_ <= sc_vecdeque_max_cap_(pv)) { \
|
||||
/* not too big */ \
|
||||
size_t newsize = sc_vecdeque_growsize_((pv)->cap); \
|
||||
newsize = CLAMP(newsize, mincap_, sc_vecdeque_max_cap_(pv)); \
|
||||
ok = sc_vecdeque_realloc_(pv, newsize); \
|
||||
} else { \
|
||||
ok = false; \
|
||||
} \
|
||||
ok; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Automatically grow the VecDeque capacity
|
||||
*
|
||||
* Private.
|
||||
*
|
||||
* \retval true on success
|
||||
* \retval false on allocation failure (the VecDeque is left untouched)
|
||||
*/
|
||||
#define sc_vecdeque_grow_(pv) \
|
||||
({ \
|
||||
bool ok; \
|
||||
if ((pv)->cap < sc_vecdeque_max_cap_(pv)) { \
|
||||
size_t newsize = sc_vecdeque_growsize_((pv)->cap); \
|
||||
newsize = CLAMP(newsize, SC_VECDEQUE_MINCAP_, \
|
||||
sc_vecdeque_max_cap_(pv)); \
|
||||
ok = sc_vecdeque_realloc_(pv, newsize); \
|
||||
} else { \
|
||||
ok = false; \
|
||||
} \
|
||||
ok; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Grow the VecDeque capacity if it is full
|
||||
*
|
||||
* Private.
|
||||
*
|
||||
* \retval true on success
|
||||
* \retval false on allocation failure (the VecDeque is left untouched)
|
||||
*/
|
||||
#define sc_vecdeque_grow_if_needed_(pv) \
|
||||
(!sc_vecdeque_is_full(pv) || sc_vecdeque_grow_(pv))
|
||||
|
||||
/**
|
||||
* Push an uninitialized item, and return a pointer to it
|
||||
*
|
||||
* It does not attempt to resize the VecDeque. It is an error to this function
|
||||
* if the VecDeque is full.
|
||||
*
|
||||
* This function may not fail. It returns a valid non-NULL pointer to the
|
||||
* uninitialized item just pushed.
|
||||
*/
|
||||
#define sc_vecdeque_push_hole_noresize(pv) \
|
||||
({ \
|
||||
assert(!sc_vecdeque_is_full(pv)); \
|
||||
++(pv)->size; \
|
||||
&(pv)->data[((pv)->origin + (pv)->size - 1) % (pv)->cap]; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Push an uninitialized item, and return a pointer to it
|
||||
*
|
||||
* If the VecDeque is full, it is resized.
|
||||
*
|
||||
* This function returns either a valid non-nULL pointer to the uninitialized
|
||||
* item just pushed, or NULL on reallocation failure.
|
||||
*/
|
||||
#define sc_vecdeque_push_hole(pv) \
|
||||
(sc_vecdeque_grow_if_needed_(pv) ? \
|
||||
sc_vecdeque_push_hole_noresize(pv) : NULL)
|
||||
|
||||
/**
|
||||
* Push an item
|
||||
*
|
||||
* It does not attempt to resize the VecDeque. It is an error to this function
|
||||
* if the VecDeque is full.
|
||||
*
|
||||
* This function may not fail.
|
||||
*/
|
||||
#define sc_vecdeque_push_noresize(pv, item) \
|
||||
(void) ({ \
|
||||
assert(!sc_vecdeque_is_full(pv)); \
|
||||
++(pv)->size; \
|
||||
(pv)->data[((pv)->origin + (pv)->size - 1) % (pv)->cap] = item; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Push an item
|
||||
*
|
||||
* If the VecDeque is full, it is resized.
|
||||
*
|
||||
* \retval true on success
|
||||
* \retval false on allocation failure (the VecDeque is left untouched)
|
||||
*/
|
||||
#define sc_vecdeque_push(pv, item) \
|
||||
({ \
|
||||
bool ok = sc_vecdeque_grow_if_needed_(pv); \
|
||||
if (ok) { \
|
||||
sc_vecdeque_push_noresize(pv, item); \
|
||||
} \
|
||||
ok; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Pop an item and return a pointer to it (still in the VecDeque)
|
||||
*
|
||||
* Returning a pointer allows the caller to destroy it in place without copy
|
||||
* (especially if the item type is big).
|
||||
*
|
||||
* It is an error to call this function if the VecDeque is empty.
|
||||
*/
|
||||
#define sc_vecdeque_popref(pv) \
|
||||
({ \
|
||||
assert(!sc_vecdeque_is_empty(pv)); \
|
||||
size_t pos = (pv)->origin; \
|
||||
(pv)->origin = ((pv)->origin + 1) % (pv)->cap; \
|
||||
--(pv)->size; \
|
||||
&(pv)->data[pos]; \
|
||||
})
|
||||
|
||||
/**
|
||||
* Pop an item and returns it
|
||||
*
|
||||
* It is an error to call this function if the VecDeque is empty.
|
||||
*/
|
||||
#define sc_vecdeque_pop(pv) \
|
||||
(*sc_vecdeque_popref(pv))
|
||||
|
||||
#endif
|
||||
@@ -118,7 +118,7 @@ static inline void *
|
||||
sc_vector_reallocdata_(void *ptr, size_t count, size_t size,
|
||||
size_t *restrict pcap, size_t *restrict psize)
|
||||
{
|
||||
void *p = realloc(ptr, count * size);
|
||||
void *p = reallocarray(ptr, count, size);
|
||||
if (!p) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ run_v4l2_sink(void *data) {
|
||||
vs->has_frame = false;
|
||||
sc_mutex_unlock(&vs->mutex);
|
||||
|
||||
sc_video_buffer_consume(&vs->vb, vs->frame);
|
||||
sc_frame_buffer_consume(&vs->fb, vs->frame);
|
||||
|
||||
bool ok = encode_and_write_frame(vs, vs->frame);
|
||||
av_frame_unref(vs->frame);
|
||||
@@ -141,42 +141,19 @@ run_v4l2_sink(void *data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
void *userdata) {
|
||||
(void) vb;
|
||||
struct sc_v4l2_sink *vs = userdata;
|
||||
|
||||
if (!previous_skipped) {
|
||||
sc_mutex_lock(&vs->mutex);
|
||||
vs->has_frame = true;
|
||||
sc_cond_signal(&vs->cond);
|
||||
sc_mutex_unlock(&vs->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
(void) ctx;
|
||||
|
||||
static const struct sc_video_buffer_callbacks cbs = {
|
||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||
};
|
||||
|
||||
bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs);
|
||||
bool ok = sc_frame_buffer_init(&vs->fb);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_video_buffer_start(&vs->vb);
|
||||
if (!ok) {
|
||||
goto error_video_buffer_destroy;
|
||||
}
|
||||
|
||||
ok = sc_mutex_init(&vs->mutex);
|
||||
if (!ok) {
|
||||
goto error_video_buffer_stop_and_join;
|
||||
goto error_frame_buffer_destroy;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&vs->cond);
|
||||
@@ -301,11 +278,8 @@ error_cond_destroy:
|
||||
sc_cond_destroy(&vs->cond);
|
||||
error_mutex_destroy:
|
||||
sc_mutex_destroy(&vs->mutex);
|
||||
error_video_buffer_stop_and_join:
|
||||
sc_video_buffer_stop(&vs->vb);
|
||||
sc_video_buffer_join(&vs->vb);
|
||||
error_video_buffer_destroy:
|
||||
sc_video_buffer_destroy(&vs->vb);
|
||||
error_frame_buffer_destroy:
|
||||
sc_frame_buffer_destroy(&vs->fb);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -317,10 +291,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||
sc_cond_signal(&vs->cond);
|
||||
sc_mutex_unlock(&vs->mutex);
|
||||
|
||||
sc_video_buffer_stop(&vs->vb);
|
||||
|
||||
sc_thread_join(&vs->thread, NULL);
|
||||
sc_video_buffer_join(&vs->vb);
|
||||
|
||||
av_packet_free(&vs->packet);
|
||||
av_frame_free(&vs->frame);
|
||||
@@ -330,12 +301,25 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||
avformat_free_context(vs->format_ctx);
|
||||
sc_cond_destroy(&vs->cond);
|
||||
sc_mutex_destroy(&vs->mutex);
|
||||
sc_video_buffer_destroy(&vs->vb);
|
||||
sc_frame_buffer_destroy(&vs->fb);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||
return sc_video_buffer_push(&vs->vb, frame);
|
||||
bool previous_skipped;
|
||||
bool ok = sc_frame_buffer_push(&vs->fb, frame, &previous_skipped);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!previous_skipped) {
|
||||
sc_mutex_lock(&vs->mutex);
|
||||
vs->has_frame = true;
|
||||
sc_cond_signal(&vs->cond);
|
||||
sc_mutex_unlock(&vs->mutex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -358,7 +342,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
|
||||
bool
|
||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||
struct sc_size frame_size, sc_tick buffering_time) {
|
||||
struct sc_size frame_size) {
|
||||
vs->device_name = strdup(device_name);
|
||||
if (!vs->device_name) {
|
||||
LOGE("Could not strdup v4l2 device name");
|
||||
@@ -366,7 +350,6 @@ sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||
}
|
||||
|
||||
vs->frame_size = frame_size;
|
||||
vs->buffering_time = buffering_time;
|
||||
|
||||
static const struct sc_frame_sink_ops ops = {
|
||||
.open = sc_v4l2_frame_sink_open,
|
||||
|
||||
@@ -8,19 +8,18 @@
|
||||
|
||||
#include "coords.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "video_buffer.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "util/tick.h"
|
||||
|
||||
struct sc_v4l2_sink {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
struct sc_video_buffer vb;
|
||||
struct sc_frame_buffer fb;
|
||||
AVFormatContext *format_ctx;
|
||||
AVCodecContext *encoder_ctx;
|
||||
|
||||
char *device_name;
|
||||
struct sc_size frame_size;
|
||||
sc_tick buffering_time;
|
||||
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
@@ -35,7 +34,7 @@ struct sc_v4l2_sink {
|
||||
|
||||
bool
|
||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||
struct sc_size frame_size, sc_tick buffering_time);
|
||||
struct sc_size frame_size);
|
||||
|
||||
void
|
||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
#include "video_buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_BUFFERING_NDEBUG // comment to debug
|
||||
|
||||
static struct sc_video_buffer_frame *
|
||||
sc_video_buffer_frame_new(const AVFrame *frame) {
|
||||
struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame));
|
||||
if (!vb_frame) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vb_frame->frame = av_frame_alloc();
|
||||
if (!vb_frame->frame) {
|
||||
LOG_OOM();
|
||||
free(vb_frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (av_frame_ref(vb_frame->frame, frame)) {
|
||||
av_frame_free(&vb_frame->frame);
|
||||
free(vb_frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return vb_frame;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_video_buffer_frame_delete(struct sc_video_buffer_frame *vb_frame) {
|
||||
av_frame_unref(vb_frame->frame);
|
||||
av_frame_free(&vb_frame->frame);
|
||||
free(vb_frame);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) {
|
||||
bool previous_skipped;
|
||||
bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run_buffering(void *data) {
|
||||
struct sc_video_buffer *vb = data;
|
||||
|
||||
assert(vb->buffering_time > 0);
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&vb->b.mutex);
|
||||
|
||||
while (!vb->b.stopped && sc_queue_is_empty(&vb->b.queue)) {
|
||||
sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex);
|
||||
}
|
||||
|
||||
if (vb->b.stopped) {
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
struct sc_video_buffer_frame *vb_frame;
|
||||
sc_queue_take(&vb->b.queue, next, &vb_frame);
|
||||
|
||||
sc_tick max_deadline = sc_tick_now() + vb->buffering_time;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_TO_US(vb_frame->frame->pts);
|
||||
|
||||
bool timed_out = false;
|
||||
while (!vb->b.stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&vb->b.clock, pts)
|
||||
+ vb->buffering_time;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline);
|
||||
}
|
||||
|
||||
if (vb->b.stopped) {
|
||||
sc_video_buffer_frame_delete(vb_frame);
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, vb_frame->push_date, sc_tick_now());
|
||||
#endif
|
||||
|
||||
sc_video_buffer_offer(vb, vb_frame->frame);
|
||||
|
||||
sc_video_buffer_frame_delete(vb_frame);
|
||||
}
|
||||
|
||||
stopped:
|
||||
// Flush queue
|
||||
while (!sc_queue_is_empty(&vb->b.queue)) {
|
||||
struct sc_video_buffer_frame *vb_frame;
|
||||
sc_queue_take(&vb->b.queue, next, &vb_frame);
|
||||
sc_video_buffer_frame_delete(vb_frame);
|
||||
}
|
||||
|
||||
LOGD("Buffering thread ended");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time,
|
||||
const struct sc_video_buffer_callbacks *cbs,
|
||||
void *cbs_userdata) {
|
||||
bool ok = sc_frame_buffer_init(&vb->fb);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(buffering_time >= 0);
|
||||
if (buffering_time) {
|
||||
ok = sc_mutex_init(&vb->b.mutex);
|
||||
if (!ok) {
|
||||
sc_frame_buffer_destroy(&vb->fb);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&vb->b.queue_cond);
|
||||
if (!ok) {
|
||||
sc_mutex_destroy(&vb->b.mutex);
|
||||
sc_frame_buffer_destroy(&vb->fb);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&vb->b.wait_cond);
|
||||
if (!ok) {
|
||||
sc_cond_destroy(&vb->b.queue_cond);
|
||||
sc_mutex_destroy(&vb->b.mutex);
|
||||
sc_frame_buffer_destroy(&vb->fb);
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_clock_init(&vb->b.clock);
|
||||
sc_queue_init(&vb->b.queue);
|
||||
}
|
||||
|
||||
assert(cbs);
|
||||
assert(cbs->on_new_frame);
|
||||
|
||||
vb->buffering_time = buffering_time;
|
||||
vb->cbs = cbs;
|
||||
vb->cbs_userdata = cbs_userdata;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_video_buffer_start(struct sc_video_buffer *vb) {
|
||||
if (vb->buffering_time) {
|
||||
bool ok =
|
||||
sc_thread_create(&vb->b.thread, run_buffering, "scrcpy-vbuf", vb);
|
||||
if (!ok) {
|
||||
LOGE("Could not start buffering thread");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_video_buffer_stop(struct sc_video_buffer *vb) {
|
||||
if (vb->buffering_time) {
|
||||
sc_mutex_lock(&vb->b.mutex);
|
||||
vb->b.stopped = true;
|
||||
sc_cond_signal(&vb->b.queue_cond);
|
||||
sc_cond_signal(&vb->b.wait_cond);
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_video_buffer_join(struct sc_video_buffer *vb) {
|
||||
if (vb->buffering_time) {
|
||||
sc_thread_join(&vb->b.thread, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_video_buffer_destroy(struct sc_video_buffer *vb) {
|
||||
sc_frame_buffer_destroy(&vb->fb);
|
||||
if (vb->buffering_time) {
|
||||
sc_cond_destroy(&vb->b.wait_cond);
|
||||
sc_cond_destroy(&vb->b.queue_cond);
|
||||
sc_mutex_destroy(&vb->b.mutex);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) {
|
||||
if (!vb->buffering_time) {
|
||||
// No buffering
|
||||
return sc_video_buffer_offer(vb, frame);
|
||||
}
|
||||
|
||||
sc_mutex_lock(&vb->b.mutex);
|
||||
|
||||
sc_tick pts = SC_TICK_FROM_US(frame->pts);
|
||||
sc_clock_update(&vb->b.clock, sc_tick_now(), pts);
|
||||
sc_cond_signal(&vb->b.wait_cond);
|
||||
|
||||
if (vb->b.clock.count == 1) {
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
// First frame, offer it immediately, for two reasons:
|
||||
// - not to delay the opening of the scrcpy window
|
||||
// - the buffering estimation needs at least two clock points, so it
|
||||
// could not handle the first frame
|
||||
return sc_video_buffer_offer(vb, frame);
|
||||
}
|
||||
|
||||
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
|
||||
if (!vb_frame) {
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
vb_frame->push_date = sc_tick_now();
|
||||
#endif
|
||||
sc_queue_push(&vb->b.queue, next, vb_frame);
|
||||
sc_cond_signal(&vb->b.queue_cond);
|
||||
|
||||
sc_mutex_unlock(&vb->b.mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst) {
|
||||
sc_frame_buffer_consume(&vb->fb, dst);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
#ifndef SC_VIDEO_BUFFER_H
|
||||
#define SC_VIDEO_BUFFER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "util/queue.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/tick.h"
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
struct sc_video_buffer_frame {
|
||||
AVFrame *frame;
|
||||
struct sc_video_buffer_frame *next;
|
||||
#ifndef NDEBUG
|
||||
sc_tick push_date;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct sc_video_buffer_frame_queue SC_QUEUE(struct sc_video_buffer_frame);
|
||||
|
||||
struct sc_video_buffer {
|
||||
struct sc_frame_buffer fb;
|
||||
|
||||
sc_tick buffering_time;
|
||||
|
||||
// only if buffering_time > 0
|
||||
struct {
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond queue_cond;
|
||||
sc_cond wait_cond;
|
||||
|
||||
struct sc_clock clock;
|
||||
struct sc_video_buffer_frame_queue queue;
|
||||
bool stopped;
|
||||
} b; // buffering
|
||||
|
||||
const struct sc_video_buffer_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_video_buffer_callbacks {
|
||||
void (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time,
|
||||
const struct sc_video_buffer_callbacks *cbs,
|
||||
void *cbs_userdata);
|
||||
|
||||
bool
|
||||
sc_video_buffer_start(struct sc_video_buffer *vb);
|
||||
|
||||
void
|
||||
sc_video_buffer_stop(struct sc_video_buffer *vb);
|
||||
|
||||
void
|
||||
sc_video_buffer_join(struct sc_video_buffer *vb);
|
||||
|
||||
void
|
||||
sc_video_buffer_destroy(struct sc_video_buffer *vb);
|
||||
|
||||
bool
|
||||
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame);
|
||||
|
||||
void
|
||||
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst);
|
||||
|
||||
#endif
|
||||
@@ -13,23 +13,23 @@ void test_bytebuf_simple(void) {
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 5);
|
||||
assert(sc_bytebuf_read_available(&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_read_remaining(&buf) == 7);
|
||||
assert(sc_bytebuf_read_available(&buf) == 7);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 8);
|
||||
assert(sc_bytebuf_read_available(&buf) == 8);
|
||||
|
||||
sc_bytebuf_read(&buf, &data[4], 8);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
assert(sc_bytebuf_read_available(&buf) == 0);
|
||||
|
||||
data[12] = '\0';
|
||||
assert(!strcmp((char *) data, "hello world!"));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
assert(sc_bytebuf_read_available(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
@@ -42,58 +42,31 @@ void test_bytebuf_boundaries(void) {
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||
assert(sc_bytebuf_read_available(&buf) == 6);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
assert(sc_bytebuf_read_available(&buf) == 12);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 18);
|
||||
assert(sc_bytebuf_read_available(&buf) == 18);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "hello hel", 9));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
assert(sc_bytebuf_read_available(&buf) == 9);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 14);
|
||||
assert(sc_bytebuf_read_available(&buf) == 14);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 15);
|
||||
assert(sc_bytebuf_read_available(&buf) == 15);
|
||||
|
||||
sc_bytebuf_skip(&buf, 3);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
assert(sc_bytebuf_read_available(&buf) == 12);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 12);
|
||||
data[12] = '\0';
|
||||
assert(!strcmp((char *) data, "hello world!"));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
|
||||
void test_bytebuf_overwrite(void) {
|
||||
struct sc_bytebuf buf;
|
||||
uint8_t data[10];
|
||||
|
||||
bool ok = sc_bytebuf_init(&buf, 10); // so actual capacity is 9
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "abcdef", sizeof("abcdef") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "lo abcdef", 9));
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "a very big buffer",
|
||||
sizeof("a very big buffer") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "ig buffer", 9));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
assert(sc_bytebuf_read_available(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
@@ -106,37 +79,37 @@ void test_bytebuf_two_steps_write(void) {
|
||||
assert(ok);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 6);
|
||||
assert(sc_bytebuf_read_available(&buf) == 6);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
assert(sc_bytebuf_read_available(&buf) == 12);
|
||||
|
||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12); // write not committed yet
|
||||
assert(sc_bytebuf_read_available(&buf) == 12); // write not committed yet
|
||||
|
||||
sc_bytebuf_read(&buf, data, 9);
|
||||
assert(!strncmp((char *) data, "hello hel", 3));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 3);
|
||||
assert(sc_bytebuf_read_available(&buf) == 3);
|
||||
|
||||
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9);
|
||||
assert(sc_bytebuf_read_available(&buf) == 9);
|
||||
|
||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 9); // write not committed yet
|
||||
assert(sc_bytebuf_read_available(&buf) == 9); // write not committed yet
|
||||
|
||||
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 14);
|
||||
assert(sc_bytebuf_read_available(&buf) == 14);
|
||||
|
||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 15);
|
||||
assert(sc_bytebuf_read_available(&buf) == 15);
|
||||
|
||||
sc_bytebuf_skip(&buf, 3);
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 12);
|
||||
assert(sc_bytebuf_read_available(&buf) == 12);
|
||||
|
||||
sc_bytebuf_read(&buf, data, 12);
|
||||
data[12] = '\0';
|
||||
assert(!strcmp((char *) data, "hello world!"));
|
||||
assert(sc_bytebuf_read_remaining(&buf) == 0);
|
||||
assert(sc_bytebuf_read_available(&buf) == 0);
|
||||
|
||||
sc_bytebuf_destroy(&buf);
|
||||
}
|
||||
@@ -147,7 +120,6 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
test_bytebuf_simple();
|
||||
test_bytebuf_boundaries();
|
||||
test_bytebuf_overwrite();
|
||||
test_bytebuf_two_steps_write();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/cbuf.h"
|
||||
|
||||
struct int_queue CBUF(int, 32);
|
||||
|
||||
static void test_cbuf_empty(void) {
|
||||
struct int_queue queue;
|
||||
cbuf_init(&queue);
|
||||
|
||||
assert(cbuf_is_empty(&queue));
|
||||
|
||||
bool push_ok = cbuf_push(&queue, 42);
|
||||
assert(push_ok);
|
||||
assert(!cbuf_is_empty(&queue));
|
||||
|
||||
int item;
|
||||
bool take_ok = cbuf_take(&queue, &item);
|
||||
assert(take_ok);
|
||||
assert(cbuf_is_empty(&queue));
|
||||
|
||||
bool take_empty_ok = cbuf_take(&queue, &item);
|
||||
assert(!take_empty_ok); // the queue is empty
|
||||
}
|
||||
|
||||
static void test_cbuf_full(void) {
|
||||
struct int_queue queue;
|
||||
cbuf_init(&queue);
|
||||
|
||||
assert(!cbuf_is_full(&queue));
|
||||
|
||||
// fill the queue
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
bool ok = cbuf_push(&queue, i);
|
||||
assert(ok);
|
||||
}
|
||||
bool ok = cbuf_push(&queue, 42);
|
||||
assert(!ok); // the queue if full
|
||||
|
||||
int item;
|
||||
bool take_ok = cbuf_take(&queue, &item);
|
||||
assert(take_ok);
|
||||
assert(!cbuf_is_full(&queue));
|
||||
}
|
||||
|
||||
static void test_cbuf_push_take(void) {
|
||||
struct int_queue queue;
|
||||
cbuf_init(&queue);
|
||||
|
||||
bool push1_ok = cbuf_push(&queue, 42);
|
||||
assert(push1_ok);
|
||||
|
||||
bool push2_ok = cbuf_push(&queue, 35);
|
||||
assert(push2_ok);
|
||||
|
||||
int item;
|
||||
|
||||
bool take1_ok = cbuf_take(&queue, &item);
|
||||
assert(take1_ok);
|
||||
assert(item == 42);
|
||||
|
||||
bool take2_ok = cbuf_take(&queue, &item);
|
||||
assert(take2_ok);
|
||||
assert(item == 35);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_cbuf_empty();
|
||||
test_cbuf_full();
|
||||
test_cbuf_push_take();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/queue.h"
|
||||
|
||||
struct foo {
|
||||
int value;
|
||||
struct foo *next;
|
||||
};
|
||||
|
||||
static void test_queue(void) {
|
||||
struct my_queue SC_QUEUE(struct foo) queue;
|
||||
sc_queue_init(&queue);
|
||||
|
||||
assert(sc_queue_is_empty(&queue));
|
||||
|
||||
struct foo v1 = { .value = 42 };
|
||||
struct foo v2 = { .value = 27 };
|
||||
|
||||
sc_queue_push(&queue, next, &v1);
|
||||
sc_queue_push(&queue, next, &v2);
|
||||
|
||||
struct foo *foo;
|
||||
|
||||
assert(!sc_queue_is_empty(&queue));
|
||||
sc_queue_take(&queue, next, &foo);
|
||||
assert(foo->value == 42);
|
||||
|
||||
assert(!sc_queue_is_empty(&queue));
|
||||
sc_queue_take(&queue, next, &foo);
|
||||
assert(foo->value == 27);
|
||||
|
||||
assert(sc_queue_is_empty(&queue));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_queue();
|
||||
return 0;
|
||||
}
|
||||
197
app/tests/test_vecdeque.c
Normal file
197
app/tests/test_vecdeque.c
Normal file
@@ -0,0 +1,197 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
#define pr(pv) \
|
||||
({ \
|
||||
fprintf(stderr, "cap=%lu origin=%lu size=%lu\n", (pv)->cap, (pv)->origin, (pv)->size); \
|
||||
for (size_t i = 0; i < (pv)->cap; ++i) \
|
||||
fprintf(stderr, "%d ", (pv)->data[i]); \
|
||||
fprintf(stderr, "\n"); \
|
||||
})
|
||||
|
||||
static void test_vecdeque_push_pop(void) {
|
||||
struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER;
|
||||
|
||||
assert(sc_vecdeque_is_empty(&vdq));
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
bool ok = sc_vecdeque_push(&vdq, 5);
|
||||
assert(ok);
|
||||
assert(sc_vecdeque_size(&vdq) == 1);
|
||||
|
||||
ok = sc_vecdeque_push(&vdq, 12);
|
||||
assert(ok);
|
||||
assert(sc_vecdeque_size(&vdq) == 2);
|
||||
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == 5);
|
||||
assert(sc_vecdeque_size(&vdq) == 1);
|
||||
|
||||
ok = sc_vecdeque_push(&vdq, 7);
|
||||
assert(ok);
|
||||
assert(sc_vecdeque_size(&vdq) == 2);
|
||||
|
||||
int *p = sc_vecdeque_popref(&vdq);
|
||||
assert(p);
|
||||
assert(*p == 12);
|
||||
assert(sc_vecdeque_size(&vdq) == 1);
|
||||
|
||||
v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == 7);
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
assert(sc_vecdeque_is_empty(&vdq));
|
||||
|
||||
sc_vecdeque_destroy(&vdq);
|
||||
}
|
||||
|
||||
static void test_vecdeque_reserve(void) {
|
||||
struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER;
|
||||
|
||||
bool ok = sc_vecdeque_reserve(&vdq, 20);
|
||||
assert(ok);
|
||||
assert(vdq.cap == 20);
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
for (size_t i = 0; i < 20; ++i) {
|
||||
ok = sc_vecdeque_push(&vdq, i);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 20);
|
||||
|
||||
// It is now full
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == i);
|
||||
}
|
||||
assert(sc_vecdeque_size(&vdq) == 15);
|
||||
|
||||
for (int i = 20; i < 25; ++i) {
|
||||
ok = sc_vecdeque_push(&vdq, i);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 20);
|
||||
assert(vdq.cap == 20);
|
||||
|
||||
// Now, the content wraps around the ring buffer:
|
||||
// 20 21 22 23 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
||||
// ^
|
||||
// origin
|
||||
|
||||
// It is now full, let's reserve some space
|
||||
ok = sc_vecdeque_reserve(&vdq, 30);
|
||||
assert(ok);
|
||||
assert(vdq.cap == 30);
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 20);
|
||||
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
// We should retrieve the items we inserted in order
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == i + 5);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
sc_vecdeque_destroy(&vdq);
|
||||
}
|
||||
|
||||
static void test_vecdeque_grow() {
|
||||
struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER;
|
||||
|
||||
bool ok = sc_vecdeque_reserve(&vdq, 20);
|
||||
assert(ok);
|
||||
assert(vdq.cap == 20);
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
ok = sc_vecdeque_push(&vdq, i);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 500);
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == i);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 400);
|
||||
|
||||
for (int i = 500; i < 1000; ++i) {
|
||||
ok = sc_vecdeque_push(&vdq, i);
|
||||
assert(ok);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 900);
|
||||
|
||||
for (int i = 100; i < 1000; ++i) {
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == i);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
sc_vecdeque_destroy(&vdq);
|
||||
}
|
||||
|
||||
static void test_vecdeque_push_hole() {
|
||||
struct SC_VECDEQUE(int) vdq = SC_VECDEQUE_INITIALIZER;
|
||||
|
||||
bool ok = sc_vecdeque_reserve(&vdq, 20);
|
||||
assert(ok);
|
||||
assert(vdq.cap == 20);
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
int *p = sc_vecdeque_push_hole(&vdq);
|
||||
assert(p);
|
||||
*p = i * 10;
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 20);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == i * 10);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 10);
|
||||
|
||||
for (int i = 20; i < 30; ++i) {
|
||||
int *p = sc_vecdeque_push_hole(&vdq);
|
||||
assert(p);
|
||||
*p = i * 10;
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 20);
|
||||
|
||||
for (int i = 10; i < 30; ++i) {
|
||||
int v = sc_vecdeque_pop(&vdq);
|
||||
assert(v == i * 10);
|
||||
}
|
||||
|
||||
assert(sc_vecdeque_size(&vdq) == 0);
|
||||
|
||||
sc_vecdeque_destroy(&vdq);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_vecdeque_push_pop();
|
||||
test_vecdeque_reserve();
|
||||
test_vecdeque_grow();
|
||||
test_vecdeque_push_hole();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -183,6 +183,8 @@ public final class AudioEncoder {
|
||||
thread = new Thread(() -> {
|
||||
try {
|
||||
encode();
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
} catch (IOException e) {
|
||||
Ln.e("Audio encoding error", e);
|
||||
} finally {
|
||||
@@ -247,7 +249,7 @@ public final class AudioEncoder {
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void encode() throws IOException {
|
||||
public void encode() throws IOException, ConfigurationException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||
streamer.writeDisableStream(false);
|
||||
@@ -259,7 +261,6 @@ public final class AudioEncoder {
|
||||
|
||||
boolean mediaCodecStarted = false;
|
||||
boolean recorderStarted = false;
|
||||
boolean configurationError = false;
|
||||
try {
|
||||
Codec codec = streamer.getCodec();
|
||||
mediaCodec = createMediaCodec(codec, encoderName);
|
||||
@@ -279,7 +280,9 @@ public final class AudioEncoder {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
Ln.e("Failed to start audio capture");
|
||||
Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy.");
|
||||
throw new ConfigurationException("Unsupported audio capture");
|
||||
// Continue to mirror without audio (configurationError is left to false)
|
||||
streamer.writeDisableStream(false);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
stopWorkaroundAndroid11();
|
||||
@@ -320,15 +323,14 @@ public final class AudioEncoder {
|
||||
|
||||
waitEnded();
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
// Notify the error to scrcpy to make it exit
|
||||
configurationError = true;
|
||||
// Notify the error to make scrcpy exit
|
||||
streamer.writeDisableStream(true);
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Notify the client that the audio could not be captured
|
||||
streamer.writeDisableStream(false);
|
||||
throw e;
|
||||
} finally {
|
||||
if (!recorderStarted) {
|
||||
// Notify the client that the audio could not be captured
|
||||
streamer.writeDisableStream(configurationError);
|
||||
}
|
||||
|
||||
// Cleanup everything (either at the end or on error at any step of the initialization)
|
||||
if (mediaCodecThread != null) {
|
||||
Looper looper = mediaCodecThread.getLooper();
|
||||
|
||||
Reference in New Issue
Block a user