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, ¶m) == -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, ¶m) == -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, ¶m) == -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, ¶m) == -1 ) {
1247 throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
1248 }
1249
1250 param.sched_priority = origSchedPriority;
1251
1252 if ( sched_setscheduler( 0, origSchedPolicy, ¶m) == -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