forked from stsaz/phiola
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrecord.h
235 lines (218 loc) · 7.06 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
/** phiola: executor: 'record' command
2023, Simon Zolin */
static int rec_help()
{
static const char s[] = "\
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\
Note: recording is automatically on pause unless something is playing!\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\
-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 CHAR AAC profile:\n\
l AAC-LC\n\
h AAC-HE\n\
H 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\
-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\
";
ffstdout_write(s, FFS_LEN(s));
x->exit_code = 0;
return 1;
}
struct cmd_rec {
char* audio_module;
const char* aac_profile;
const char* audio;
const char* danorm;
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_q;
uint rate;
uint vorbis_q;
uint64 until;
};
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,
},
.until_msec = r->until,
.afilter = {
.gain_db = r->gain,
.danorm = r->danorm,
},
.aac = {
.profile = r->aac_profile[0],
.quality = r->aac_q,
},
.vorbis.quality = (r->vorbis_q + 1) * 10,
.opus = {
.bitrate = r->opus_q,
},
.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("danorm.f"), 0))
|| !track->filter(t, x->core->mod("afilter.gain"), 0)
|| !track->filter(t, x->core->mod("afilter.auto-conv"), 0)
|| !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;
track->start(t);
if (r->remote) {
const phi_remote_sv_if *rsv = x->core->mod("remote.server");
if (rsv->start(NULL))
return -1;
}
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)
r->aac_profile = "l";
ffstr name;
ffpath_splitname_str(FFSTR_Z(r->output), &name, NULL);
x->stdout_busy = ffstr_eqz(&name, "@stdout");
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 = pcm_str_fmt(s.ptr, s.len)))
return _ffargs_err(&x->cmd, 1, "incorrect audio format '%S'", &s);
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_quality", 'u', O(opus_q) },
{ "-out", 's', O(output) },
{ "-rate", 'u', O(rate) },
{ "-remote", '1', O(remote) },
{ "-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);
}