1 /*------------------------------------------------------------------------------
2 
3    Copyright (c) 2000-2007 Tyrell Corporation. All rights reserved.
4 
5    Tyrell DarkIce
6 
7    File     : DarkIce.cpp
8    Version  : $Revision$
9    Author   : $Author$
10    Location : $HeadURL$
11 
12 
13    Copyright notice:
14 
15     This program is free software; you can redistribute it and/or
16     modify it under the terms of the GNU General Public License
17     as published by the Free Software Foundation; either version 3
18     of the License, or (at your option) any later version.
19 
20     This program is distributed in the hope that it will be useful,
21     but WITHOUT ANY WARRANTY; without even the implied warranty of
22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     GNU General Public License for more details.
24 
25     You should have received a copy of the GNU General Public License
26     along with this program; if not, write to the Free Software
27     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28 
29 ------------------------------------------------------------------------------*/
30 
31 /* ============================================================ include files */
32 
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 
37 #ifdef HAVE_STDLIB_H
38 #include <stdlib.h>
39 #else
40 #error need stdlib.h
41 #endif
42 
43 #ifdef HAVE_UNISTD_H
44 #include <unistd.h>
45 #else
46 #error need unistd.h
47 #endif
48 
49 #ifdef HAVE_SYS_TYPES_H
50 #include <sys/types.h>
51 #else
52 #error need sys/types.h
53 #endif
54 
55 #ifdef HAVE_SYS_WAIT_H
56 #include <sys/wait.h>
57 #else
58 #error need sys/wait.h
59 #endif
60 
61 #ifdef HAVE_ERRNO_H
62 #include <errno.h>
63 #else
64 #error need errno.h
65 #endif
66 
67 #ifdef HAVE_SCHED_H
68 #include <sched.h>
69 #else
70 #error need sched.h
71 #endif
72 
73 
74 
75 #include "Util.h"
76 #include "IceCast.h"
77 #include "IceCast2.h"
78 #include "ShoutCast.h"
79 #include "FileCast.h"
80 #include "MultiThreadedConnector.h"
81 #include "DarkIce.h"
82 
83 #ifdef HAVE_LAME_LIB
84 #include "LameLibEncoder.h"
85 #endif
86 
87 #ifdef HAVE_TWOLAME_LIB
88 #include "TwoLameLibEncoder.h"
89 #endif
90 
91 #ifdef HAVE_VORBIS_LIB
92 #include "VorbisLibEncoder.h"
93 #endif
94 
95 #ifdef HAVE_OPUS_LIB
96 #include "OpusLibEncoder.h"
97 #endif
98 
99 #ifdef HAVE_FAAC_LIB
100 #include "FaacEncoder.h"
101 #endif
102 
103 #ifdef HAVE_AACPLUS_LIB
104 #include "aacPlusEncoder.h"
105 #endif
106 
107 
108 /* ===================================================  local data structures */
109 
110 
111 /* ================================================  local constants & macros */
112 
113 /*------------------------------------------------------------------------------
114  *  File identity
115  *----------------------------------------------------------------------------*/
116 static const char fileid[] = "$Id$";
117 
118 
119 /*------------------------------------------------------------------------------
120  *  Make sure wait-related stuff is what we expect
121  *----------------------------------------------------------------------------*/
122 #ifndef WEXITSTATUS
123 # define WEXITSTATUS(stat_val)      ((unsigned)(stat_val) >> 8)
124 #endif
125 #ifndef WIFEXITED
126 # define WIFEXITED(stat_val)        (((stat_val) & 255) == 0)
127 #endif
128 
129 
130 
131 /* ===============================================  local function prototypes */
132 
133 
134 /* =============================================================  module code */
135 
136 /*------------------------------------------------------------------------------
137  *  Initialize the object
138  *----------------------------------------------------------------------------*/
139 void
init(const Config & config)140 DarkIce :: init ( const Config      & config )              throw ( Exception )
141 {
142     unsigned int             bufferSecs;
143     const ConfigSection    * cs;
144     const char             * str;
145     unsigned int             sampleRate;
146     unsigned int             bitsPerSample;
147     unsigned int             channel;
148     bool                     reconnect;
149     const char             * device;
150     const char             * jackClientName;
151     const char             * paSourceName;
152 
153     // the [general] section
154     if ( !(cs = config.get( "general")) ) {
155         throw Exception( __FILE__, __LINE__, "no section [general] in config");
156     }
157     str = cs->getForSure( "duration", " missing in section [general]");
158     duration = Util::strToL( str);
159     str = cs->getForSure( "bufferSecs", " missing in section [general]");
160     bufferSecs = Util::strToL( str);
161     if (bufferSecs == 0) {
162         throw Exception(__FILE__, __LINE__,
163                         "setting bufferSecs to 0 not supported");
164     }
165     str           = cs->get( "reconnect");
166     reconnect     = str ? (Util::strEq( str, "yes") ? true : false) : true;
167 
168     // real-time scheduling is enabled by default
169     str = cs->get( "realtime" );
170     enableRealTime = str ? (Util::strEq( str, "yes") ? true : false) : true;
171 
172     // get realtime scheduling priority. If unspecified, set it to 4.
173     // Why 4? jackd's default priority is 10, jackd client threads run
174     // at 5, so make the encoder thread use 4. jackd automatically sets
175     // the process callback priority to the right value, so all we have
176     // to care about is the encoder priority.
177     str = cs->get( "rtprio" );
178     realTimeSchedPriority = (str != NULL) ? Util::strToL( str ) : 4;
179 
180     // the [input] section
181     if ( !(cs = config.get( "input")) ) {
182         throw Exception( __FILE__, __LINE__, "no section [input] in config");
183     }
184 
185     str        = cs->getForSure( "sampleRate", " missing in section [input]");
186     sampleRate = Util::strToL( str);
187     str       = cs->getForSure( "bitsPerSample", " missing in section [input]");
188     bitsPerSample = Util::strToL( str);
189     str           = cs->getForSure( "channel", " missing in section [input]");
190     channel       = Util::strToL( str);
191     device        = cs->getForSure( "device", " missing in section [input]");
192     jackClientName = cs->get ( "jackClientName");
193     paSourceName = cs->get ( "paSourceName");
194 
195     dsp             = AudioSource::createDspSource( device,
196                                                     jackClientName,
197                                                     paSourceName,
198                                                     sampleRate,
199                                                     bitsPerSample,
200                                                     channel );
201     encConnector    = new MultiThreadedConnector( dsp.get(), reconnect );
202 
203     noAudioOuts = 0;
204     configIceCast( config, bufferSecs);
205     configIceCast2( config, bufferSecs);
206     configShoutCast( config, bufferSecs);
207     configFileCast( config);
208 }
209 
210 
211 /*------------------------------------------------------------------------------
212  *  Look for the IceCast stream outputs in the config file
213  *----------------------------------------------------------------------------*/
214 void
configIceCast(const Config & config,unsigned int bufferSecs)215 DarkIce :: configIceCast (  const Config      & config,
216                             unsigned int        bufferSecs  )
217                                                         throw ( Exception )
218 {
219     // look for IceCast encoder output streams,
220     // sections [icecast-0], [icecast-1], ...
221     char            stream[]        = "icecast- ";
222     size_t          streamLen       = Util::strLen( stream);
223     unsigned int    u;
224 
225     for ( u = noAudioOuts; u < maxOutput; ++u ) {
226         const ConfigSection    * cs;
227 
228         // ugly hack to change the section name to "stream0", "stream1", etc.
229         stream[streamLen-1] = '0' + (u - noAudioOuts);
230 
231         if ( !(cs = config.get( stream)) ) {
232             break;
233         }
234 
235 #if !defined HAVE_LAME_LIB && !defined HAVE_TWOLAME_LIB
236         throw Exception( __FILE__, __LINE__,
237                          "DarkIce not compiled with lame or twolame support, "
238                          "thus can't connect to IceCast 1.x, stream: ",
239                          stream);
240 #else
241 
242         const char                * str;
243 
244         unsigned int                sampleRate      = 0;
245         unsigned int                channel         = 0;
246         AudioEncoder::BitrateMode   bitrateMode;
247         unsigned int                bitrate         = 0;
248         double                      quality         = 0.0;
249         const char                * server          = 0;
250         unsigned int                port            = 0;
251         const char                * password        = 0;
252         const char                * mountPoint      = 0;
253         const char                * remoteDumpFile  = 0;
254         const char                * name            = 0;
255         const char                * description     = 0;
256         const char                * url             = 0;
257         const char                * genre           = 0;
258         bool                        isPublic        = false;
259         int                         lowpass         = 0;
260         int                         highpass        = 0;
261         const char                * localDumpName   = 0;
262         FileSink                  * localDumpFile   = 0;
263         bool                        fileAddDate     = false;
264         const char                * fileDateFormat  = 0;
265         BufferedSink              * audioOut        = 0;
266         int                         bufferSize      = 0;
267 
268         str         = cs->get( "sampleRate");
269         sampleRate  = str ? Util::strToL( str) : dsp->getSampleRate();
270         str         = cs->get( "channel");
271         channel     = str ? Util::strToL( str) : dsp->getChannel();
272 
273         str         = cs->get( "bitrate");
274         bitrate     = str ? Util::strToL( str) : 0;
275         str         = cs->get( "quality");
276         quality     = str ? Util::strToD( str) : 0.0;
277 
278         str         = cs->getForSure( "bitrateMode",
279                                       " not specified in section ",
280                                       stream);
281         if ( Util::strEq( str, "cbr") ) {
282             bitrateMode = AudioEncoder::cbr;
283 
284             if ( bitrate == 0 ) {
285                 throw Exception( __FILE__, __LINE__,
286                                  "bitrate not specified for CBR encoding");
287             }
288             if ( cs->get( "quality" ) == 0 ) {
289                 throw Exception( __FILE__, __LINE__,
290                                  "quality not specified for CBR encoding");
291             }
292         } else if ( Util::strEq( str, "abr") ) {
293             bitrateMode = AudioEncoder::abr;
294 
295             if ( bitrate == 0 ) {
296                 throw Exception( __FILE__, __LINE__,
297                                  "bitrate not specified for ABR encoding");
298             }
299         } else if ( Util::strEq( str, "vbr") ) {
300             bitrateMode = AudioEncoder::vbr;
301 
302             if ( cs->get( "quality" ) == 0 ) {
303                 throw Exception( __FILE__, __LINE__,
304                                  "quality not specified for VBR encoding");
305             }
306         } else {
307             throw Exception( __FILE__, __LINE__,
308                              "invalid bitrate mode: ", str);
309         }
310 
311 
312 
313         server      = cs->getForSure( "server", " missing in section ", stream);
314         str         = cs->getForSure( "port", " missing in section ", stream);
315         port        = Util::strToL( str);
316         password    = cs->getForSure("password"," missing in section ",stream);
317         mountPoint  = cs->getForSure( "mountPoint",
318                                       " missing in section ",
319                                       stream);
320         remoteDumpFile = cs->get( "remoteDumpFile");
321         name        = cs->get( "name");
322         description = cs->get("description");
323         url         = cs->get( "url");
324         genre       = cs->get( "genre");
325         str         = cs->get( "public");
326         isPublic    = str ? (Util::strEq( str, "yes") ? true : false) : false;
327         str         = cs->get( "lowpass");
328         lowpass     = str ? Util::strToL( str) : 0;
329         str         = cs->get( "highpass");
330         highpass    = str ? Util::strToL( str) : 0;
331         str         = cs->get("fileAddDate");
332         fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
333         fileDateFormat = cs->get("fileDateFormat");
334 
335         bufferSize = dsp->getSampleSize() * dsp->getSampleRate() * bufferSecs;
336         reportEvent( 3, "buffer size: ", bufferSize);
337 
338         localDumpName = cs->get( "localDumpFile");
339 
340         // go on and create the things
341 
342         // check for and create the local dump file if needed
343         if ( localDumpName != 0 ) {
344             if ( fileAddDate ) {
345                 if (fileDateFormat == 0) {
346                     localDumpName = Util::fileAddDate(localDumpName);
347                 }
348                 else {
349                     localDumpName = Util::fileAddDate(  localDumpName,
350                                                         fileDateFormat );
351                 }
352             }
353 
354             localDumpFile = new FileSink( stream, localDumpName);
355             if ( !localDumpFile->exists() ) {
356                 if ( !localDumpFile->create() ) {
357                     reportEvent( 1, "can't create local dump file",
358                                     localDumpName);
359                     localDumpFile = 0;
360                 }
361             }
362             if ( fileAddDate ) {
363                 delete[] localDumpName;
364             }
365         }
366         // streaming related stuff
367         audioOuts[u].socket = new TcpSocket( server, port);
368         audioOuts[u].server = new IceCast( audioOuts[u].socket.get(),
369                                            password,
370                                            mountPoint,
371                                            bitrate,
372                                            name,
373                                            description,
374                                            url,
375                                            genre,
376                                            isPublic,
377                                            remoteDumpFile,
378                                            localDumpFile);
379 
380         str = cs->getForSure( "format", " missing in section ", stream);
381 
382         if (!Util::strEq(str, "mp3") && !Util::strEq(str, "mp2")) {
383             throw Exception( __FILE__, __LINE__,
384                              "unsupported stream format: ", str);
385 
386         }
387 
388         // augment audio outs with a buffer when used from encoder
389         audioOut = new BufferedSink( audioOuts[u].server.get(),
390                                                   bufferSize, 1);
391 
392 #ifdef HAVE_LAME_LIB
393         if ( Util::strEq( str, "mp3") ) {
394             audioOuts[u].encoder = new LameLibEncoder( audioOut,
395                                           dsp.get(),
396                                           bitrateMode,
397                                           bitrate,
398                                           quality,
399                                           sampleRate,
400                                           channel,
401                                           lowpass,
402                                           highpass );
403         }
404 #endif
405 #ifdef HAVE_TWOLAME_LIB
406         if ( Util::strEq( str, "mp2") ) {
407             audioOuts[u].encoder = new TwoLameLibEncoder(
408                                             audioOut,
409                                             dsp.get(),
410                                             bitrateMode,
411                                             bitrate,
412                                             sampleRate,
413                                             channel );
414         }
415 #endif
416 
417         encConnector->attach( audioOuts[u].encoder.get());
418 #endif // HAVE_LAME_LIB || HAVE_TWOLAME_LIB
419     }
420 
421     noAudioOuts += u;
422 }
423 
424 
425 /*------------------------------------------------------------------------------
426  *  Look for the IceCast2 stream outputs in the config file
427  *----------------------------------------------------------------------------*/
428 void
configIceCast2(const Config & config,unsigned int bufferSecs)429 DarkIce :: configIceCast2 (  const Config      & config,
430                              unsigned int        bufferSecs  )
431                                                         throw ( Exception )
432 {
433     // look for IceCast2 encoder output streams,
434     // sections [icecast2-0], [icecast2-1], ...
435     char            stream[]        = "icecast2- ";
436     size_t          streamLen       = Util::strLen( stream);
437     unsigned int    u;
438 
439     for ( u = noAudioOuts; u < maxOutput; ++u ) {
440         const ConfigSection    * cs;
441 
442         // ugly hack to change the section name to "stream0", "stream1", etc.
443         stream[streamLen-1] = '0' + (u - noAudioOuts);
444 
445         if ( !(cs = config.get( stream)) ) {
446             break;
447         }
448 
449         const char                * str;
450 
451         IceCast2::StreamFormat      format;
452         unsigned int                sampleRate      = 0;
453         unsigned int                channel         = 0;
454         AudioEncoder::BitrateMode   bitrateMode;
455         unsigned int                bitrate         = 0;
456         unsigned int                maxBitrate      = 0;
457         double                      quality         = 0.0;
458         const char                * server          = 0;
459         unsigned int                port            = 0;
460         const char                * password        = 0;
461         const char                * mountPoint      = 0;
462         const char                * name            = 0;
463         const char                * description     = 0;
464         const char                * url             = 0;
465         const char                * genre           = 0;
466         bool                        isPublic        = false;
467         int                         lowpass         = 0;
468         int                         highpass        = 0;
469         const char                * localDumpName   = 0;
470         FileSink                  * localDumpFile   = 0;
471         bool                        fileAddDate     = false;
472         const char                * fileDateFormat  = 0;
473         BufferedSink              * audioOut        = 0;
474         int                         bufferSize      = 0;
475 
476         str         = cs->getForSure( "format", " missing in section ", stream);
477         if ( Util::strEq( str, "vorbis") ) {
478             format = IceCast2::oggVorbis;
479         } else if ( Util::strEq( str, "opus") ) {
480             format = IceCast2::oggOpus;
481         } else if ( Util::strEq( str, "mp3") ) {
482             format = IceCast2::mp3;
483         } else if ( Util::strEq( str, "mp2") ) {
484             format = IceCast2::mp2;
485         } else if ( Util::strEq( str, "aac") ) {
486             format = IceCast2::aac;
487         } else if ( Util::strEq( str, "aacp") ) {
488             format = IceCast2::aacp;
489         } else {
490             throw Exception( __FILE__, __LINE__,
491                              "unsupported stream format: ", str);
492         }
493 
494         str         = cs->get( "sampleRate");
495         sampleRate  = str ? Util::strToL( str) : dsp->getSampleRate();
496         str         = cs->get( "channel");
497         channel     = str ? Util::strToL( str) : dsp->getChannel();
498 
499         // determine fixed bitrate or variable bitrate quality
500         str         = cs->get( "bitrate");
501         bitrate     = str ? Util::strToL( str) : 0;
502         str         = cs->get( "maxBitrate");
503         maxBitrate  = str ? Util::strToL( str) : 0;
504         str         = cs->get( "quality");
505         quality     = str ? Util::strToD( str) : 0.0;
506 
507         str         = cs->getForSure( "bitrateMode",
508                                       " not specified in section ",
509                                       stream);
510         if ( Util::strEq( str, "cbr") ) {
511             bitrateMode = AudioEncoder::cbr;
512 
513             if ( bitrate == 0 ) {
514                 throw Exception( __FILE__, __LINE__,
515                                  "bitrate not specified for CBR encoding");
516             }
517         } else if ( Util::strEq( str, "abr") ) {
518             bitrateMode = AudioEncoder::abr;
519 
520             if ( bitrate == 0 ) {
521                 throw Exception( __FILE__, __LINE__,
522                                  "bitrate not specified for ABR encoding");
523             }
524         } else if ( Util::strEq( str, "vbr") ) {
525             bitrateMode = AudioEncoder::vbr;
526 
527             if ( cs->get( "quality" ) == 0 ) {
528                 throw Exception( __FILE__, __LINE__,
529                                  "quality not specified for VBR encoding");
530             }
531         } else {
532             throw Exception( __FILE__, __LINE__,
533                              "invalid bitrate mode: ", str);
534         }
535 
536         server      = cs->getForSure( "server", " missing in section ", stream);
537         str         = cs->getForSure( "port", " missing in section ", stream);
538         port        = Util::strToL( str);
539         password    = cs->getForSure("password"," missing in section ",stream);
540         mountPoint  = cs->getForSure( "mountPoint",
541                                       " missing in section ",
542                                       stream);
543         name        = cs->get( "name");
544         description = cs->get( "description");
545         url         = cs->get( "url");
546         genre       = cs->get( "genre");
547         str         = cs->get( "public");
548         isPublic    = str ? (Util::strEq( str, "yes") ? true : false) : false;
549         str         = cs->get( "lowpass");
550         lowpass     = str ? Util::strToL( str) : 0;
551         str         = cs->get( "highpass");
552         highpass    = str ? Util::strToL( str) : 0;
553         str         = cs->get( "fileAddDate");
554         fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
555         fileDateFormat = cs->get( "fileDateFormat");
556 
557         bufferSize = dsp->getSampleSize() * dsp->getSampleRate() * bufferSecs;
558         reportEvent( 3, "buffer size: ", bufferSize);
559 
560         localDumpName = cs->get( "localDumpFile");
561 
562         // go on and create the things
563 
564         // check for and create the local dump file if needed
565         if ( localDumpName != 0 ) {
566             if ( fileAddDate ) {
567                 if (fileDateFormat == 0) {
568                     localDumpName = Util::fileAddDate(localDumpName);
569                 }
570                 else {
571                     localDumpName = Util::fileAddDate(  localDumpName,
572                                                         fileDateFormat );
573                 }
574             }
575 
576             localDumpFile = new FileSink( stream, localDumpName);
577             if ( !localDumpFile->exists() ) {
578                 if ( !localDumpFile->create() ) {
579                     reportEvent( 1, "can't create local dump file",
580                                     localDumpName);
581                     localDumpFile = 0;
582                 }
583             }
584             if ( fileAddDate ) {
585                 delete[] localDumpName;
586             }
587         }
588 
589         // streaming related stuff
590         audioOuts[u].socket = new TcpSocket( server, port);
591         audioOuts[u].server = new IceCast2( audioOuts[u].socket.get(),
592                                             password,
593                                             mountPoint,
594                                             format,
595                                             bitrate,
596                                             name,
597                                             description,
598                                             url,
599                                             genre,
600                                             isPublic,
601                                             localDumpFile);
602 
603         audioOut = new BufferedSink( audioOuts[u].server.get(),
604                                      bufferSize, 1);
605 
606         switch ( format ) {
607             case IceCast2::mp3:
608 #ifndef HAVE_LAME_LIB
609                 throw Exception( __FILE__, __LINE__,
610                                  "DarkIce not compiled with lame support, "
611                                  "thus can't create mp3 stream: ",
612                                  stream);
613 #else
614                 audioOuts[u].encoder = new LameLibEncoder(
615                                              audioOut,
616                                              dsp.get(),
617                                              bitrateMode,
618                                              bitrate,
619                                              quality,
620                                              sampleRate,
621                                              channel,
622                                              lowpass,
623                                              highpass );
624 
625 #endif // HAVE_LAME_LIB
626                 break;
627 
628 
629             case IceCast2::oggVorbis:
630 #ifndef HAVE_VORBIS_LIB
631                 throw Exception( __FILE__, __LINE__,
632                                 "DarkIce not compiled with Ogg Vorbis support, "
633                                 "thus can't Ogg Vorbis stream: ",
634                                 stream);
635 #else
636 
637                 audioOuts[u].encoder = new VorbisLibEncoder(
638                                                audioOut,
639                                                dsp.get(),
640                                                bitrateMode,
641                                                bitrate,
642                                                quality,
643                                                sampleRate,
644                                                dsp->getChannel(),
645                                                maxBitrate);
646 
647 #endif // HAVE_VORBIS_LIB
648                 break;
649 
650             case IceCast2::oggOpus:
651 #ifndef HAVE_OPUS_LIB
652                 throw Exception( __FILE__, __LINE__,
653                                 "DarkIce not compiled with Ogg Opus support, "
654                                 "thus can't Ogg Opus stream: ",
655                                 stream);
656 #else
657 
658                 audioOuts[u].encoder = new OpusLibEncoder(
659                                                audioOut,
660                                                dsp.get(),
661                                                bitrateMode,
662                                                bitrate,
663                                                quality,
664                                                sampleRate,
665                                                dsp->getChannel(),
666                                                maxBitrate);
667 
668 #endif // HAVE_OPUS_LIB
669                 break;
670 
671             case IceCast2::mp2:
672 #ifndef HAVE_TWOLAME_LIB
673                 throw Exception( __FILE__, __LINE__,
674                                  "DarkIce not compiled with TwoLame support, "
675                                  "thus can't create mp2 stream: ",
676                                  stream);
677 #else
678                 audioOuts[u].encoder = new TwoLameLibEncoder(
679                                                 audioOut,
680                                                 dsp.get(),
681                                                 bitrateMode,
682                                                 bitrate,
683                                                 sampleRate,
684                                                 channel );
685 
686 #endif // HAVE_TWOLAME_LIB
687                 break;
688 
689 
690             case IceCast2::aac:
691 #ifndef HAVE_FAAC_LIB
692                 throw Exception( __FILE__, __LINE__,
693                                 "DarkIce not compiled with AAC support, "
694                                 "thus can't aac stream: ",
695                                 stream);
696 #else
697                 audioOuts[u].encoder = new FaacEncoder(
698                                           audioOut,
699                                           dsp.get(),
700                                           bitrateMode,
701                                           bitrate,
702                                           quality,
703                                           sampleRate,
704                                           dsp->getChannel());
705 
706 #endif // HAVE_FAAC_LIB
707                 break;
708 
709             case IceCast2::aacp:
710 #ifndef HAVE_AACPLUS_LIB
711                 throw Exception( __FILE__, __LINE__,
712                                 "DarkIce not compiled with AAC+ support, "
713                                 "thus can't aacp stream: ",
714                                 stream);
715 #else
716                 audioOuts[u].encoder = new aacPlusEncoder(
717                                              audioOut,
718                                              dsp.get(),
719                                              bitrateMode,
720                                              bitrate,
721                                              quality,
722                                              sampleRate,
723                                              channel );
724 
725 #endif // HAVE_AACPLUS_LIB
726                 break;
727 
728             default:
729                 throw Exception( __FILE__, __LINE__,
730                                 "Illegal stream format: ", format);
731         }
732 
733         encConnector->attach( audioOuts[u].encoder.get());
734     }
735 
736     noAudioOuts += u;
737 }
738 
739 
740 /*------------------------------------------------------------------------------
741  *  Look for the ShoutCast stream outputs in the config file
742  *----------------------------------------------------------------------------*/
743 void
configShoutCast(const Config & config,unsigned int bufferSecs)744 DarkIce :: configShoutCast (    const Config      & config,
745                                 unsigned int        bufferSecs  )
746                                                         throw ( Exception )
747 {
748     // look for Shoutcast encoder output streams,
749     // sections [shoutcast-0], [shoutcast-1], ...
750     char            stream[]        = "shoutcast- ";
751     size_t          streamLen       = Util::strLen( stream);
752     unsigned int    u;
753 
754     for ( u = noAudioOuts; u < maxOutput; ++u ) {
755         const ConfigSection    * cs;
756 
757         // ugly hack to change the section name to "stream0", "stream1", etc.
758         stream[streamLen-1] = '0' + (u - noAudioOuts);
759 
760         if ( !(cs = config.get( stream)) ) {
761             break;
762         }
763 
764 #ifndef HAVE_LAME_LIB
765         throw Exception( __FILE__, __LINE__,
766                          "DarkIce not compiled with lame support, "
767                          "thus can't connect to ShoutCast, stream: ",
768                          stream);
769 #else
770 
771         const char                * str;
772 
773         unsigned int                sampleRate      = 0;
774         unsigned int                channel         = 0;
775         AudioEncoder::BitrateMode   bitrateMode;
776         unsigned int                bitrate         = 0;
777         double                      quality         = 0.0;
778         const char                * server          = 0;
779         unsigned int                port            = 0;
780         const char                * password        = 0;
781         const char                * name            = 0;
782         const char                * url             = 0;
783         const char                * genre           = 0;
784         bool                        isPublic        = false;
785         const char                * mountPoint      = 0;
786         int                         lowpass         = 0;
787         int                         highpass        = 0;
788         const char                * irc             = 0;
789         const char                * aim             = 0;
790         const char                * icq             = 0;
791         const char                * localDumpName   = 0;
792         FileSink                  * localDumpFile   = 0;
793         bool                        fileAddDate     = false;
794         const char                * fileDateFormat  = 0;
795         AudioEncoder              * encoder         = 0;
796         int                         bufferSize      = 0;
797 
798         str         = cs->get( "sampleRate");
799         sampleRate  = str ? Util::strToL( str) : dsp->getSampleRate();
800         str         = cs->get( "channel");
801         channel     = str ? Util::strToL( str) : dsp->getChannel();
802 
803         str         = cs->get( "bitrate");
804         bitrate     = str ? Util::strToL( str) : 0;
805         str         = cs->get( "quality");
806         quality     = str ? Util::strToD( str) : 0.0;
807 
808         str         = cs->getForSure( "bitrateMode",
809                                       " not specified in section ",
810                                       stream);
811         if ( Util::strEq( str, "cbr") ) {
812             bitrateMode = AudioEncoder::cbr;
813 
814             if ( bitrate == 0 ) {
815                 throw Exception( __FILE__, __LINE__,
816                                  "bitrate not specified for CBR encoding");
817             }
818             if ( cs->get( "quality" ) == 0 ) {
819                 throw Exception( __FILE__, __LINE__,
820                                  "quality not specified for CBR encoding");
821             }
822         } else if ( Util::strEq( str, "abr") ) {
823             bitrateMode = AudioEncoder::abr;
824 
825             if ( bitrate == 0 ) {
826                 throw Exception( __FILE__, __LINE__,
827                                  "bitrate not specified for ABR encoding");
828             }
829         } else if ( Util::strEq( str, "vbr") ) {
830             bitrateMode = AudioEncoder::vbr;
831 
832             if ( cs->get( "quality" ) == 0 ) {
833                 throw Exception( __FILE__, __LINE__,
834                                  "quality not specified for VBR encoding");
835             }
836         } else {
837             throw Exception( __FILE__, __LINE__,
838                              "invalid bitrate mode: ", str);
839         }
840 
841         server      = cs->getForSure( "server", " missing in section ", stream);
842         str         = cs->getForSure( "port", " missing in section ", stream);
843         port        = Util::strToL( str);
844         password    = cs->getForSure("password"," missing in section ",stream);
845         name        = cs->get( "name");
846         mountPoint  = cs->get( "mountPoint" );
847         url         = cs->get( "url");
848         genre       = cs->get( "genre");
849         str         = cs->get( "public");
850         isPublic    = str ? (Util::strEq( str, "yes") ? true : false) : false;
851         str         = cs->get( "lowpass");
852         lowpass     = str ? Util::strToL( str) : 0;
853         str         = cs->get( "highpass");
854         highpass    = str ? Util::strToL( str) : 0;
855         irc         = cs->get( "irc");
856         aim         = cs->get( "aim");
857         icq         = cs->get( "icq");
858         str         = cs->get("fileAddDate");
859         fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
860         fileDateFormat = cs->get( "fileDateFormat");
861 
862         bufferSize = dsp->getBitsPerSample() / 8 * dsp->getSampleRate() * dsp->getChannel() * bufferSecs;
863         reportEvent( 3, "buffer size: ", bufferSize);
864 
865         localDumpName = cs->get( "localDumpFile");
866 
867         // go on and create the things
868 
869         // check for and create the local dump file if needed
870         if ( localDumpName != 0 ) {
871             if ( fileAddDate ) {
872                 if (fileDateFormat == 0) {
873                     localDumpName = Util::fileAddDate(localDumpName);
874                 }
875                 else {
876                     localDumpName = Util::fileAddDate(  localDumpName,
877                                                         fileDateFormat );
878                 }
879             }
880 
881             localDumpFile = new FileSink( stream, localDumpName);
882             if ( !localDumpFile->exists() ) {
883                 if ( !localDumpFile->create() ) {
884                     reportEvent( 1, "can't create local dump file",
885                                     localDumpName);
886                     localDumpFile = 0;
887                 }
888             }
889             if ( fileAddDate ) {
890                 delete[] localDumpName;
891             }
892         }
893 
894         // streaming related stuff
895         audioOuts[u].socket = new TcpSocket( server, port);
896         audioOuts[u].server = new ShoutCast( audioOuts[u].socket.get(),
897                                              password,
898                                              mountPoint,
899                                              bitrate,
900                                              name,
901                                              url,
902                                              genre,
903                                              isPublic,
904                                              irc,
905                                              aim,
906                                              icq,
907                                              localDumpFile);
908 
909 
910         encoder = new LameLibEncoder( audioOuts[u].server.get(),
911                                       dsp.get(),
912                                       bitrateMode,
913                                       bitrate,
914                                       quality,
915                                       sampleRate,
916                                       channel,
917                                       lowpass,
918                                       highpass );
919         audioOuts[u].encoder = new BufferedSink(encoder, bufferSize, dsp->getSampleSize());
920 
921         encConnector->attach( audioOuts[u].encoder.get());
922 #endif // HAVE_LAME_LIB
923     }
924 
925     noAudioOuts += u;
926 }
927 
928 
929 /*------------------------------------------------------------------------------
930  *  Look for the FileCast stream outputs in the config file
931  *----------------------------------------------------------------------------*/
932 void
configFileCast(const Config & config)933 DarkIce :: configFileCast (  const Config      & config )
934                                                         throw ( Exception )
935 {
936     // look for FileCast encoder output streams,
937     // sections [file-0], [file-1], ...
938     char            stream[]        = "file- ";
939     size_t          streamLen       = Util::strLen( stream);
940     unsigned int    u;
941 
942     for ( u = noAudioOuts; u < maxOutput; ++u ) {
943         const ConfigSection    * cs;
944 
945         // ugly hack to change the section name to "stream0", "stream1", etc.
946         stream[streamLen-1] = '0' + (u - noAudioOuts);
947 
948         if ( !(cs = config.get( stream)) ) {
949             break;
950         }
951 
952         const char                * str;
953 
954         const char                * format          = 0;
955         AudioEncoder::BitrateMode   bitrateMode;
956         unsigned int                bitrate         = 0;
957         double                      quality         = 0.0;
958         const char                * targetFileName  = 0;
959         unsigned int                sampleRate      = 0;
960         int                         lowpass         = 0;
961         int                         highpass        = 0;
962         bool                        fileAddDate     = false;
963         const char                * fileDateFormat  = 0;
964 
965         format      = cs->getForSure( "format", " missing in section ", stream);
966         if ( !Util::strEq( format, "vorbis")
967           && !Util::strEq( format, "opus")
968           && !Util::strEq( format, "mp3")
969           && !Util::strEq( format, "mp2")
970           && !Util::strEq( format, "aac")
971           && !Util::strEq( format, "aacp") ) {
972             throw Exception( __FILE__, __LINE__,
973                              "unsupported stream format: ", format);
974         }
975 
976         str         = cs->getForSure("bitrate", " missing in section ", stream);
977         bitrate     = Util::strToL( str);
978         targetFileName    = cs->getForSure( "fileName",
979                                             " missing in section ",
980                                             stream);
981 
982         str         = cs->get( "fileAddDate");
983         fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
984         fileDateFormat = cs->get( "fileDateFormat");
985 
986         str         = cs->get( "sampleRate");
987         sampleRate  = str ? Util::strToL( str) : dsp->getSampleRate();
988 
989         str         = cs->get( "bitrate");
990         bitrate     = str ? Util::strToL( str) : 0;
991         str         = cs->get( "quality");
992         quality     = str ? Util::strToD( str) : 0.0;
993 
994         str         = cs->getForSure( "bitrateMode",
995                                       " not specified in section ",
996                                       stream);
997         if ( Util::strEq( str, "cbr") ) {
998             bitrateMode = AudioEncoder::cbr;
999 
1000             if ( bitrate == 0 ) {
1001                 throw Exception( __FILE__, __LINE__,
1002                                  "bitrate not specified for CBR encoding");
1003             }
1004         } else if ( Util::strEq( str, "abr") ) {
1005             bitrateMode = AudioEncoder::abr;
1006 
1007             if ( bitrate == 0 ) {
1008                 throw Exception( __FILE__, __LINE__,
1009                                  "bitrate not specified for ABR encoding");
1010             }
1011         } else if ( Util::strEq( str, "vbr") ) {
1012             bitrateMode = AudioEncoder::vbr;
1013 
1014             if ( cs->get( "quality" ) == 0 ) {
1015                 throw Exception( __FILE__, __LINE__,
1016                                  "quality not specified for VBR encoding");
1017             }
1018         } else {
1019             throw Exception( __FILE__, __LINE__,
1020                              "invalid bitrate mode: ", str);
1021         }
1022 
1023         if (Util::strEq(format, "aac") && bitrateMode != AudioEncoder::abr) {
1024             throw Exception(__FILE__, __LINE__,
1025                             "currently the AAC format only supports "
1026                             "average bitrate mode");
1027         }
1028 
1029         if (Util::strEq(format, "aacp") && bitrateMode != AudioEncoder::cbr) {
1030             throw Exception(__FILE__, __LINE__,
1031                             "currently the AAC+ format only supports "
1032                             "constant bitrate mode");
1033         }
1034 
1035         str         = cs->get( "lowpass");
1036         lowpass     = str ? Util::strToL( str) : 0;
1037         str         = cs->get( "highpass");
1038         highpass    = str ? Util::strToL( str) : 0;
1039 
1040         // go on and create the things
1041 
1042         // the underlying file
1043         if ( fileAddDate ) {
1044             if (fileDateFormat == 0) {
1045                 targetFileName = Util::fileAddDate( targetFileName);
1046             }
1047             else {
1048                 targetFileName = Util::fileAddDate( targetFileName,
1049                                                     fileDateFormat );
1050             }
1051         }
1052 
1053         FileSink  * targetFile = new FileSink( stream, targetFileName);
1054         if ( !targetFile->exists() ) {
1055             if ( !targetFile->create() ) {
1056                 throw Exception( __FILE__, __LINE__,
1057                                  "can't create output file", targetFileName);
1058             }
1059         }
1060 
1061         // streaming related stuff
1062         audioOuts[u].socket = 0;
1063         audioOuts[u].server = new FileCast( targetFile );
1064 
1065         if ( Util::strEq( format, "mp3") ) {
1066 #ifndef HAVE_LAME_LIB
1067                 throw Exception( __FILE__, __LINE__,
1068                                  "DarkIce not compiled with lame support, "
1069                                  "thus can't create mp3 stream: ",
1070                                  stream);
1071 #else
1072                 audioOuts[u].encoder = new LameLibEncoder(
1073                                                     audioOuts[u].server.get(),
1074                                                     dsp.get(),
1075                                                     bitrateMode,
1076                                                     bitrate,
1077                                                     quality,
1078                                                     sampleRate,
1079                                                     dsp->getChannel(),
1080                                                     lowpass,
1081                                                     highpass );
1082 #endif // HAVE_TWOLAME_LIB
1083         } else if ( Util::strEq( format, "mp2") ) {
1084 #ifndef HAVE_TWOLAME_LIB
1085                 throw Exception( __FILE__, __LINE__,
1086                                 "DarkIce not compiled with TwoLAME support, "
1087                                 "thus can't create MPEG Audio Layer 2 stream: ",
1088                                 stream);
1089 #else
1090                 audioOuts[u].encoder = new TwoLameLibEncoder(
1091                                                     audioOuts[u].server.get(),
1092                                                     dsp.get(),
1093                                                     bitrateMode,
1094                                                     bitrate,
1095                                                     sampleRate,
1096                                                     dsp->getChannel() );
1097 #endif // HAVE_TWOLAME_LIB
1098         } else if ( Util::strEq( format, "vorbis") ) {
1099 #ifndef HAVE_VORBIS_LIB
1100                 throw Exception( __FILE__, __LINE__,
1101                                 "DarkIce not compiled with Ogg Vorbis support, "
1102                                 "thus can't Ogg Vorbis stream: ",
1103                                 stream);
1104 #else
1105                 audioOuts[u].encoder = new VorbisLibEncoder(
1106                                                     audioOuts[u].server.get(),
1107                                                     dsp.get(),
1108                                                     bitrateMode,
1109                                                     bitrate,
1110                                                     quality,
1111                                                     dsp->getSampleRate(),
1112                                                     dsp->getChannel() );
1113 #endif // HAVE_VORBIS_LIB
1114         } else if ( Util::strEq( format, "opus") ) {
1115 #ifndef HAVE_OPUS_LIB
1116                 throw Exception( __FILE__, __LINE__,
1117                                 "DarkIce not compiled with Ogg Opus support, "
1118                                 "thus can't Ogg Opus stream: ",
1119                                 stream);
1120 #else
1121                 audioOuts[u].encoder = new OpusLibEncoder(
1122                                                     audioOuts[u].server.get(),
1123                                                     dsp.get(),
1124                                                     bitrateMode,
1125                                                     bitrate,
1126                                                     quality,
1127                                                     dsp->getSampleRate(),
1128                                                     dsp->getChannel() );
1129 #endif // HAVE_OPUS_LIB
1130         } else if ( Util::strEq( format, "aac") ) {
1131 #ifndef HAVE_FAAC_LIB
1132                 throw Exception( __FILE__, __LINE__,
1133                                 "DarkIce not compiled with AAC support, "
1134                                 "thus can't aac stream: ",
1135                                 stream);
1136 #else
1137                 audioOuts[u].encoder = new FaacEncoder(
1138                                                 audioOuts[u].server.get(),
1139                                                 dsp.get(),
1140                                                 bitrateMode,
1141                                                 bitrate,
1142                                                 quality,
1143                                                 sampleRate,
1144                                                 dsp->getChannel());
1145 #endif // HAVE_FAAC_LIB
1146         } else if ( Util::strEq( format, "aacp") ) {
1147 #ifndef HAVE_AACPLUS_LIB
1148                 throw Exception( __FILE__, __LINE__,
1149                                 "DarkIce not compiled with AAC+ support, "
1150                                 "thus can't aacplus stream: ",
1151                                 stream);
1152 #else
1153                 audioOuts[u].encoder = new aacPlusEncoder(
1154                                                 audioOuts[u].server.get(),
1155                                                 dsp.get(),
1156                                                 bitrateMode,
1157                                                 bitrate,
1158                                                 quality,
1159                                                 sampleRate,
1160                                                 dsp->getChannel());
1161 #endif // HAVE_AACPLUS_LIB
1162         } else {
1163                 throw Exception( __FILE__, __LINE__,
1164                                 "Illegal stream format: ", format);
1165         }
1166 
1167         encConnector->attach( audioOuts[u].encoder.get());
1168     }
1169 
1170     noAudioOuts += u;
1171 }
1172 
1173 
1174 /*------------------------------------------------------------------------------
1175  *  Set POSIX real-time scheduling
1176  *----------------------------------------------------------------------------*/
1177 void
setRealTimeScheduling(void)1178 DarkIce :: setRealTimeScheduling ( void )               throw ( Exception )
1179 {
1180 // Only if the OS has the POSIX real-time scheduling functions implemented.
1181 #if defined( HAVE_SCHED_GETSCHEDULER ) && defined( HAVE_SCHED_GETPARAM )
1182     int                 high_priority;
1183     struct sched_param  param;
1184 
1185     /* store the old scheduling parameters */
1186     if ( (origSchedPolicy = sched_getscheduler(0)) == -1 ) {
1187         throw Exception( __FILE__, __LINE__, "sched_getscheduler", errno);
1188     }
1189 
1190     if ( sched_getparam( 0, &param) == -1 ) {
1191         throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
1192     }
1193     origSchedPriority = param.sched_priority;
1194 
1195     /* set SCHED_FIFO with max - 1 priority or user configured value */
1196     if ( (high_priority = sched_get_priority_max(SCHED_FIFO)) == -1 ) {
1197         throw Exception(__FILE__,__LINE__,"sched_get_priority_max",errno);
1198     }
1199     reportEvent( 8, "scheduler high priority", high_priority);
1200 
1201     if (realTimeSchedPriority > high_priority) {
1202         param.sched_priority = high_priority - 1;
1203     } else {
1204         param.sched_priority = realTimeSchedPriority;
1205     }
1206 
1207     if ( sched_setscheduler( 0, SCHED_FIFO, &param) == -1 ) {
1208         reportEvent( 1,
1209                      "Could not set POSIX real-time scheduling, "
1210                      "this may cause recording skips.\n"
1211                      "Try to run darkice as the super-user.");
1212     } else {
1213         /* ask the new priortiy and report it */
1214         if ( sched_getparam( 0, &param) == -1 ) {
1215             throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
1216         }
1217 
1218         reportEvent( 1,
1219                      "Using POSIX real-time scheduling, priority",
1220                      param.sched_priority );
1221     }
1222 #else
1223     reportEvent( 1, "POSIX scheduling not supported on this system, "
1224                     "this may cause recording skips");
1225 #endif // HAVE_SCHED_GETSCHEDULER && HAVE_SCHED_GETPARAM
1226 }
1227 
1228 
1229 /*------------------------------------------------------------------------------
1230  *  Set the original scheduling of the process, the one prior to the
1231  *  setRealTimeScheduling call.
1232  *  WARNING: make sure you don't call this before setRealTimeScheduling!!
1233  *----------------------------------------------------------------------------*/
1234 void
setOriginalScheduling(void)1235 DarkIce :: setOriginalScheduling ( void )               throw ( Exception )
1236 {
1237 // Only if the OS has the POSIX real-time scheduling functions implemented.
1238 #if defined( HAVE_SCHED_GETSCHEDULER ) && defined( HAVE_SCHED_GETPARAM )
1239     uid_t   euid;
1240 
1241     euid = geteuid();
1242 
1243     if ( euid == 0 ) {
1244         struct sched_param  param;
1245 
1246         if ( sched_getparam( 0, &param) == -1 ) {
1247             throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
1248         }
1249 
1250         param.sched_priority = origSchedPriority;
1251 
1252         if ( sched_setscheduler( 0, origSchedPolicy, &param) == -1 ) {
1253             throw Exception( __FILE__, __LINE__, "sched_setscheduler", errno);
1254         }
1255 
1256         reportEvent( 5, "reverted to original scheduling");
1257     }
1258 #endif // HAVE_SCHED_GETSCHEDULER && HAVE_SCHED_GETPARAM
1259 }
1260 
1261 
1262 /*------------------------------------------------------------------------------
1263  *  Run the encoder
1264  *----------------------------------------------------------------------------*/
1265 bool
encode(void)1266 DarkIce :: encode ( void )                          throw ( Exception )
1267 {
1268     unsigned int       len;
1269     unsigned long      bytes;
1270 
1271     if ( !encConnector->open() ) {
1272         throw Exception( __FILE__, __LINE__, "can't open connector");
1273     }
1274 
1275     bytes = dsp->getSampleRate() * dsp->getSampleSize() * duration;
1276 
1277     len = encConnector->transfer( bytes, 4096, 1, 0 );
1278 
1279     reportEvent( 1, len, "bytes transferred to the encoders");
1280 
1281     encConnector->close();
1282 
1283     return true;
1284 }
1285 
1286 
1287 /*------------------------------------------------------------------------------
1288  *  Run
1289  *----------------------------------------------------------------------------*/
1290 int
run(void)1291 DarkIce :: run ( void )                             throw ( Exception )
1292 {
1293     reportEvent( 3, "encoding");
1294 
1295     if (enableRealTime) {
1296         setRealTimeScheduling();
1297     }
1298     encode();
1299     if (enableRealTime) {
1300         setOriginalScheduling();
1301     }
1302     reportEvent( 3, "encoding ends");
1303 
1304     return 0;
1305 }
1306 
1307 
1308 /*------------------------------------------------------------------------------
1309  *  Tell each sink to cut what they are doing, and start again.
1310  *----------------------------------------------------------------------------*/
1311 void
cut(void)1312 DarkIce :: cut ( void )                             throw ()
1313 {
1314     reportEvent( 5, "cutting");
1315 
1316     encConnector->cut();
1317 
1318     reportEvent( 5, "cutting ends");
1319 }
1320