1 /*
2  * encode_lame.c - encode audio frames using lame
3  * Written by Andrew Church <achurch@achurch.org>
4  *
5  * This file is part of transcode, a video stream processing tool.
6  * transcode is free software, distributable under the terms of the GNU
7  * General Public License (version 2 or later).  See the file COPYING
8  * for details.
9  */
10 
11 #include "transcode.h"
12 #include "libtc/libtc.h"
13 #include "libtc/optstr.h"
14 #include "libtc/tcmodule-plugin.h"
15 
16 #include <lame/lame.h>
17 
18 #define MOD_NAME        "encode_lame.so"
19 #define MOD_VERSION     "v1.1 (2006-11-01)"
20 #define MOD_CAP         "Encodes audio to MP3 using LAME"
21 #define MOD_AUTHOR      "Andrew Church"
22 
23 #define MOD_FEATURES \
24     TC_MODULE_FEATURE_ENCODE|TC_MODULE_FEATURE_AUDIO
25 
26 #define MOD_FLAGS \
27     TC_MODULE_FLAG_RECONFIGURABLE
28 
29 
30 /*************************************************************************/
31 
32 /* Local data structure: */
33 
34 typedef struct {
35     lame_global_flags *lgf;
36     int bps;  /* bytes per sample */
37     int channels;
38     int flush_flag;
39 } PrivateData;
40 
41 /*************************************************************************/
42 
43 /**
44  * lame_log_error, lame_log_msg, lame_log_debug:  Internal logging
45  * functions for LAME.
46  *
47  * Parameters:
48  *     format: Log message format string.
49  *       args: Log message format arguments.
50  * Return value:
51  *     None.
52  */
53 
lame_log_error(const char * format,va_list args)54 static void lame_log_error(const char *format, va_list args)
55 {
56     char buf[TC_BUF_MAX];
57     tc_vsnprintf(buf, sizeof(buf), format, args);
58     tc_log_error(MOD_NAME, "%s", buf);
59 }
60 
lame_log_msg(const char * format,va_list args)61 static void lame_log_msg(const char *format, va_list args)
62 {
63     if (verbose & TC_INFO) {
64         char buf[TC_BUF_MAX];
65         tc_vsnprintf(buf, sizeof(buf), format, args);
66         tc_log_info(MOD_NAME, "%s", buf);
67     }
68 }
69 
lame_log_debug(const char * format,va_list args)70 static void lame_log_debug(const char *format, va_list args)
71 {
72     if (verbose & TC_DEBUG) {
73         char buf[TC_BUF_MAX];
74         tc_vsnprintf(buf, sizeof(buf), format, args);
75         tc_log_msg(MOD_NAME, "%s", buf);
76     }
77 }
78 
79 /*************************************************************************/
80 /*************************************************************************/
81 
82 /* Module interface routines and data. */
83 
84 /*************************************************************************/
85 
86 /**
87  * lamemod_init:  Initialize this instance of the module.  See
88  * tcmodule-data.h for function details.  Note the name of this function--
89  * we don't want to conflict with libmp3lame's lame_init().
90  */
91 
lamemod_init(TCModuleInstance * self,uint32_t features)92 static int lamemod_init(TCModuleInstance *self, uint32_t features)
93 {
94     PrivateData *pd;
95 
96     TC_MODULE_SELF_CHECK(self, "init");
97     TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features);
98 
99     self->userdata = pd = tc_malloc(sizeof(PrivateData));
100     if (!pd) {
101         tc_log_error(MOD_NAME, "init: out of memory!");
102         return TC_ERROR;
103     }
104     pd->lgf = NULL;
105 
106     /* FIXME: shouldn't this test a specific flag? */
107     if (verbose) {
108         tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
109         if (verbose & TC_INFO)
110             tc_log_info(MOD_NAME, "Using LAME %s", get_lame_version());
111     }
112     return TC_OK;
113 }
114 
115 /*************************************************************************/
116 
117 /**
118  * lame_configure:  Configure this instance of the module.  See
119  * tcmodule-data.h for function details.
120  */
121 
lame_configure(TCModuleInstance * self,const char * options,vob_t * vob)122 static int lame_configure(TCModuleInstance *self,
123                           const char *options, vob_t *vob)
124 {
125     PrivateData *pd;
126     int samplerate = vob->mp3frequency ? vob->mp3frequency : vob->a_rate;
127     int quality;
128     MPEG_mode mode;
129 
130     TC_MODULE_SELF_CHECK(self, "configure");
131     pd = self->userdata;
132 
133     pd->flush_flag = vob->encoder_flush;
134     /* Save bytes per sample */
135     pd->bps = (vob->dm_chan * vob->dm_bits) / 8;
136     /* And audio channels */
137     pd->channels = vob->dm_chan;
138 
139     /* Create LAME object (freeing any old one that might be left over) */
140     if (pd->lgf)
141         lame_close(pd->lgf);
142     pd->lgf = lame_init();
143     if (!pd->lgf) {
144         tc_log_error(MOD_NAME, "LAME initialization failed");
145         return TC_ERROR;
146     }
147 
148     /* Set up logging functions (assume no failure) */
149     lame_set_errorf(pd->lgf, lame_log_error);
150     lame_set_msgf  (pd->lgf, lame_log_msg  );
151     lame_set_debugf(pd->lgf, lame_log_debug);
152 
153     /* Set up audio parameters */
154     if (vob->dm_bits != 16) {
155         tc_log_error(MOD_NAME, "Only 16-bit samples supported");
156         return TC_ERROR;
157     }
158     if (lame_set_in_samplerate(pd->lgf, samplerate) < 0) {
159         tc_log_error(MOD_NAME, "lame_set_in_samplerate(%d) failed",samplerate);
160         return TC_ERROR;
161     }
162     if (lame_set_num_channels(pd->lgf, pd->channels) < 0) {
163         tc_log_error(MOD_NAME, "lame_set_num_channels(%d) failed",
164                      pd->channels);
165         return TC_ERROR;
166     }
167     if (lame_set_scale(pd->lgf, vob->volume) < 0) {
168         tc_log_error(MOD_NAME, "lame_set_scale(%f) failed", vob->volume);
169         return TC_ERROR;
170     }
171     if (lame_set_bWriteVbrTag(pd->lgf, (vob->a_vbr!=0)) < 0) {
172         tc_log_error(MOD_NAME, "lame_set_bWriteVbrTag(%d) failed",
173                      (vob->a_vbr!=0));
174         return TC_ERROR;
175     }
176     quality = (int)TC_CLAMP(vob->mp3quality, 0.0, 9.0);
177     if (lame_set_quality(pd->lgf, quality) < 0) {
178         tc_log_error(MOD_NAME, "lame_set_quality(%d) failed", quality);
179         return TC_ERROR;
180     }
181     switch (vob->mp3mode) {
182       case 0: mode = JOINT_STEREO; break;
183       case 1: mode = STEREO;       break;
184       case 2: mode = MONO;         break;
185       default:
186         tc_log_warn(MOD_NAME,"Invalid audio mode, defaulting to joint stereo");
187         mode = JOINT_STEREO;
188         break;
189     }
190     /* FIXME: add coherency check with given audio channels? */
191     if (lame_set_mode(pd->lgf, mode) < 0) {
192         tc_log_error(MOD_NAME, "lame_set_mode(%d) failed", mode);
193         return TC_ERROR;
194     }
195     if (lame_set_brate(pd->lgf, vob->mp3bitrate) < 0) {
196         tc_log_error(MOD_NAME, "lame_set_brate(%d) failed", vob->mp3bitrate);
197         return TC_ERROR;
198     }
199     /* Ugly preset handling */
200     if (vob->lame_preset) {
201         preset_mode preset;
202         int fast = 0;
203         char *s = strchr(vob->lame_preset, ',');
204         if (s) {
205             *s++ = 0;
206             if (strcmp(s, "fast") == 0)
207                 fast = 1;
208         }
209         if (strcmp(vob->lame_preset, "standard") == 0) {
210             preset = (fast ? STANDARD_FAST : STANDARD);
211             vob->a_vbr = 1;
212         } else if (strcmp(vob->lame_preset, "medium") == 0) {
213             preset = (fast ? MEDIUM_FAST : MEDIUM);
214             vob->a_vbr = 1;
215         } else if (strcmp(vob->lame_preset, "extreme") == 0) {
216             preset = (fast ? EXTREME_FAST : EXTREME);
217             vob->a_vbr = 1;
218         } else if (strcmp(vob->lame_preset, "insane") == 0) {
219             preset = INSANE;
220             vob->a_vbr = 1;
221         } else {
222             preset = strtol(vob->lame_preset, &s, 10);
223             if (*s || preset < 8 || preset > 320) {
224                 tc_log_error(MOD_NAME, "Invalid preset \"%s\"",
225                              vob->lame_preset);
226                 return TC_ERROR;
227             } else {
228                 vob->a_vbr = 1;
229             }
230         }
231         if (lame_set_preset(pd->lgf, preset) < 0) {
232             tc_log_error(MOD_NAME, "lame_set_preset(%d) failed", preset);
233             return TC_ERROR;
234         }
235     }  // if (vob->lame_preset)
236     /* Acceleration setting failures aren't fatal */
237     if (lame_set_asm_optimizations(pd->lgf, MMX, (tc_accel&AC_MMX  )?1:0) < 0)
238         tc_log_warn(MOD_NAME, "lame_set_asm_optimizations(MMX,%d) failed",
239                     (tc_accel&AC_MMX)?1:0);
240     if (lame_set_asm_optimizations(pd->lgf, AMD_3DNOW,
241                                    (tc_accel&AC_3DNOW)?1:0) < 0)
242         tc_log_warn(MOD_NAME, "lame_set_asm_optimizations(3DNOW,%d) failed",
243                     (tc_accel&AC_3DNOW)?1:0);
244     if (lame_set_asm_optimizations(pd->lgf, SSE, (tc_accel&AC_SSE  )?1:0) < 0)
245         tc_log_warn(MOD_NAME, "lame_set_asm_optimizations(SSE,%d) failed",
246                     (tc_accel&AC_SSE)?1:0);
247     /* FIXME: this function is documented as "for testing only"--should we
248      * really expose it to the user? */
249     if (!vob->bitreservoir && lame_set_disable_reservoir(pd->lgf, 1) < 0) {
250         tc_log_error(MOD_NAME, "lame_set_disable_reservoir(1) failed");
251         return TC_ERROR;
252     }
253     if (lame_set_VBR(pd->lgf, vob->a_vbr ? vbr_default : vbr_off) < 0) {
254         tc_log_error(MOD_NAME, "lame_set_VBR(%d) failed",
255                      vob->a_vbr ? vbr_default : vbr_off);
256         return TC_ERROR;
257     }
258     if (vob->a_vbr) {
259         /* FIXME: we should have a separate VBR quality control */
260         if (lame_set_VBR_q(pd->lgf, quality) < 0) {
261             tc_log_error(MOD_NAME, "lame_set_VBR_q(%d) failed", quality);
262             return TC_ERROR;
263         }
264     }
265 
266     /* Initialize encoder */
267     if (lame_init_params(pd->lgf) < 0) {
268         tc_log_error(MOD_NAME, "lame_init_params() failed");
269         return TC_ERROR;
270     }
271 
272     return TC_OK;
273 }
274 
275 /*************************************************************************/
276 
277 /**
278  * lame_inspect:  Return the value of an option in this instance of the
279  * module.  See tcmodule-data.h for function details.
280  */
281 
lame_inspect(TCModuleInstance * self,const char * param,const char ** value)282 static int lame_inspect(TCModuleInstance *self,
283                        const char *param, const char **value)
284 {
285     static char buf[TC_BUF_MAX];
286 
287     TC_MODULE_SELF_CHECK(self, "inspect");
288     TC_MODULE_SELF_CHECK(param, "inspect");
289 
290     if (optstr_lookup(param, "help")) {
291         tc_snprintf(buf, sizeof(buf),
292                 "Overview:\n"
293                 "    Encodes audio to MP3 using the LAME library.\n"
294                 "No options available.\n");
295         *value = buf;
296     }
297     return TC_OK;
298 }
299 
300 /*************************************************************************/
301 
302 /**
303  * lame_stop:  Reset this instance of the module.  See tcmodule-data.h for
304  * function details.
305  */
306 
lame_stop(TCModuleInstance * self)307 static int lame_stop(TCModuleInstance *self)
308 {
309     PrivateData *pd;
310 
311     TC_MODULE_SELF_CHECK(self, "stop");
312 
313     pd = self->userdata;
314 
315     if (pd->lgf) {
316         lame_close(pd->lgf);
317         pd->lgf = NULL;
318     }
319 
320     return TC_OK;
321 }
322 
323 /*************************************************************************/
324 
325 /**
326  * lame_fini:  Clean up after this instance of the module.  See
327  * tcmodule-data.h for function details.
328  */
329 
lame_fini(TCModuleInstance * self)330 static int lame_fini(TCModuleInstance *self)
331 {
332     TC_MODULE_SELF_CHECK(self, "fini");
333 
334     lame_stop(self);
335     tc_free(self->userdata);
336     self->userdata = NULL;
337     return TC_OK;
338 }
339 
340 /*************************************************************************/
341 
342 /**
343  * lame_encode:  Encode a frame of data.  See tcmodule-data.h for
344  * function details.
345  */
346 
347 #define LAME_FLUSH_BUFFER_SIZE  7200 /* from lame/lame.h */
348 
lame_encode(TCModuleInstance * self,aframe_list_t * in,aframe_list_t * out)349 static int lame_encode(TCModuleInstance *self,
350                        aframe_list_t *in, aframe_list_t *out)
351 {
352     PrivateData *pd;
353     int res;
354 
355     TC_MODULE_SELF_CHECK(self, "encode");
356     if (out == NULL) {
357         tc_log_error(MOD_NAME, "no output buffer supplied");
358         return TC_ERROR;
359     }
360 
361     pd = self->userdata;
362 
363     if (in == NULL) {
364         /* flush request */
365         if (!pd->flush_flag) {
366             /* No-flush option given, so don't do anything */
367             out->audio_len = 0;
368             return TC_OK;
369         }
370         if (out->audio_size < LAME_FLUSH_BUFFER_SIZE) {
371             /* paranoia is a virtue */
372             tc_log_error(MOD_NAME, "output buffer too small for"
373                                    " flushing (%i|%i)",
374                                    out->audio_size,
375                                    LAME_FLUSH_BUFFER_SIZE);
376             return TC_ERROR;
377         }
378 
379         /*
380          * Looks like _nogap should behave better when
381          * splitting/rotating output files.
382          * Moreover, our streams should'nt contain any ID3 tag,
383          * -- FR
384          */
385         res = lame_encode_flush_nogap(pd->lgf, out->audio_buf, 0);
386         if (verbose & TC_DEBUG) {
387             tc_log_info(MOD_NAME, "flushing %d audio bytes", res);
388         }
389     } else {
390         /* regular encoding */
391         if (pd->channels == 1) { /* mono */
392             res = lame_encode_buffer(pd->lgf,
393                                      (short *)(in->audio_buf),
394                                      (short *)(in->audio_buf),
395                                      in->audio_size / pd->bps,
396                                      out->audio_buf,
397                                      out->audio_size);
398         } else { /* all stereo flavours */
399             res = lame_encode_buffer_interleaved(pd->lgf,
400                                                  (short *)in->audio_buf,
401                                                  in->audio_size / pd->bps,
402                                                  out->audio_buf,
403                                                  out->audio_size);
404         }
405 
406         if (res < 0) {
407             if (verbose & TC_DEBUG) {
408                 tc_log_error(MOD_NAME, "lame_encode_buffer_interleaved() failed"
409                              " (%d: %s)", res,
410                              res==-1 ? "output buffer overflow" :
411                              res==-2 ? "out of memory" :
412                              res==-3 ? "not initialized" :
413                              res==-4 ? "psychoacoustic problems" : "unknown");
414             } else {
415                 tc_log_error(MOD_NAME, "Audio encoding failed!");
416             }
417             return TC_ERROR;
418         }
419     }
420     out->audio_len = res;
421     return TC_OK;
422 }
423 
424 /*************************************************************************/
425 
426 static const TCCodecID lame_codecs_in[] = { TC_CODEC_PCM, TC_CODEC_ERROR };
427 static const TCCodecID lame_codecs_out[] = { TC_CODEC_MP3, TC_CODEC_ERROR };
428 TC_MODULE_CODEC_FORMATS(lame);
429 
430 TC_MODULE_INFO(lame);
431 
432 static const TCModuleClass lame_class = {
433     TC_MODULE_CLASS_HEAD(lame),
434 
435     .init         = lamemod_init,
436     .fini         = lame_fini,
437     .configure    = lame_configure,
438     .stop         = lame_stop,
439     .inspect      = lame_inspect,
440 
441     .encode_audio = lame_encode,
442 };
443 
444 TC_MODULE_ENTRY_POINT(lame);
445 
446 /*************************************************************************/
447 
448 /*
449  * Local variables:
450  *   c-file-style: "stroustrup"
451  *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
452  *   indent-tabs-mode: nil
453  * End:
454  *
455  * vim: expandtab shiftwidth=4:
456  */
457