-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathrecord.h
310 lines (283 loc) · 8.81 KB
/
record.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/** phiola: executor: 'record' command
2023, Simon Zolin */
static int rec_help()
{
help_info_write("\
Record audio:\n\
`phiola record` -o OUTPUT [OPTIONS]\n\
\n\
Options:\n\
`-audio` STRING Audio library name (e.g. alsa)\n\
`-device` NUMBER Capture device number\n\
`-exclusive` Open device in exclusive mode (WASAPI)\n\
`-loopback` Loopback mode (\"record what you hear\") (WASAPI)\n\
Note: '-device NUMBER' specifies Playback device and not Capture device.\n\
`-buffer` NUMBER Length (in msec) of the capture buffer\n\
`-aformat` FORMAT Audio sample format:\n\
int8 | int16 | int24 | int32 | float32\n\
`-rate` NUMBER Sample rate\n\
`-channels` NUMBER Channels number\n\
\n\
`-split` TIME Create new output file periodically\n\
[[HH:]MM:]SS[.MSC]\n\
`-until` TIME Stop at time\n\
[[HH:]MM:]SS[.MSC]\n\
\n\
`-danorm` \"OPTIONS\" Apply Dynamic Audio Normalizer filter. Options:\n\
`frame` Integer\n\
`size` Integer\n\
`peak` Float\n\
`max-amp` Float\n\
`target-rms` Float\n\
`compress` Float\n\
`-gain` NUMBER Gain/attenuation in dB\n\
\n\
`-aac_profile` STRING AAC profile:\n\
`LC` AAC-LC\n\
`HE` AAC-HE\n\
`HE2` AAC-HEv2\n\
`-aac_quality` NUMBER AAC encoding quality:\n\
1..5 (VBR) or 8..800 (CBR, kbit/s)\n\
`-opus_quality` NUMBER Opus encoding bitrate:\n\
6..510 (VBR)\n\
`-opus_mode` CHAR Opus mode:\n\
`a` Audio (default)\n\
`v` VOIP\n\
`-vorbis_quality` NUMBER\n\
Vorbis encoding quality:\n\
0..10\n\
\n\
`-meta` NAME=VALUE Meta data\n\
.mp3 supports: album, albumartist, artist, comment, date, genre, picture, publisher, title, tracknumber, tracktotal.\n\
.mp4 supports: album, albumartist, artist, comment, composer, copyright, date, discnumber, genre, lyrics, title, tracknumber.\n\
.flac, .ogg support tags of any name, but the use of MP3/MP4-compatible names is recommended.\n\
\n\
`-out` FILE Output file name\n\
`@stdout` Write to standard output\n\
The encoder is selected automatically from the given file extension:\n\
.m4a AAC\n\
.ogg Vorbis\n\
.opus Opus\n\
.flac FLAC\n\
.wav PCM\n\
Supports runtime variable expansion:\n\
`@nowdate` Current date\n\
`@nowtime` Current time\n\
`@counter` Sequentially incremented number\n\
`-force` Overwrite output file\n\
\n\
`-remote` Listen for incoming remote commands\n\
");
x->exit_code = 0;
return 1;
}
struct cmd_rec {
char* audio_module;
const char* aac_profile;
const char* audio;
const char* danorm;
const char* opus_mode;
const char* output;
ffvec meta;
int gain;
u_char exclusive;
u_char force;
u_char loopback;
u_char remote;
uint aac_q;
uint aformat;
uint buffer;
uint channels;
uint device;
uint opus_mode_n;
uint opus_q;
uint rate;
uint split;
uint vorbis_q;
uint64 until;
};
#ifdef FF_WIN
/** Start the track that generates silence so that
WASAPI loopback recording won't be paused when there's nothing else playing. */
static void rec_silence_track(struct cmd_rec *r)
{
const phi_track_if *track = x->core->track;
struct phi_track_conf c = {
.oaudio = {
.device_index = r->device,
},
};
phi_track *t = track->create(&c);
struct phi_af af = {
.format = PHI_PCM_16,
.rate = 48000,
.channels = 2,
};
t->oaudio.format = af;
if (!track->filter(t, x->core->mod("afilter.silence-gen"), 0)
|| !track->filter(t, x->core->mod("wasapi.play"), 0)) {
track->close(t);
return;
}
track->start(t);
}
#else
static void rec_silence_track(struct cmd_rec *r) {}
#endif
static int rec_action(struct cmd_rec *r)
{
struct phi_track_conf c = {
.iaudio = {
.format = {
.format = r->aformat,
.rate = r->rate,
.channels = r->channels,
},
.device_index = r->device,
.exclusive = r->exclusive,
.loopback = r->loopback,
.buf_time = r->buffer,
},
.split_msec = r->split,
.until_msec = r->until,
.afilter = {
.gain_db = r->gain,
.danorm = r->danorm,
},
.oaudio = {
.format = {
.format = r->aformat,
.rate = r->rate,
.channels = r->channels,
},
},
.aac = {
.profile = r->aac_profile[0],
.quality = r->aac_q,
},
.vorbis.quality = (r->vorbis_q) ? (r->vorbis_q + 1) * 10 : 0,
.opus = {
.bitrate = r->opus_q,
.mode = r->opus_mode_n,
},
.ofile = {
.name = ffsz_dup(r->output),
.overwrite = r->force,
},
};
const phi_track_if *track = x->core->track;
phi_track *t = track->create(&c);
const char *input = "core.auto-rec";
if (r->audio) {
r->audio_module = ffsz_allocfmt("%s.rec%Z", r->audio);
input = r->audio_module;
}
const char *output = (x->stdout_busy) ? "core.stdout" : "core.file-write";
if (!track->filter(t, &phi_guard, 0)
|| !track->filter(t, x->core->mod(input), 0)
|| !track->filter(t, x->core->mod("afilter.until"), 0)
|| !track->filter(t, x->core->mod("afilter.rtpeak"), 0)
|| !track->filter(t, x->core->mod("tui.rec"), 0)
|| (r->danorm
&& !track->filter(t, x->core->mod("af-danorm.f"), 0))
|| !track->filter(t, x->core->mod("afilter.gain"), 0)
|| !track->filter(t, x->core->mod("afilter.auto-conv"), 0)
|| (r->split
&& !track->filter(t, x->core->mod("afilter.split"), 0))
|| (!r->split
&& (!track->filter(t, x->core->mod("format.auto-write"), 0)
|| !track->filter(t, x->core->mod(output), 0)))
) {
track->close(t);
return -1;
}
cmd_meta_set(&t->meta, &r->meta);
ffvec_free(&r->meta);
x->mode_record = 1;
t->output.allow_async = 1;
track->start(t);
if (r->remote) {
const phi_remote_sv_if *rsv = x->core->mod("remote.server");
if (rsv->start(NULL))
return -1;
}
if (r->loopback)
rec_silence_track(r);
return 0;
}
static int rec_check(struct cmd_rec *r)
{
if (!r->output)
return _ffargs_err(&x->cmd, 1, "please specify output file name with '-out FILE'");
if (!(r->aac_profile = cmd_aac_profile(r->aac_profile)))
return _ffargs_err(&x->cmd, 1, "-aac_profile: incorrect value");
if ((int)(r->opus_mode_n = cmd_opus_mode(r->opus_mode)) < 0)
return _ffargs_err(&x->cmd, 1, "-opus_mode: incorrect value");
ffstr name;
ffpath_splitname_str(FFSTR_Z(r->output), &name, NULL);
x->stdout_busy = ffstr_eqz(&name, "@stdout");
if (r->buffer)
x->timer_int_msec = ffmin(r->buffer / 2, x->timer_int_msec);
return 0;
}
static int rec_meta(struct cmd_rec *r, ffstr s)
{
ffstr name, val;
if (ffstr_splitby(&s, '=', &name, &val) <= 0)
return _ffargs_err(&x->cmd, 1, "invalid meta: '%S'", &s);
*ffvec_pushT(&r->meta, ffstr) = s;
return 0;
}
static int rec_aformat(struct cmd_rec *r, ffstr s)
{
if (~0U == (r->aformat = phi_af_val(s)))
return _ffargs_err(&x->cmd, 1, "incorrect audio format '%S'", &s);
return 0;
}
static int rec_split(struct cmd_rec *c, ffstr s)
{
uint64 v;
int r;
if ((r = cmd_time_value(&v, s)))
return r;
c->split = v;
return 0;
}
static int rec_until(struct cmd_rec *r, ffstr s) { return cmd_time_value(&r->until, s); }
#define O(m) (void*)FF_OFF(struct cmd_rec, m)
static const struct ffarg cmd_rec[] = {
{ "-aac_profile", 's', O(aac_profile) },
{ "-aac_quality", 'u', O(aac_q) },
{ "-aformat", 'S', rec_aformat },
{ "-audio", 's', O(audio) },
{ "-buffer", 'u', O(buffer) },
{ "-channels", 'u', O(channels) },
{ "-danorm", 's', O(danorm) },
{ "-device", 'u', O(device) },
{ "-exclusive", '1', O(exclusive) },
{ "-force", '1', O(force) },
{ "-gain", 'd', O(gain) },
{ "-help", 0, rec_help },
{ "-loopback", '1', O(loopback) },
{ "-meta", '+S', rec_meta },
{ "-o", 's', O(output) },
{ "-opus_mode", 's', O(opus_mode) },
{ "-opus_quality", 'u', O(opus_q) },
{ "-out", 's', O(output) },
{ "-rate", 'u', O(rate) },
{ "-remote", '1', O(remote) },
{ "-split", 'S', rec_split },
{ "-until", 'S', rec_until },
{ "-vorbis_quality",'u', O(vorbis_q) },
{ "", 0, rec_check },
};
#undef O
static void cmd_rec_free(struct cmd_rec *r)
{
ffmem_free(r->audio_module);
ffmem_free(r);
}
static struct ffarg_ctx cmd_rec_init(void *obj)
{
return SUBCMD_INIT(ffmem_new(struct cmd_rec), cmd_rec_free, rec_action, cmd_rec);
}