-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathrecord.h
More file actions
385 lines (343 loc) · 10.5 KB
/
Copy pathrecord.h
File metadata and controls
385 lines (343 loc) · 10.5 KB
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/** 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\
`-noise_gate` \"OPTIONS\" Suppress noise. Options:\n\
`threshold` Integer (dB)\n\
`release` Integer (msec)\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\
`-mp3_quality` NUMBER MP3 encoding quality:\n\
9..0 (VBR) or 64..320 (CBR, kbit/s)\n\
\n\
`-meta` NAME=VALUE Meta data\n\
.mp4 supports: album, albumartist, artist, comment, composer, copyright, date, discnumber, genre, lyrics, title, tracknumber.\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\
`-remote_id` STRING phiola instance ID\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* noise_gate;
const char* opus_mode;
const char* output;
const char* remote_id;
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 mp3_q;
uint opus_mode_n;
uint opus_q;
uint rate;
uint split;
uint vorbis_q;
uint64 until;
u_char aenc;
};
#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("ad-wasapi.play"), 0)) {
track->close(t);
return;
}
track->start(t);
}
#else
static void rec_silence_track(struct cmd_rec *r) {}
#endif
static void rec_grd_close(void *f, phi_track *t)
{
phi_grd_close(f, t);
ffmem_free(t->conf.ofile.name); t->conf.ofile.name = NULL;
x->core->metaif->destroy(&t->meta);
x->core->sig(PHI_CORE_STOP);
}
static const phi_filter rec_guard = {
NULL, rec_grd_close, phi_grd_process,
"rec-guard"
};
enum {
FMR_INPUT = 1,
FMR_NG = 5,
FMR_DAN,
FMR_GAIN,
FMR_SPLIT = 9,
FMR_WRITE,
FMR_OUTPUT,
};
static struct filter_map FF_STRUCTALIGN(64) rec_f_map[] = {
{ "", 1, &rec_guard },
{ "", 1, NULL },
{ "afilter.until", 1, NULL },
{ "afilter.rtpeak", 1, NULL },
{ "tui.rec", 1, NULL },
{ "afilter.noise-gate", 0, NULL },
{ "af-danorm.f", 0, NULL },
{ "afilter.gain", 0, NULL },
{ "afilter.auto-conv", 1, NULL },
{ "afilter.split", 0, NULL },
{ "format.auto-write", 0, NULL },
{ "", 1, NULL },
{ FM_END, 0, NULL }
};
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,
.noise_gate = r->noise_gate,
},
.oaudio = {
.format = {
.format = r->aformat,
.rate = r->rate,
.channels = r->channels,
},
},
.ofile = {
.name = ffsz_dup(r->output),
.overwrite = r->force,
},
};
switch (r->aenc) {
case PHI_AC_AAC:
c.aac.profile = r->aac_profile[0];
c.aac.quality = r->aac_q;
break;
case PHI_AC_OPUS:
c.opus.bitrate = r->opus_q;
c.opus.mode = r->opus_mode_n;
break;
case PHI_AC_MP3:
c.mp3.quality = (r->mp3_q != ~0U) ? (r->mp3_q + 1) : 0; break;
case PHI_AC_VORBIS:
c.vorbis.quality = (r->vorbis_q) ? (r->vorbis_q + 1) * 10 : 0; break;
}
const phi_track_if *track = x->core->track;
phi_track *t = track->create(&c);
struct filter_map map[FF_COUNT(rec_f_map)];
ffmem_copy(map, rec_f_map, sizeof(rec_f_map));
if (r->audio)
r->audio_module = ffsz_allocfmt("ad-%s.rec%Z", r->audio);
ffsz_copyz(map[FMR_INPUT].name, sizeof(map[0].name), (r->audio_module) ? r->audio_module : "core.auto-rec");
map[FMR_NG].use = !!r->noise_gate;
map[FMR_DAN].use = !!r->danorm;
map[FMR_GAIN].use = r->gain;
map[FMR_SPLIT].use = r->split;
map[FMR_WRITE].use = !r->split;
map[FMR_OUTPUT].use = !r->split;
ffsz_copyz(map[FMR_OUTPUT].name, sizeof(map[0].name), (x->stdout_busy) ? "core.stdout" : "core.file-write");
if (trk_add_filters(x->core, t, map, rec_f_map)) {
track->close(t);
return -1;
}
cmd_meta_set(&t->meta, &r->meta);
ffvec_free(&r->meta);
t->output.allow_async = 1;
track->start(t);
if (r->remote || r->remote_id) {
const phi_remote_sv_if *rsv = x->core->mod("remote.server");
if (rsv->start(r->remote_id))
return -1;
}
if (r->loopback)
rec_silence_track(r);
return 0;
}
static int rec_check(struct cmd_rec *r)
{
if (r->noise_gate && r->danorm)
return _ffargs_err(&x->cmd, 1, "`-noise_gate` and `-danorm` can't be used together");
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, ext;
ffpath_splitname_str(FFSTR_Z(r->output), &name, &ext);
x->stdout_busy = ffstr_eqz(&name, "@stdout");
if (!ext.len)
return _ffargs_err(&x->cmd, 1, "Please specify output file extension: \"%s\"", r->output);
if (!(r->aenc = cmd_oext_aenc(ext, 0)))
return _ffargs_err(&x->cmd, 1, "Specified output file format is not supported: \"%S\"", &ext);
if (!r->aformat) {
switch (r->aenc) {
case PHI_AC_MP3:
case PHI_AC_OPUS:
case PHI_AC_VORBIS:
r->aformat = PHI_PCM_FLOAT32;
}
}
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) },
{ "-m", '+S', rec_meta },
{ "-meta", '+S', rec_meta },
{ "-mp3_quality", 'u', O(mp3_q) },
{ "-noise_gate", 's', O(noise_gate) },
{ "-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) },
{ "-remote_id", 's', O(remote_id) },
{ "-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)
{
struct cmd_rec *r = ffmem_new(struct cmd_rec);
r->mp3_q = ~0U;
return SUBCMD_INIT(r, cmd_rec_free, rec_action, cmd_rec);
}