Skip to content

Commit 77b620e

Browse files
committed
Merge branch 'record' into dev (#292)
Record screen to file
2 parents b5c64c0 + 22ff03f commit 77b620e

File tree

15 files changed

+408
-25
lines changed

15 files changed

+408
-25
lines changed

app/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ src = [
1212
'src/input_manager.c',
1313
'src/lock_util.c',
1414
'src/net.c',
15+
'src/recorder.c',
1516
'src/scrcpy.c',
1617
'src/screen.c',
1718
'src/server.c',

app/src/buffer_util.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,14 @@ static inline void buffer_write32be(Uint8 *buf, Uint32 value) {
1515
buf[3] = value;
1616
}
1717

18+
static inline Uint32 buffer_read32be(Uint8 *buf) {
19+
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
20+
}
21+
22+
static inline Uint64 buffer_read64be(Uint8 *buf) {
23+
Uint32 msb = buffer_read32be(buf);
24+
Uint32 lsb = buffer_read32be(&buf[4]);
25+
return ((Uint64) msb << 32) | lsb;
26+
}
27+
1828
#endif

app/src/decoder.c

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,119 @@
11
#include "decoder.h"
22

33
#include <libavformat/avformat.h>
4+
#include <libavutil/time.h>
5+
#include <SDL2/SDL_assert.h>
46
#include <SDL2/SDL_events.h>
57
#include <SDL2/SDL_mutex.h>
68
#include <SDL2/SDL_thread.h>
79
#include <unistd.h>
810

911
#include "config.h"
12+
#include "buffer_util.h"
1013
#include "events.h"
1114
#include "frames.h"
1215
#include "lock_util.h"
1316
#include "log.h"
17+
#include "recorder.h"
1418

1519
#define BUFSIZE 0x10000
1620

17-
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
21+
#define HEADER_SIZE 12
22+
#define NO_PTS UINT64_C(-1)
23+
24+
static struct frame_meta *frame_meta_new(uint64_t pts) {
25+
struct frame_meta *meta = malloc(sizeof(*meta));
26+
if (!meta) {
27+
return meta;
28+
}
29+
meta->pts = pts;
30+
meta->next = NULL;
31+
return meta;
32+
}
33+
34+
static void frame_meta_delete(struct frame_meta *frame_meta) {
35+
free(frame_meta);
36+
}
37+
38+
static SDL_bool receiver_state_push_meta(struct receiver_state *state,
39+
uint64_t pts) {
40+
struct frame_meta *frame_meta = frame_meta_new(pts);
41+
if (!frame_meta) {
42+
return SDL_FALSE;
43+
}
44+
45+
// append to the list
46+
// (iterate to find the last item, in practice the list should be tiny)
47+
struct frame_meta **p = &state->frame_meta_queue;
48+
while (*p) {
49+
p = &(*p)->next;
50+
}
51+
*p = frame_meta;
52+
return SDL_TRUE;
53+
}
54+
55+
static uint64_t receiver_state_take_meta(struct receiver_state *state) {
56+
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
57+
SDL_assert(frame_meta); // must not be empty
58+
uint64_t pts = frame_meta->pts;
59+
state->frame_meta_queue = frame_meta->next; // remove the item
60+
frame_meta_delete(frame_meta);
61+
return pts;
62+
}
63+
64+
static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
65+
struct decoder *decoder = opaque;
66+
struct receiver_state *state = &decoder->receiver_state;
67+
68+
// The video stream contains raw packets, without time information. When we
69+
// record, we retrieve the timestamps separately, from a "meta" header
70+
// added by the server before each raw packet.
71+
//
72+
// The "meta" header length is 12 bytes:
73+
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
74+
// <-------------> <-----> <-----------------------------...
75+
// PTS packet raw packet
76+
// size
77+
//
78+
// It is followed by <packet_size> bytes containing the packet/frame.
79+
80+
if (!state->remaining) {
81+
#define HEADER_SIZE 12
82+
uint8_t header[HEADER_SIZE];
83+
ssize_t ret = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
84+
if (ret <= 0) {
85+
return ret;
86+
}
87+
// no partial read (net_recv_all())
88+
SDL_assert_release(ret == HEADER_SIZE);
89+
90+
uint64_t pts = buffer_read64be(header);
91+
state->remaining = buffer_read32be(&header[8]);
92+
93+
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
94+
LOGE("Could not store PTS for recording");
95+
// we cannot save the PTS, the recording would be broken
96+
return -1;
97+
}
98+
}
99+
100+
SDL_assert(state->remaining);
101+
102+
if (buf_size > state->remaining)
103+
buf_size = state->remaining;
104+
105+
ssize_t ret = net_recv(decoder->video_socket, buf, buf_size);
106+
if (ret <= 0) {
107+
return ret;
108+
}
109+
110+
SDL_assert(state->remaining >= ret);
111+
state->remaining -= ret;
112+
113+
return ret;
114+
}
115+
116+
static int read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
18117
struct decoder *decoder = opaque;
19118
return net_recv(decoder->video_socket, buf, buf_size);
20119
}
@@ -70,7 +169,15 @@ static int run_decoder(void *data) {
70169
goto run_finally_free_format_ctx;
71170
}
72171

73-
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, decoder, read_packet, NULL, NULL);
172+
// initialize the receiver state
173+
decoder->receiver_state.frame_meta_queue = NULL;
174+
decoder->receiver_state.remaining = 0;
175+
176+
// if recording is enabled, a "header" is sent between raw packets
177+
int (*read_packet)(void *, uint8_t *, int) =
178+
decoder->recorder ? read_packet_with_meta : read_raw_packet;
179+
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, decoder,
180+
read_packet, NULL, NULL);
74181
if (!avio_ctx) {
75182
LOGC("Could not allocate avio context");
76183
// avformat_open_input takes ownership of 'buffer'
@@ -86,6 +193,12 @@ static int run_decoder(void *data) {
86193
goto run_finally_free_avio_ctx;
87194
}
88195

196+
if (decoder->recorder &&
197+
!recorder_open(decoder->recorder, codec)) {
198+
LOGE("Could not open recorder");
199+
goto run_finally_close_input;
200+
}
201+
89202
AVPacket packet;
90203
av_init_packet(&packet);
91204
packet.data = NULL;
@@ -125,6 +238,23 @@ static int run_decoder(void *data) {
125238
packet.data += len;
126239
}
127240
#endif
241+
242+
if (decoder->recorder) {
243+
// we retrieve the PTS in order they were received, so they will
244+
// be assigned to the correct frame
245+
uint64_t pts = receiver_state_take_meta(&decoder->receiver_state);
246+
packet.pts = pts;
247+
packet.dts = pts;
248+
249+
// no need to rescale with av_packet_rescale_ts(), the timestamps
250+
// are in microseconds both in input and output
251+
if (!recorder_write(decoder->recorder, &packet)) {
252+
LOGE("Could not write frame to output file");
253+
av_packet_unref(&packet);
254+
goto run_quit;
255+
}
256+
}
257+
128258
av_packet_unref(&packet);
129259

130260
if (avio_ctx->eof_reached) {
@@ -135,6 +265,10 @@ static int run_decoder(void *data) {
135265
LOGD("End of frames");
136266

137267
run_quit:
268+
if (decoder->recorder) {
269+
recorder_close(decoder->recorder);
270+
}
271+
run_finally_close_input:
138272
avformat_close_input(&format_ctx);
139273
run_finally_free_avio_ctx:
140274
av_freep(&avio_ctx);
@@ -149,9 +283,11 @@ static int run_decoder(void *data) {
149283
return 0;
150284
}
151285

152-
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
286+
void decoder_init(struct decoder *decoder, struct frames *frames,
287+
socket_t video_socket, struct recorder *recorder) {
153288
decoder->frames = frames;
154289
decoder->video_socket = video_socket;
290+
decoder->recorder = recorder;
155291
}
156292

157293
SDL_bool decoder_start(struct decoder *decoder) {
@@ -162,7 +298,6 @@ SDL_bool decoder_start(struct decoder *decoder) {
162298
LOGC("Could not start decoder thread");
163299
return SDL_FALSE;
164300
}
165-
166301
return SDL_TRUE;
167302
}
168303

app/src/decoder.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@
44
#include <SDL2/SDL_stdinc.h>
55
#include <SDL2/SDL_thread.h>
66

7+
#include "common.h"
78
#include "net.h"
89

910
struct frames;
1011

12+
struct frame_meta {
13+
uint64_t pts;
14+
struct frame_meta *next;
15+
};
16+
1117
struct decoder {
1218
struct frames *frames;
1319
socket_t video_socket;
1420
SDL_Thread *thread;
1521
SDL_mutex *mutex;
22+
struct recorder *recorder;
23+
struct receiver_state {
24+
// meta (in order) for frames not consumed yet
25+
struct frame_meta *frame_meta_queue;
26+
size_t remaining; // remaining bytes to receive for the current frame
27+
} receiver_state;
1628
};
1729

18-
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket);
30+
void decoder_init(struct decoder *decoder, struct frames *frames,
31+
socket_t video_socket, struct recorder *recoder);
1932
SDL_bool decoder_start(struct decoder *decoder);
2033
void decoder_stop(struct decoder *decoder);
2134
void decoder_join(struct decoder *decoder);

app/src/main.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
struct args {
1212
const char *serial;
1313
const char *crop;
14+
const char *record_filename;
1415
SDL_bool fullscreen;
1516
SDL_bool help;
1617
SDL_bool version;
@@ -53,6 +54,9 @@ static void usage(const char *arg0) {
5354
" Set the TCP port the client listens on.\n"
5455
" Default is %d.\n"
5556
"\n"
57+
" -r, --record file.mp4\n"
58+
" Record screen to file.\n"
59+
"\n"
5660
" -s, --serial\n"
5761
" The device serial number. Mandatory only if several devices\n"
5862
" are connected to adb.\n"
@@ -208,13 +212,14 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
208212
{"help", no_argument, NULL, 'h'},
209213
{"max-size", required_argument, NULL, 'm'},
210214
{"port", required_argument, NULL, 'p'},
215+
{"record", required_argument, NULL, 'r'},
211216
{"serial", required_argument, NULL, 's'},
212217
{"show-touches", no_argument, NULL, 't'},
213218
{"version", no_argument, NULL, 'v'},
214219
{NULL, 0, NULL, 0 },
215220
};
216221
int c;
217-
while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) {
222+
while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tv", long_options, NULL)) != -1) {
218223
switch (c) {
219224
case 'b':
220225
if (!parse_bit_rate(optarg, &args->bit_rate)) {
@@ -240,6 +245,9 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
240245
return SDL_FALSE;
241246
}
242247
break;
248+
case 'r':
249+
args->record_filename = optarg;
250+
break;
243251
case 's':
244252
args->serial = optarg;
245253
break;
@@ -273,6 +281,7 @@ int main(int argc, char *argv[]) {
273281
struct args args = {
274282
.serial = NULL,
275283
.crop = NULL,
284+
.record_filename = NULL,
276285
.help = SDL_FALSE,
277286
.version = SDL_FALSE,
278287
.show_touches = SDL_FALSE,
@@ -310,6 +319,7 @@ int main(int argc, char *argv[]) {
310319
.serial = args.serial,
311320
.crop = args.crop,
312321
.port = args.port,
322+
.record_filename = args.record_filename,
313323
.max_size = args.max_size,
314324
.bit_rate = args.bit_rate,
315325
.show_touches = args.show_touches,

0 commit comments

Comments
 (0)