1148 lines
43 KiB
C++
1148 lines
43 KiB
C++
/*------------------------------------------------------------------------------
|
|
|
|
Copyright (c) 2000 Tyrell Corporation. All rights reserved.
|
|
|
|
Tyrell DarkIce
|
|
|
|
File : DarkIce.cpp
|
|
Version : $Revision$
|
|
Author : $Author$
|
|
Location : $Source$
|
|
|
|
|
|
Copyright notice:
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|
|
/* ============================================================ include files */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#else
|
|
#error need stdlib.h
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#else
|
|
#error need unistd.h
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#else
|
|
#error need sys/types.h
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_WAIT_H
|
|
#include <sys/wait.h>
|
|
#else
|
|
#error need sys/wait.h
|
|
#endif
|
|
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#else
|
|
#error need errno.h
|
|
#endif
|
|
|
|
#ifdef HAVE_SCHED_H
|
|
#include <sched.h>
|
|
#else
|
|
#error need sched.h
|
|
#endif
|
|
|
|
|
|
|
|
#include "Util.h"
|
|
#include "IceCast.h"
|
|
#include "IceCast2.h"
|
|
#include "ShoutCast.h"
|
|
#include "FileCast.h"
|
|
#include "MultiThreadedConnector.h"
|
|
#include "DarkIce.h"
|
|
|
|
#ifdef HAVE_LAME_LIB
|
|
#include "LameLibEncoder.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_VORBIS_LIB
|
|
#include "VorbisLibEncoder.h"
|
|
#endif
|
|
|
|
|
|
/* =================================================== local data structures */
|
|
|
|
|
|
/* ================================================ local constants & macros */
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* File identity
|
|
*----------------------------------------------------------------------------*/
|
|
static const char fileid[] = "$Id$";
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Make sure wait-related stuff is what we expect
|
|
*----------------------------------------------------------------------------*/
|
|
#ifndef WEXITSTATUS
|
|
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
|
|
#endif
|
|
#ifndef WIFEXITED
|
|
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
|
|
#endif
|
|
|
|
|
|
|
|
/* =============================================== local function prototypes */
|
|
|
|
|
|
/* ============================================================= module code */
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Initialize the object
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: init ( const Config & config ) throw ( Exception )
|
|
{
|
|
unsigned int bufferSecs;
|
|
const ConfigSection * cs;
|
|
const char * str;
|
|
unsigned int sampleRate;
|
|
unsigned int bitsPerSample;
|
|
unsigned int channel;
|
|
const char * device;
|
|
|
|
// the [general] section
|
|
if ( !(cs = config.get( "general")) ) {
|
|
throw Exception( __FILE__, __LINE__, "no section [general] in config");
|
|
}
|
|
str = cs->getForSure( "duration", " missing in section [general]");
|
|
duration = Util::strToL( str);
|
|
str = cs->getForSure( "bufferSecs", " missing in section [general]");
|
|
bufferSecs = Util::strToL( str);
|
|
|
|
|
|
// the [input] section
|
|
if ( !(cs = config.get( "input")) ) {
|
|
throw Exception( __FILE__, __LINE__, "no section [general] in config");
|
|
}
|
|
|
|
str = cs->getForSure( "sampleRate", " missing in section [input]");
|
|
sampleRate = Util::strToL( str);
|
|
str = cs->getForSure( "bitsPerSample", " missing in section [input]");
|
|
bitsPerSample = Util::strToL( str);
|
|
str = cs->getForSure( "channel", " missing in section [input]");
|
|
channel = Util::strToL( str);
|
|
device = cs->getForSure( "device", " missing in section [input]");
|
|
|
|
dsp = AudioSource::createDspSource( device,
|
|
sampleRate,
|
|
bitsPerSample,
|
|
channel );
|
|
encConnector = new MultiThreadedConnector( dsp.get());
|
|
|
|
noAudioOuts = 0;
|
|
configIceCast( config, bufferSecs);
|
|
configIceCast2( config, bufferSecs);
|
|
configShoutCast( config, bufferSecs);
|
|
configFileCast( config);
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Look for the IceCast stream outputs in the config file
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: configIceCast ( const Config & config,
|
|
unsigned int bufferSecs )
|
|
throw ( Exception )
|
|
{
|
|
// look for IceCast encoder output streams,
|
|
// sections [icecast-0], [icecast-1], ...
|
|
char stream[] = "icecast- ";
|
|
size_t streamLen = Util::strLen( stream);
|
|
unsigned int u;
|
|
|
|
for ( u = noAudioOuts; u < maxOutput; ++u ) {
|
|
const ConfigSection * cs;
|
|
|
|
// ugly hack to change the section name to "stream0", "stream1", etc.
|
|
stream[streamLen-1] = '0' + (u - noAudioOuts);
|
|
|
|
if ( !(cs = config.get( stream)) ) {
|
|
break;
|
|
}
|
|
|
|
#ifndef HAVE_LAME_LIB
|
|
throw Exception( __FILE__, __LINE__,
|
|
"DarkIce not compiled with lame support, "
|
|
"thus can't connect to IceCast 1.x, stream: ",
|
|
stream);
|
|
#else
|
|
|
|
const char * str;
|
|
|
|
unsigned int sampleRate = 0;
|
|
unsigned int channel = 0;
|
|
AudioEncoder::BitrateMode bitrateMode;
|
|
unsigned int bitrate = 0;
|
|
double quality = 0.0;
|
|
const char * server = 0;
|
|
unsigned int port = 0;
|
|
const char * password = 0;
|
|
const char * mountPoint = 0;
|
|
const char * remoteDumpFile = 0;
|
|
const char * name = 0;
|
|
const char * description = 0;
|
|
const char * url = 0;
|
|
const char * genre = 0;
|
|
bool isPublic = false;
|
|
int lowpass = 0;
|
|
int highpass = 0;
|
|
const char * localDumpName = 0;
|
|
FileSink * localDumpFile = 0;
|
|
bool fileAddDate = false;
|
|
|
|
str = cs->get( "sampleRate");
|
|
sampleRate = str ? Util::strToL( str) : dsp->getSampleRate();
|
|
str = cs->get( "channel");
|
|
channel = str ? Util::strToL( str) : dsp->getChannel();
|
|
|
|
str = cs->get( "bitrate");
|
|
bitrate = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "quality");
|
|
quality = str ? Util::strToD( str) : 0.0;
|
|
|
|
str = cs->getForSure( "bitrateMode",
|
|
" not specified in section ",
|
|
stream);
|
|
if ( Util::strEq( str, "cbr") ) {
|
|
bitrateMode = AudioEncoder::cbr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for CBR encoding");
|
|
}
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for CBR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "abr") ) {
|
|
bitrateMode = AudioEncoder::abr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for ABR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "vbr") ) {
|
|
bitrateMode = AudioEncoder::vbr;
|
|
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for VBR encoding");
|
|
}
|
|
} else {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"invalid bitrate mode: ", str);
|
|
}
|
|
|
|
server = cs->getForSure( "server", " missing in section ", stream);
|
|
str = cs->getForSure( "port", " missing in section ", stream);
|
|
port = Util::strToL( str);
|
|
password = cs->getForSure("password"," missing in section ",stream);
|
|
mountPoint = cs->getForSure( "mountPoint",
|
|
" missing in section ",
|
|
stream);
|
|
remoteDumpFile = cs->get( "remoteDumpFile");
|
|
name = cs->get( "name");
|
|
description = cs->get("description");
|
|
url = cs->get( "url");
|
|
genre = cs->get( "genre");
|
|
str = cs->get( "public");
|
|
isPublic = str ? (Util::strEq( str, "yes") ? true : false) : false;
|
|
str = cs->get( "lowpass");
|
|
lowpass = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "highpass");
|
|
highpass = str ? Util::strToL( str) : 0;
|
|
str = cs->get("fileAddDate");
|
|
fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
|
|
|
|
localDumpName = cs->get( "localDumpFile");
|
|
|
|
// go on and create the things
|
|
|
|
// check for and create the local dump file if needed
|
|
if ( localDumpName != 0 ) {
|
|
if ( fileAddDate ) {
|
|
localDumpName = Util::fileAddDate(localDumpName);
|
|
}
|
|
|
|
localDumpFile = new FileSink( localDumpName);
|
|
if ( !localDumpFile->exists() ) {
|
|
if ( !localDumpFile->create() ) {
|
|
reportEvent( 1, "can't create local dump file",
|
|
localDumpName);
|
|
localDumpFile = 0;
|
|
}
|
|
}
|
|
if ( fileAddDate ) {
|
|
delete[] localDumpFile;
|
|
}
|
|
}
|
|
// streaming related stuff
|
|
audioOuts[u].socket = new TcpSocket( server, port);
|
|
audioOuts[u].server = new IceCast( audioOuts[u].socket.get(),
|
|
password,
|
|
mountPoint,
|
|
bitrate,
|
|
name,
|
|
description,
|
|
url,
|
|
genre,
|
|
isPublic,
|
|
remoteDumpFile,
|
|
localDumpFile,
|
|
bufferSecs );
|
|
|
|
audioOuts[u].encoder = new LameLibEncoder( audioOuts[u].server.get(),
|
|
dsp.get(),
|
|
bitrateMode,
|
|
bitrate,
|
|
quality,
|
|
sampleRate,
|
|
channel,
|
|
lowpass,
|
|
highpass );
|
|
|
|
encConnector->attach( audioOuts[u].encoder.get());
|
|
#endif // HAVE_LAME_LIB
|
|
}
|
|
|
|
noAudioOuts += u;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Look for the IceCast2 stream outputs in the config file
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: configIceCast2 ( const Config & config,
|
|
unsigned int bufferSecs )
|
|
throw ( Exception )
|
|
{
|
|
// look for IceCast2 encoder output streams,
|
|
// sections [icecast2-0], [icecast2-1], ...
|
|
char stream[] = "icecast2- ";
|
|
size_t streamLen = Util::strLen( stream);
|
|
unsigned int u;
|
|
|
|
for ( u = noAudioOuts; u < maxOutput; ++u ) {
|
|
const ConfigSection * cs;
|
|
|
|
// ugly hack to change the section name to "stream0", "stream1", etc.
|
|
stream[streamLen-1] = '0' + (u - noAudioOuts);
|
|
|
|
if ( !(cs = config.get( stream)) ) {
|
|
break;
|
|
}
|
|
|
|
const char * str;
|
|
|
|
IceCast2::StreamFormat format;
|
|
unsigned int sampleRate = 0;
|
|
unsigned int channel = 0;
|
|
AudioEncoder::BitrateMode bitrateMode;
|
|
unsigned int bitrate = 0;
|
|
unsigned int maxBitrate = 0;
|
|
double quality = 0.0;
|
|
const char * server = 0;
|
|
unsigned int port = 0;
|
|
const char * password = 0;
|
|
const char * mountPoint = 0;
|
|
const char * name = 0;
|
|
const char * description = 0;
|
|
const char * url = 0;
|
|
const char * genre = 0;
|
|
bool isPublic = false;
|
|
const char * localDumpName = 0;
|
|
FileSink * localDumpFile = 0;
|
|
bool fileAddDate = false;
|
|
|
|
str = cs->getForSure( "format", " missing in section ", stream);
|
|
if ( Util::strEq( str, "vorbis") ) {
|
|
format = IceCast2::oggVorbis;
|
|
} else if ( Util::strEq( str, "mp3") ) {
|
|
format = IceCast2::mp3;
|
|
} else {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"unsupported stream format: ", str);
|
|
}
|
|
|
|
str = cs->get( "sampleRate");
|
|
sampleRate = str ? Util::strToL( str) : dsp->getSampleRate();
|
|
str = cs->get( "channel");
|
|
channel = str ? Util::strToL( str) : dsp->getChannel();
|
|
|
|
// determine fixed bitrate or variable bitrate quality
|
|
str = cs->get( "bitrate");
|
|
bitrate = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "maxBitrate");
|
|
maxBitrate = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "quality");
|
|
quality = str ? Util::strToD( str) : 0.0;
|
|
|
|
str = cs->getForSure( "bitrateMode",
|
|
" not specified in section ",
|
|
stream);
|
|
if ( Util::strEq( str, "cbr") ) {
|
|
bitrateMode = AudioEncoder::cbr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for CBR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "abr") ) {
|
|
bitrateMode = AudioEncoder::abr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for ABR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "vbr") ) {
|
|
bitrateMode = AudioEncoder::vbr;
|
|
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for VBR encoding");
|
|
}
|
|
} else {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"invalid bitrate mode: ", str);
|
|
}
|
|
|
|
server = cs->getForSure( "server", " missing in section ", stream);
|
|
str = cs->getForSure( "port", " missing in section ", stream);
|
|
port = Util::strToL( str);
|
|
password = cs->getForSure("password"," missing in section ",stream);
|
|
mountPoint = cs->getForSure( "mountPoint",
|
|
" missing in section ",
|
|
stream);
|
|
name = cs->get( "name");
|
|
description = cs->get( "description");
|
|
url = cs->get( "url");
|
|
genre = cs->get( "genre");
|
|
str = cs->get( "public");
|
|
isPublic = str ? (Util::strEq( str, "yes") ? true : false) : false;
|
|
str = cs->get( "fileAddDate");
|
|
fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
|
|
|
|
localDumpName = cs->get( "localDumpFile");
|
|
|
|
// go on and create the things
|
|
|
|
// check for and create the local dump file if needed
|
|
if ( localDumpName != 0 ) {
|
|
if ( fileAddDate ) {
|
|
localDumpName = Util::fileAddDate(localDumpName);
|
|
}
|
|
|
|
localDumpFile = new FileSink( localDumpName);
|
|
if ( !localDumpFile->exists() ) {
|
|
if ( !localDumpFile->create() ) {
|
|
reportEvent( 1, "can't create local dump file",
|
|
localDumpName);
|
|
localDumpFile = 0;
|
|
}
|
|
}
|
|
if ( fileAddDate ) {
|
|
delete[] localDumpName;
|
|
}
|
|
}
|
|
|
|
// streaming related stuff
|
|
audioOuts[u].socket = new TcpSocket( server, port);
|
|
audioOuts[u].server = new IceCast2( audioOuts[u].socket.get(),
|
|
password,
|
|
mountPoint,
|
|
format,
|
|
bitrate,
|
|
name,
|
|
description,
|
|
url,
|
|
genre,
|
|
isPublic,
|
|
localDumpFile,
|
|
bufferSecs );
|
|
|
|
switch ( format ) {
|
|
case IceCast2::mp3:
|
|
#ifndef HAVE_LAME_LIB
|
|
throw Exception( __FILE__, __LINE__,
|
|
"DarkIce not compiled with lame support, "
|
|
"thus can't create mp3 stream: ",
|
|
stream);
|
|
#else
|
|
audioOuts[u].encoder = new LameLibEncoder(
|
|
audioOuts[u].server.get(),
|
|
dsp.get(),
|
|
bitrateMode,
|
|
bitrate,
|
|
quality,
|
|
sampleRate,
|
|
channel );
|
|
#endif // HAVE_LAME_LIB
|
|
break;
|
|
|
|
case IceCast2::oggVorbis:
|
|
#ifndef HAVE_VORBIS_LIB
|
|
throw Exception( __FILE__, __LINE__,
|
|
"DarkIce not compiled with Ogg Vorbis support, "
|
|
"thus can't Ogg Vorbis stream: ",
|
|
stream);
|
|
#else
|
|
audioOuts[u].encoder = new VorbisLibEncoder(
|
|
audioOuts[u].server.get(),
|
|
dsp.get(),
|
|
bitrateMode,
|
|
bitrate,
|
|
quality,
|
|
sampleRate,
|
|
dsp->getChannel(),
|
|
maxBitrate);
|
|
#endif // HAVE_VORBIS_LIB
|
|
break;
|
|
|
|
default:
|
|
throw Exception( __FILE__, __LINE__,
|
|
"Illegal stream format: ", format);
|
|
}
|
|
|
|
encConnector->attach( audioOuts[u].encoder.get());
|
|
}
|
|
|
|
noAudioOuts += u;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Look for the ShoutCast stream outputs in the config file
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: configShoutCast ( const Config & config,
|
|
unsigned int bufferSecs )
|
|
throw ( Exception )
|
|
{
|
|
// look for Shoutcast encoder output streams,
|
|
// sections [shoutcast-0], [shoutcast-1], ...
|
|
char stream[] = "shoutcast- ";
|
|
size_t streamLen = Util::strLen( stream);
|
|
unsigned int u;
|
|
|
|
for ( u = noAudioOuts; u < maxOutput; ++u ) {
|
|
const ConfigSection * cs;
|
|
|
|
// ugly hack to change the section name to "stream0", "stream1", etc.
|
|
stream[streamLen-1] = '0' + (u - noAudioOuts);
|
|
|
|
if ( !(cs = config.get( stream)) ) {
|
|
break;
|
|
}
|
|
|
|
#ifndef HAVE_LAME_LIB
|
|
throw Exception( __FILE__, __LINE__,
|
|
"DarkIce not compiled with lame support, "
|
|
"thus can't connect to ShoutCast, stream: ",
|
|
stream);
|
|
#else
|
|
|
|
const char * str;
|
|
|
|
unsigned int sampleRate = 0;
|
|
unsigned int channel = 0;
|
|
AudioEncoder::BitrateMode bitrateMode;
|
|
unsigned int bitrate = 0;
|
|
double quality = 0.0;
|
|
const char * server = 0;
|
|
unsigned int port = 0;
|
|
const char * password = 0;
|
|
const char * name = 0;
|
|
const char * url = 0;
|
|
const char * genre = 0;
|
|
bool isPublic = false;
|
|
int lowpass = 0;
|
|
int highpass = 0;
|
|
const char * irc = 0;
|
|
const char * aim = 0;
|
|
const char * icq = 0;
|
|
const char * localDumpName = 0;
|
|
FileSink * localDumpFile = 0;
|
|
bool fileAddDate = false;
|
|
|
|
str = cs->get( "sampleRate");
|
|
sampleRate = str ? Util::strToL( str) : dsp->getSampleRate();
|
|
str = cs->get( "channel");
|
|
channel = str ? Util::strToL( str) : dsp->getChannel();
|
|
|
|
str = cs->get( "bitrate");
|
|
bitrate = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "quality");
|
|
quality = str ? Util::strToD( str) : 0.0;
|
|
|
|
str = cs->getForSure( "bitrateMode",
|
|
" not specified in section ",
|
|
stream);
|
|
if ( Util::strEq( str, "cbr") ) {
|
|
bitrateMode = AudioEncoder::cbr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for CBR encoding");
|
|
}
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for CBR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "abr") ) {
|
|
bitrateMode = AudioEncoder::abr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for ABR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "vbr") ) {
|
|
bitrateMode = AudioEncoder::vbr;
|
|
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for VBR encoding");
|
|
}
|
|
} else {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"invalid bitrate mode: ", str);
|
|
}
|
|
|
|
server = cs->getForSure( "server", " missing in section ", stream);
|
|
str = cs->getForSure( "port", " missing in section ", stream);
|
|
port = Util::strToL( str);
|
|
password = cs->getForSure("password"," missing in section ",stream);
|
|
name = cs->get( "name");
|
|
url = cs->get( "url");
|
|
genre = cs->get( "genre");
|
|
str = cs->get( "public");
|
|
isPublic = str ? (Util::strEq( str, "yes") ? true : false) : false;
|
|
str = cs->get( "lowpass");
|
|
lowpass = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "highpass");
|
|
highpass = str ? Util::strToL( str) : 0;
|
|
irc = cs->get( "irc");
|
|
aim = cs->get( "aim");
|
|
icq = cs->get( "icq");
|
|
str = cs->get("fileAddDate");
|
|
fileAddDate = str ? (Util::strEq( str, "yes") ? true : false) : false;
|
|
|
|
localDumpName = cs->get( "localDumpFile");
|
|
|
|
// go on and create the things
|
|
|
|
// check for and create the local dump file if needed
|
|
if ( localDumpName != 0 ) {
|
|
if ( fileAddDate ) {
|
|
localDumpName = Util::fileAddDate(localDumpName);
|
|
}
|
|
|
|
localDumpFile = new FileSink( localDumpName);
|
|
if ( !localDumpFile->exists() ) {
|
|
if ( !localDumpFile->create() ) {
|
|
reportEvent( 1, "can't create local dump file",
|
|
localDumpName);
|
|
localDumpFile = 0;
|
|
}
|
|
}
|
|
if ( fileAddDate ) {
|
|
delete[] localDumpFile;
|
|
}
|
|
}
|
|
|
|
// streaming related stuff
|
|
audioOuts[u].socket = new TcpSocket( server, port);
|
|
audioOuts[u].server = new ShoutCast( audioOuts[u].socket.get(),
|
|
password,
|
|
bitrate,
|
|
name,
|
|
url,
|
|
genre,
|
|
isPublic,
|
|
irc,
|
|
aim,
|
|
icq,
|
|
localDumpFile,
|
|
bufferSecs );
|
|
|
|
audioOuts[u].encoder = new LameLibEncoder( audioOuts[u].server.get(),
|
|
dsp.get(),
|
|
bitrateMode,
|
|
bitrate,
|
|
quality,
|
|
sampleRate,
|
|
channel,
|
|
lowpass,
|
|
highpass );
|
|
|
|
encConnector->attach( audioOuts[u].encoder.get());
|
|
#endif // HAVE_LAME_LIB
|
|
}
|
|
|
|
noAudioOuts += u;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Look for the FileCast stream outputs in the config file
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: configFileCast ( const Config & config )
|
|
throw ( Exception )
|
|
{
|
|
// look for FileCast encoder output streams,
|
|
// sections [file-0], [file-1], ...
|
|
char stream[] = "file- ";
|
|
size_t streamLen = Util::strLen( stream);
|
|
unsigned int u;
|
|
|
|
for ( u = noAudioOuts; u < maxOutput; ++u ) {
|
|
const ConfigSection * cs;
|
|
|
|
// ugly hack to change the section name to "stream0", "stream1", etc.
|
|
stream[streamLen-1] = '0' + (u - noAudioOuts);
|
|
|
|
if ( !(cs = config.get( stream)) ) {
|
|
break;
|
|
}
|
|
|
|
const char * str;
|
|
|
|
const char * format = 0;
|
|
AudioEncoder::BitrateMode bitrateMode;
|
|
unsigned int bitrate = 0;
|
|
double quality = 0.0;
|
|
const char * targetFileName = 0;
|
|
unsigned int sampleRate = 0;
|
|
int lowpass = 0;
|
|
int highpass = 0;
|
|
|
|
format = cs->getForSure( "format", " missing in section ", stream);
|
|
if ( !Util::strEq( format, "vorbis") && !Util::strEq( format, "mp3") ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"unsupported stream format: ", format);
|
|
}
|
|
|
|
str = cs->getForSure("bitrate", " missing in section ", stream);
|
|
bitrate = Util::strToL( str);
|
|
targetFileName = cs->getForSure( "fileName",
|
|
" missing in section ",
|
|
stream);
|
|
str = cs->get( "sampleRate");
|
|
sampleRate = str ? Util::strToL( str) : dsp->getSampleRate();
|
|
|
|
str = cs->get( "bitrate");
|
|
bitrate = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "quality");
|
|
quality = str ? Util::strToD( str) : 0.0;
|
|
|
|
str = cs->getForSure( "bitrateMode",
|
|
" not specified in section ",
|
|
stream);
|
|
if ( Util::strEq( str, "cbr") ) {
|
|
bitrateMode = AudioEncoder::cbr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for CBR encoding");
|
|
}
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for CBR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "abr") ) {
|
|
bitrateMode = AudioEncoder::abr;
|
|
|
|
if ( bitrate == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"bitrate not specified for ABR encoding");
|
|
}
|
|
} else if ( Util::strEq( str, "vbr") ) {
|
|
bitrateMode = AudioEncoder::vbr;
|
|
|
|
if ( quality == 0 ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"quality not specified for VBR encoding");
|
|
}
|
|
} else {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"invalid bitrate mode: ", str);
|
|
}
|
|
|
|
str = cs->get( "lowpass");
|
|
lowpass = str ? Util::strToL( str) : 0;
|
|
str = cs->get( "highpass");
|
|
highpass = str ? Util::strToL( str) : 0;
|
|
|
|
// go on and create the things
|
|
|
|
// the underlying file
|
|
FileSink * targetFile = new FileSink( targetFileName);
|
|
if ( !targetFile->exists() ) {
|
|
if ( !targetFile->create() ) {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"can't create output file", targetFileName);
|
|
}
|
|
}
|
|
|
|
// streaming related stuff
|
|
audioOuts[u].socket = 0;
|
|
audioOuts[u].server = new FileCast( targetFile );
|
|
|
|
if ( Util::strEq( format, "mp3") ) {
|
|
#ifndef HAVE_LAME_LIB
|
|
throw Exception( __FILE__, __LINE__,
|
|
"DarkIce not compiled with lame support, "
|
|
"thus can't create mp3 stream: ",
|
|
stream);
|
|
#else
|
|
audioOuts[u].encoder = new LameLibEncoder(
|
|
audioOuts[u].server.get(),
|
|
dsp.get(),
|
|
bitrateMode,
|
|
bitrate,
|
|
quality,
|
|
sampleRate,
|
|
dsp->getChannel(),
|
|
lowpass,
|
|
highpass );
|
|
#endif // HAVE_LAME_LIB
|
|
} else if ( Util::strEq( format, "vorbis") ) {
|
|
#ifndef HAVE_VORBIS_LIB
|
|
throw Exception( __FILE__, __LINE__,
|
|
"DarkIce not compiled with Ogg Vorbis support, "
|
|
"thus can't Ogg Vorbis stream: ",
|
|
stream);
|
|
#else
|
|
audioOuts[u].encoder = new VorbisLibEncoder(
|
|
audioOuts[u].server.get(),
|
|
dsp.get(),
|
|
bitrateMode,
|
|
bitrate,
|
|
quality,
|
|
dsp->getSampleRate(),
|
|
dsp->getChannel() );
|
|
#endif // HAVE_VORBIS_LIB
|
|
} else {
|
|
throw Exception( __FILE__, __LINE__,
|
|
"Illegal stream format: ", format);
|
|
}
|
|
|
|
encConnector->attach( audioOuts[u].encoder.get());
|
|
}
|
|
|
|
noAudioOuts += u;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Set POSIX real-time scheduling, if super-user
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: setRealTimeScheduling ( void ) throw ( Exception )
|
|
{
|
|
// don't include the following on OpenBSD / NetBSD, as the scheduling
|
|
// functions are not implemented.
|
|
#if !defined( __OpenBSD__ ) && !defined( __NetBSD__ )
|
|
uid_t euid;
|
|
|
|
euid = geteuid();
|
|
|
|
if ( euid == 0 ) {
|
|
int high_priority;
|
|
struct sched_param param;
|
|
|
|
/* store the old scheduling parameters */
|
|
if ( (origSchedPolicy = sched_getscheduler(0)) == -1 ) {
|
|
throw Exception( __FILE__, __LINE__, "sched_getscheduler", errno);
|
|
}
|
|
|
|
if ( sched_getparam( 0, ¶m) == -1 ) {
|
|
throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
|
|
}
|
|
origSchedPriority = param.sched_priority;
|
|
|
|
/* set SCHED_FIFO with max - 1 priority */
|
|
if ( (high_priority = sched_get_priority_max(SCHED_FIFO)) == -1 ) {
|
|
throw Exception(__FILE__,__LINE__,"sched_get_priority_max",errno);
|
|
}
|
|
reportEvent( 8, "scheduler high priority", high_priority);
|
|
|
|
param.sched_priority = high_priority - 1;
|
|
|
|
if ( sched_setscheduler( 0, SCHED_FIFO, ¶m) == -1 ) {
|
|
throw Exception( __FILE__, __LINE__, "sched_setscheduler", errno);
|
|
}
|
|
|
|
/* ask the new priortiy and report it */
|
|
if ( sched_getparam( 0, ¶m) == -1 ) {
|
|
throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
|
|
}
|
|
|
|
reportEvent( 1,
|
|
"Using POSIX real-time scheduling, priority",
|
|
param.sched_priority );
|
|
} else {
|
|
reportEvent( 1,
|
|
"Not running as super-user, unable to use POSIX real-time scheduling" );
|
|
reportEvent( 1,
|
|
"It is recommended that you run this program as super-user");
|
|
}
|
|
#else
|
|
reportEvent( 1, "POSIX scheduling not supported on OpenBSD / NetBSD, "
|
|
"this may cause recording skips");
|
|
#endif // !__OpenBSD__ && !__NetBSD__
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Set the original scheduling of the process, the one prior to the
|
|
* setRealTimeScheduling call.
|
|
* WARNING: make sure you don't call this before setRealTimeScheduling!!
|
|
*----------------------------------------------------------------------------*/
|
|
void
|
|
DarkIce :: setOriginalScheduling ( void ) throw ( Exception )
|
|
{
|
|
// don't include the following on OpenBSD / NetBSD, as the scheduling
|
|
// functions are not implemented.
|
|
#if !defined( __OpenBSD__ ) && !defined( __NetBSD__ )
|
|
uid_t euid;
|
|
|
|
euid = geteuid();
|
|
|
|
if ( euid == 0 ) {
|
|
struct sched_param param;
|
|
|
|
if ( sched_getparam( 0, ¶m) == -1 ) {
|
|
throw Exception( __FILE__, __LINE__, "sched_getparam", errno);
|
|
}
|
|
|
|
param.sched_priority = origSchedPriority;
|
|
|
|
if ( sched_setscheduler( 0, origSchedPolicy, ¶m) == -1 ) {
|
|
throw Exception( __FILE__, __LINE__, "sched_setscheduler", errno);
|
|
}
|
|
|
|
reportEvent( 5, "reverted to original scheduling");
|
|
}
|
|
#endif // !__OpenBSD__ && !__NetBSD__
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Run the encoder
|
|
*----------------------------------------------------------------------------*/
|
|
bool
|
|
DarkIce :: encode ( void ) throw ( Exception )
|
|
{
|
|
unsigned int len;
|
|
unsigned long bytes;
|
|
|
|
if ( !encConnector->open() ) {
|
|
throw Exception( __FILE__, __LINE__, "can't open connector");
|
|
}
|
|
|
|
bytes = dsp->getSampleRate() *
|
|
(dsp->getBitsPerSample() / 8UL) *
|
|
dsp->getChannel() *
|
|
duration;
|
|
|
|
len = encConnector->transfer( bytes, 4096, 1, 0 );
|
|
|
|
reportEvent( 1, len, "bytes transfered to the encoders");
|
|
|
|
encConnector->close();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
* Run
|
|
*----------------------------------------------------------------------------*/
|
|
int
|
|
DarkIce :: run ( void ) throw ( Exception )
|
|
{
|
|
reportEvent( 3, "encoding");
|
|
setRealTimeScheduling();
|
|
encode();
|
|
setOriginalScheduling();
|
|
reportEvent( 3, "encoding ends");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------------------
|
|
|
|
$Source$
|
|
|
|
$Log$
|
|
Revision 1.40 2004/02/23 19:12:51 darkeye
|
|
ported to NetBSD
|
|
|
|
Revision 1.39 2004/02/19 06:47:06 darkeye
|
|
finalized OpenBSD port
|
|
|
|
Revision 1.38 2004/02/18 21:08:11 darkeye
|
|
ported to OpenBSD (real-time scheduling not yet supported)
|
|
|
|
Revision 1.37 2004/02/15 12:14:38 darkeye
|
|
added patch to allow mp3 stream downsampling to mono for icecast2 as well
|
|
|
|
Revision 1.36 2004/02/15 12:06:30 darkeye
|
|
added ALSA support, thanks to Christian Forster
|
|
|
|
Revision 1.35 2003/02/09 13:15:57 darkeye
|
|
added feature for setting the TITLE comment field for vorbis streams
|
|
|
|
Revision 1.34 2003/02/09 12:57:36 darkeye
|
|
cosmetic changes to the fileAddDate option
|
|
|
|
Revision 1.33 2002/11/20 16:52:05 wandereq
|
|
added fileAddDate function
|
|
|
|
Revision 1.32 2002/10/19 12:24:55 darkeye
|
|
anged internals so that now each encoding/server connection is
|
|
a separate thread
|
|
|
|
Revision 1.31 2002/08/20 19:35:37 darkeye
|
|
added possibility to specify maximum bitrate for Ogg Vorbis streams
|
|
|
|
Revision 1.30 2002/08/20 18:37:49 darkeye
|
|
added mp3 streaming possibility for icecast2
|
|
|
|
Revision 1.29 2002/08/03 12:41:18 darkeye
|
|
added possibility to stream in mono when recording in stereo
|
|
|
|
Revision 1.28 2002/07/20 10:59:00 darkeye
|
|
added support for Ogg Vorbis 1.0, removed support for rc2
|
|
|
|
Revision 1.27 2002/04/13 11:26:00 darkeye
|
|
added cbr, abr and vbr setting feature with encoding quality
|
|
|
|
Revision 1.26 2002/03/28 16:43:11 darkeye
|
|
enabled resampling and variable bitrates for vorbis (icecast2) streams
|
|
|
|
Revision 1.25 2002/02/28 09:49:25 darkeye
|
|
added possibility to save the encoded stream to a local file only
|
|
(no streaming server needed)
|
|
|
|
Revision 1.24 2002/02/20 11:54:11 darkeye
|
|
added local dump file possibility
|
|
|
|
Revision 1.23 2002/02/20 10:35:35 darkeye
|
|
updated to work with Ogg Vorbis libs rc3 and current IceCast2 cvs
|
|
|
|
Revision 1.22 2001/10/20 10:56:45 darkeye
|
|
added possibility to disable highpass and lowpass filters for lame
|
|
|
|
Revision 1.21 2001/10/19 12:39:42 darkeye
|
|
created configure options to compile with or without lame / Ogg Vorbis
|
|
|
|
Revision 1.20 2001/10/19 09:03:39 darkeye
|
|
added support for resampling mp3 streams
|
|
|
|
Revision 1.19 2001/09/14 19:31:06 darkeye
|
|
added IceCast2 / vorbis support
|
|
|
|
Revision 1.18 2001/09/11 15:05:21 darkeye
|
|
added Solaris support
|
|
|
|
Revision 1.17 2001/09/09 11:27:31 darkeye
|
|
added support for ShoutCast servers
|
|
|
|
Revision 1.16 2001/09/05 20:11:15 darkeye
|
|
removed dependency on locally stored SGI STL header files
|
|
now compiler-supplied C++ library STL header files are used
|
|
compiles under GNU C++ 3
|
|
hash_map (an SGI extension to STL) replaced with map
|
|
std:: namespace prefix added to all STL class references
|
|
|
|
Revision 1.15 2001/08/30 17:25:56 darkeye
|
|
renamed configure.h to config.h
|
|
|
|
Revision 1.14 2001/08/29 21:08:30 darkeye
|
|
made some description options in the darkice config file optional
|
|
|
|
Revision 1.13 2001/08/26 20:44:30 darkeye
|
|
removed external command-line encoder support
|
|
replaced it with a shared-object support for lame with the possibility
|
|
of static linkage
|
|
|
|
Revision 1.12 2000/12/20 12:36:47 darkeye
|
|
added POSIX real-time scheduling
|
|
|
|
Revision 1.11 2000/11/18 11:13:27 darkeye
|
|
removed direct reference to cout, except from main.cpp
|
|
all class use the Reporter interface to report events
|
|
|
|
Revision 1.10 2000/11/17 15:50:48 darkeye
|
|
added -Wall flag to compiler and eleminated new warnings
|
|
|
|
Revision 1.9 2000/11/15 18:37:37 darkeye
|
|
changed the transferable number of bytes to unsigned long
|
|
|
|
Revision 1.8 2000/11/15 18:08:43 darkeye
|
|
added multiple verbosity-level event reporting and verbosity command
|
|
line option
|
|
|
|
Revision 1.7 2000/11/13 19:38:55 darkeye
|
|
moved command line parameter parsing from DarkIce.cpp to main.cpp
|
|
|
|
Revision 1.6 2000/11/13 18:46:50 darkeye
|
|
added kdoc-style documentation comments
|
|
|
|
Revision 1.5 2000/11/10 20:16:21 darkeye
|
|
first real tests with multiple streaming
|
|
|
|
Revision 1.4 2000/11/09 22:09:46 darkeye
|
|
added multiple outputs
|
|
added configuration reading
|
|
added command line processing
|
|
|
|
Revision 1.3 2000/11/08 17:29:50 darkeye
|
|
added configuration file reader
|
|
|
|
Revision 1.2 2000/11/05 14:08:27 darkeye
|
|
changed builting to an automake / autoconf environment
|
|
|
|
Revision 1.1.1.1 2000/11/05 10:05:49 darkeye
|
|
initial version
|
|
|
|
|
|
------------------------------------------------------------------------------*/
|
|
|