Skip to content

Commit 35d9185

Browse files
committed
Record asynchronously
The record file was written from the stream thread. As a consequence, any blocking I/O to write the file delayed the decoder. For maximum performance even when recording is enabled, send (refcounted) packets to a separate recording thread.
1 parent 63af7fb commit 35d9185

File tree

3 files changed

+233
-8
lines changed

3 files changed

+233
-8
lines changed

app/src/recorder.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "compat.h"
77
#include "config.h"
8+
#include "lock_util.h"
89
#include "log.h"
910

1011
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@@ -26,6 +27,82 @@ find_muxer(const char *name) {
2627
return oformat;
2728
}
2829

30+
static struct record_packet *
31+
record_packet_new(const AVPacket *packet) {
32+
struct record_packet *rec = SDL_malloc(sizeof(*rec));
33+
if (!rec) {
34+
return NULL;
35+
}
36+
if (av_packet_ref(&rec->packet, packet)) {
37+
SDL_free(rec);
38+
return NULL;
39+
}
40+
rec->next = NULL;
41+
return rec;
42+
}
43+
44+
static void
45+
record_packet_delete(struct record_packet *rec) {
46+
av_packet_unref(&rec->packet);
47+
SDL_free(rec);
48+
}
49+
50+
static void
51+
recorder_queue_init(struct recorder_queue *queue) {
52+
queue->first = NULL;
53+
// queue->last is undefined if queue->first == NULL
54+
}
55+
56+
static inline bool
57+
recorder_queue_is_empty(struct recorder_queue *queue) {
58+
return !queue->first;
59+
}
60+
61+
static bool
62+
recorder_queue_push(struct recorder_queue *queue, const AVPacket *packet) {
63+
struct record_packet *rec = record_packet_new(packet);
64+
if (!rec) {
65+
LOGC("Could not allocate record packet");
66+
return false;
67+
}
68+
rec->next = NULL;
69+
70+
if (recorder_queue_is_empty(queue)) {
71+
queue->first = queue->last = rec;
72+
} else {
73+
// chain rec after the (current) last packet
74+
queue->last->next = rec;
75+
// the last packet is now rec
76+
queue->last = rec;
77+
}
78+
return true;
79+
}
80+
81+
static inline struct record_packet *
82+
recorder_queue_take(struct recorder_queue *queue) {
83+
SDL_assert(!recorder_queue_is_empty(queue));
84+
85+
struct record_packet *rec = queue->first;
86+
SDL_assert(rec);
87+
88+
queue->first = rec->next;
89+
// no need to update queue->last if the queue is left empty:
90+
// queue->last is undefined if queue->first == NULL
91+
92+
return rec;
93+
}
94+
95+
static void
96+
recorder_queue_clear(struct recorder_queue *queue) {
97+
struct record_packet *rec = queue->first;
98+
while (rec) {
99+
struct record_packet *current = rec;
100+
rec = rec->next;
101+
record_packet_delete(current);
102+
}
103+
queue->first = NULL;
104+
}
105+
29106
bool
30107
recorder_init(struct recorder *recorder,
31108
const char *filename,
@@ -37,6 +114,24 @@ recorder_init(struct recorder *recorder,
37114
return false;
38115
}
39116

117+
recorder->mutex = SDL_CreateMutex();
118+
if (!recorder->mutex) {
119+
LOGC("Could not create mutex");
120+
SDL_free(recorder->filename);
121+
return false;
122+
}
123+
124+
recorder->queue_cond = SDL_CreateCond();
125+
if (!recorder->queue_cond) {
126+
LOGC("Could not create cond");
127+
SDL_DestroyMutex(recorder->mutex);
128+
SDL_free(recorder->filename);
129+
return false;
130+
}
131+
132+
recorder_queue_init(&recorder->queue);
133+
recorder->stopped = false;
134+
recorder->failed = false;
40135
recorder->format = format;
41136
recorder->declared_frame_size = declared_frame_size;
42137
recorder->header_written = false;
@@ -46,6 +141,8 @@ recorder_init(struct recorder *recorder,
46141

47142
void
48143
recorder_destroy(struct recorder *recorder) {
144+
SDL_DestroyCond(recorder->queue_cond);
145+
SDL_DestroyMutex(recorder->mutex);
49146
SDL_free(recorder->filename);
50147
}
51148

@@ -186,3 +283,90 @@ recorder_write(struct recorder *recorder, AVPacket *packet) {
186283
recorder_rescale_packet(recorder, packet);
187284
return av_write_frame(recorder->ctx, packet) >= 0;
188285
}
286+
287+
static int
288+
run_recorder(void *data) {
289+
struct recorder *recorder = data;
290+
291+
for (;;) {
292+
mutex_lock(recorder->mutex);
293+
294+
while (!recorder->stopped &&
295+
recorder_queue_is_empty(&recorder->queue)) {
296+
cond_wait(recorder->queue_cond, recorder->mutex);
297+
}
298+
299+
// if stopped is set, continue to process the remaining events (to
300+
// finish the recording) before actually stopping
301+
302+
if (recorder->stopped && recorder_queue_is_empty(&recorder->queue)) {
303+
mutex_unlock(recorder->mutex);
304+
break;
305+
}
306+
307+
struct record_packet *rec = recorder_queue_take(&recorder->queue);
308+
309+
mutex_unlock(recorder->mutex);
310+
311+
bool ok = recorder_write(recorder, &rec->packet);
312+
record_packet_delete(rec);
313+
if (!ok) {
314+
LOGE("Could not record packet");
315+
316+
mutex_lock(recorder->mutex);
317+
recorder->failed = true;
318+
// discard pending packets
319+
recorder_queue_clear(&recorder->queue);
320+
mutex_unlock(recorder->mutex);
321+
break;
322+
}
323+
324+
}
325+
326+
LOGD("Recorder thread ended");
327+
328+
return 0;
329+
}
330+
331+
bool
332+
recorder_start(struct recorder *recorder) {
333+
LOGD("Starting recorder thread");
334+
335+
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
336+
if (!recorder->thread) {
337+
LOGC("Could not start recorder thread");
338+
return false;
339+
}
340+
341+
return true;
342+
}
343+
344+
void
345+
recorder_stop(struct recorder *recorder) {
346+
mutex_lock(recorder->mutex);
347+
recorder->stopped = true;
348+
cond_signal(recorder->queue_cond);
349+
mutex_unlock(recorder->mutex);
350+
}
351+
352+
void
353+
recorder_join(struct recorder *recorder) {
354+
SDL_WaitThread(recorder->thread, NULL);
355+
}
356+
357+
bool
358+
recorder_push(struct recorder *recorder, const AVPacket *packet) {
359+
mutex_lock(recorder->mutex);
360+
SDL_assert(!recorder->stopped);
361+
362+
if (recorder->failed) {
363+
// reject any new packet (this will stop the stream)
364+
return false;
365+
}
366+
367+
bool ok = recorder_queue_push(&recorder->queue, packet);
368+
cond_signal(recorder->queue_cond);
369+
370+
mutex_unlock(recorder->mutex);
371+
return ok;
372+
}

app/src/recorder.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include <stdbool.h>
55
#include <libavformat/avformat.h>
6+
#include <SDL2/SDL_mutex.h>
7+
#include <SDL2/SDL_thread.h>
68

79
#include "common.h"
810

@@ -11,12 +13,29 @@ enum recorder_format {
1113
RECORDER_FORMAT_MKV,
1214
};
1315

16+
struct record_packet {
17+
AVPacket packet;
18+
struct record_packet *next;
19+
};
20+
21+
struct recorder_queue {
22+
struct record_packet *first;
23+
struct record_packet *last; // undefined if first is NULL
24+
};
25+
1426
struct recorder {
1527
char *filename;
1628
enum recorder_format format;
1729
AVFormatContext *ctx;
1830
struct size declared_frame_size;
1931
bool header_written;
32+
33+
SDL_Thread *thread;
34+
SDL_mutex *mutex;
35+
SDL_cond *queue_cond;
36+
bool stopped; // set on recorder_stop() by the stream reader
37+
bool failed; // set on packet write failure
38+
struct recorder_queue queue;
2039
};
2140

2241
bool
@@ -33,6 +52,15 @@ void
3352
recorder_close(struct recorder *recorder);
3453

3554
bool
36-
recorder_write(struct recorder *recorder, AVPacket *packet);
55+
recorder_start(struct recorder *recorder);
56+
57+
void
58+
recorder_stop(struct recorder *recorder);
59+
60+
void
61+
recorder_join(struct recorder *recorder);
62+
63+
bool
64+
recorder_push(struct recorder *recorder, const AVPacket *packet);
3765

3866
#endif

app/src/stream.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ notify_stopped(void) {
7171

7272
static bool
7373
process_config_packet(struct stream *stream, AVPacket *packet) {
74-
if (stream->recorder && !recorder_write(stream->recorder, packet)) {
74+
if (stream->recorder && !recorder_push(stream->recorder, packet)) {
7575
LOGE("Could not send config packet to recorder");
7676
return false;
7777
}
@@ -87,8 +87,8 @@ process_frame(struct stream *stream, AVPacket *packet) {
8787
if (stream->recorder) {
8888
packet->dts = packet->pts;
8989

90-
if (!recorder_write(stream->recorder, packet)) {
91-
LOGE("Could not write frame to output file");
90+
if (!recorder_push(stream->recorder, packet)) {
91+
LOGE("Could not send packet to recorder");
9292
return false;
9393
}
9494
}
@@ -201,15 +201,22 @@ run_stream(void *data) {
201201
goto finally_free_codec_ctx;
202202
}
203203

204-
if (stream->recorder && !recorder_open(stream->recorder, codec)) {
205-
LOGE("Could not open recorder");
206-
goto finally_close_decoder;
204+
if (stream->recorder) {
205+
if (!recorder_open(stream->recorder, codec)) {
206+
LOGE("Could not open recorder");
207+
goto finally_close_decoder;
208+
}
209+
210+
if (!recorder_start(stream->recorder)) {
211+
LOGE("Could not start recorder");
212+
goto finally_close_recorder;
213+
}
207214
}
208215

209216
stream->parser = av_parser_init(AV_CODEC_ID_H264);
210217
if (!stream->parser) {
211218
LOGE("Could not initialize parser");
212-
goto finally_close_recorder;
219+
goto finally_stop_and_join_recorder;
213220
}
214221

215222
// We must only pass complete frames to av_parser_parse2()!
@@ -239,6 +246,12 @@ run_stream(void *data) {
239246
}
240247

241248
av_parser_close(stream->parser);
249+
finally_stop_and_join_recorder:
250+
if (stream->recorder) {
251+
recorder_stop(stream->recorder);
252+
LOGI("Finishing recording...");
253+
recorder_join(stream->recorder);
254+
}
242255
finally_close_recorder:
243256
if (stream->recorder) {
244257
recorder_close(stream->recorder);

0 commit comments

Comments
 (0)