1 /******************************************************************************
2  *
3  *  Copyright 2022 Google LLC
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 #define _POSIX_C_SOURCE 199309L
20 
21 #include <stdalign.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <math.h>
27 #include <time.h>
28 #include <errno.h>
29 
30 #include <lc3.h>
31 #include "lc3bin.h"
32 #include "wave.h"
33 
34 
35 /**
36  * Error handling
37  */
38 
error(int status,const char * format,...)39 static void error(int status, const char *format, ...)
40 {
41     va_list args;
42 
43     fflush(stdout);
44 
45     va_start(args, format);
46     vfprintf(stderr, format, args);
47     va_end(args);
48 
49     fprintf(stderr, status ? ": %s\n" : "\n", strerror(status));
50     exit(status);
51 }
52 
53 
54 /**
55  * Parameters
56  */
57 
58 struct parameters {
59     const char *fname_in;
60     const char *fname_out;
61     float frame_ms;
62     int srate_hz;
63     int bitrate;
64 };
65 
parse_args(int argc,char * argv[])66 static struct parameters parse_args(int argc, char *argv[])
67 {
68     static const char *usage =
69         "Usage: %s [options] [wav_file] [out_file]\n"
70         "\n"
71         "wav_file\t"  "Input wave file, stdin if omitted\n"
72         "out_file\t"  "Output bitstream file, stdout if omitted\n"
73         "\n"
74         "Options:\n"
75         "\t-h\t"     "Display help\n"
76         "\t-b\t"     "Bitrate in bps (mandatory)\n"
77         "\t-m\t"     "Frame duration in ms (default 10)\n"
78         "\t-r\t"     "Encoder samplerate (default is input samplerate)\n"
79         "\n";
80 
81     struct parameters p = { .frame_ms = 10 };
82 
83     for (int iarg = 1; iarg < argc; ) {
84         const char *arg = argv[iarg++];
85 
86         if (arg[0] == '-') {
87             if (arg[2] != '\0')
88                 error(EINVAL, "Option %s", arg);
89 
90             char opt = arg[1];
91             const char *optarg;
92 
93             switch (opt) {
94                 case 'b': case 'm': case 'r':
95                     if (iarg >= argc)
96                         error(EINVAL, "Argument %s", arg);
97                     optarg = argv[iarg++];
98             }
99 
100             switch (opt) {
101                 case 'h': fprintf(stderr, usage, argv[0]); exit(0);
102                 case 'b': p.bitrate = atoi(optarg); break;
103                 case 'm': p.frame_ms = atof(optarg); break;
104                 case 'r': p.srate_hz = atoi(optarg); break;
105                 default:
106                     error(EINVAL, "Option %s", arg);
107             }
108 
109         } else {
110 
111             if (!p.fname_in)
112                 p.fname_in = arg;
113             else if (!p.fname_out)
114                 p.fname_out = arg;
115             else
116                 error(EINVAL, "Argument %s", arg);
117         }
118     }
119 
120     return p;
121 }
122 
123 
124 /**
125  * Return time in (us) from unspecified point in the past
126  */
127 
clock_us(void)128 static unsigned clock_us(void)
129 {
130     struct timespec ts;
131 
132     clock_gettime(CLOCK_MONOTONIC, &ts);
133 
134     return (unsigned)(ts.tv_sec * 1000*1000) + (unsigned)(ts.tv_nsec / 1000);
135 }
136 
137 
138 /**
139  * Entry point
140  */
141 
main(int argc,char * argv[])142 int main(int argc, char *argv[])
143 {
144     /* --- Read parameters --- */
145 
146     struct parameters p = parse_args(argc, argv);
147     FILE *fp_in = stdin, *fp_out = stdout;
148 
149     if (p.fname_in && (fp_in = fopen(p.fname_in, "rb")) == NULL)
150         error(errno, "%s", p.fname_in);
151 
152     if (p.fname_out && (fp_out = fopen(p.fname_out, "wb")) == NULL)
153         error(errno, "%s", p.fname_out);
154 
155     if (p.srate_hz && !LC3_CHECK_SR_HZ(p.srate_hz))
156         error(EINVAL, "Samplerate %d Hz", p.srate_hz);
157 
158     /* --- Check parameters --- */
159 
160     int frame_us = p.frame_ms * 1000;
161     int srate_hz, nch, nsamples;
162     int pcm_sbits, pcm_sbytes;
163 
164     if (wave_read_header(fp_in,
165             &pcm_sbits, &pcm_sbytes, &srate_hz, &nch, &nsamples) < 0)
166         error(EINVAL, "Bad or unsupported WAVE input file");
167 
168     if (p.bitrate <= 0)
169         error(EINVAL, "Bitrate");
170 
171     if (!LC3_CHECK_DT_US(frame_us))
172         error(EINVAL, "Frame duration");
173 
174     if (!LC3_CHECK_SR_HZ(srate_hz) || (p.srate_hz && p.srate_hz > srate_hz))
175         error(EINVAL, "Samplerate %d Hz", srate_hz);
176 
177     if (pcm_sbits != 16 && pcm_sbits != 24)
178         error(EINVAL, "Bitdepth %d", pcm_sbits);
179 
180     if ((pcm_sbits == 16 && pcm_sbytes != 16/8) ||
181         (pcm_sbits == 24 && pcm_sbytes != 24/8 && pcm_sbytes != 32/8))
182         error(EINVAL, "Sample storage on %d bytes", pcm_sbytes);
183 
184     if (nch  < 1 || nch  > 2)
185         error(EINVAL, "Number of channels %d", nch);
186 
187     int enc_srate_hz = !p.srate_hz ? srate_hz : p.srate_hz;
188     int enc_samples = !p.srate_hz ? nsamples :
189         ((int64_t)nsamples * enc_srate_hz) / srate_hz;
190 
191     lc3bin_write_header(fp_out,
192         frame_us, enc_srate_hz, p.bitrate, nch, enc_samples);
193 
194     /* --- Setup encoding --- */
195 
196     int frame_bytes = lc3_frame_bytes(frame_us, p.bitrate / nch);
197     int frame_samples = lc3_frame_samples(frame_us, srate_hz);
198     int encode_samples = nsamples + lc3_delay_samples(frame_us, srate_hz);
199 
200     lc3_encoder_t enc[nch];
201     int8_t alignas(int32_t) pcm[nch * frame_samples * pcm_sbytes];
202     enum lc3_pcm_format pcm_fmt =
203         pcm_sbytes == 32/8 ? LC3_PCM_FORMAT_S24 :
204         pcm_sbytes == 24/8 ? LC3_PCM_FORMAT_S24_3LE : LC3_PCM_FORMAT_S16;
205     uint8_t out[nch][frame_bytes];
206 
207     for (int ich = 0; ich < nch; ich++)
208         enc[ich] = lc3_setup_encoder(frame_us, enc_srate_hz, srate_hz,
209             malloc(lc3_encoder_size(frame_us, srate_hz)));
210 
211     /* --- Encoding loop --- */
212 
213     static const char *dash_line = "========================================";
214 
215     int nsec = 0;
216     unsigned t0 = clock_us();
217 
218     for (int i = 0; i * frame_samples < encode_samples; i++) {
219 
220         int nread = wave_read_pcm(fp_in, pcm_sbytes, nch, frame_samples, pcm);
221 
222         memset(pcm + nread * nch, 0,
223             nch * (frame_samples - nread) * pcm_sbytes);
224 
225         if (floorf(i * frame_us * 1e-6) > nsec) {
226             float progress = fminf(
227                 (float)i * frame_samples / encode_samples, 1);
228 
229             fprintf(stderr, "%02d:%02d [%-40s]\r",
230                     nsec / 60, nsec % 60,
231                     dash_line + (int)floorf((1 - progress) * 40));
232 
233             nsec = (int)(i * frame_us * 1e-6);
234         }
235 
236         for (int ich = 0; ich < nch; ich++)
237             lc3_encode(enc[ich],
238                 pcm_fmt, pcm + ich * pcm_sbytes, nch,
239                 frame_bytes, out[ich]);
240 
241         lc3bin_write_data(fp_out, out, nch, frame_bytes);
242     }
243 
244     unsigned t = (clock_us() - t0) / 1000;
245     nsec = encode_samples / srate_hz;
246 
247     fprintf(stderr, "%02d:%02d Encoded in %d.%d seconds %20s\n",
248         nsec / 60, nsec % 60, t / 1000, t % 1000, "");
249 
250     /* --- Cleanup --- */
251 
252     for (int ich = 0; ich < nch; ich++)
253         free(enc[ich]);
254 
255     if (fp_in != stdin)
256         fclose(fp_in);
257 
258     if (fp_out != stdout)
259         fclose(fp_out);
260 }
261