Add even more of the source
This should be about everything needed to build so far?
This commit is contained in:
parent
af3619d4fa
commit
849723c9cf
547 changed files with 149239 additions and 0 deletions
301
MP3Broadcaster/BroadcasterMain.cpp
Executable file
301
MP3Broadcaster/BroadcasterMain.cpp
Executable file
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef kVersionString
|
||||
#include "../revision.h"
|
||||
#endif
|
||||
#include "MP3Broadcaster.h"
|
||||
#include "OSHeaders.h"
|
||||
#include "SocketUtils.h"
|
||||
#include "OSThread.h"
|
||||
#include "OS.h"
|
||||
#include "OSMemory.h"
|
||||
#include <signal.h>
|
||||
#include "getopt.h"
|
||||
|
||||
MP3Broadcaster* gBroadcaster = NULL;
|
||||
|
||||
static void RegisterEventHandlers();
|
||||
static void SignalEventHandler( int signalID );
|
||||
|
||||
void version()
|
||||
{
|
||||
/*
|
||||
print PlaylistBroadcaster version and build info
|
||||
|
||||
see revision.h
|
||||
*/
|
||||
|
||||
//RoadRunner/2.0.0-v24 Built on: Sep 17 1999 , 16:09:53
|
||||
//revision.h (project level file) -- include with -i option
|
||||
qtss_printf("MP3Broadcaster/%s Built on: %s, %s\n", kVersionString, __DATE__, __TIME__ );
|
||||
|
||||
}
|
||||
|
||||
void usage()
|
||||
{
|
||||
/*
|
||||
print MP3Broadcaster usage string
|
||||
|
||||
*/
|
||||
|
||||
qtss_printf("usage: MP3Broadcaster [-v] [-d] [-i] [-x] [-X] [-a ipAddress] [-p portNum] [-l filename] [-w filename] [-e filename] -c filename\n" );
|
||||
qtss_printf(" -v: display version\n" );
|
||||
qtss_printf(" -d: run in foreground\n" );
|
||||
qtss_printf(" -i: use 'icy-' protocol header prefix (default is 'x-audio-')\n" );
|
||||
qtss_printf(" -x: preflight configuration\n" );
|
||||
qtss_printf(" -X: check MP3 files\n" );
|
||||
qtss_printf(" -a <ipaddr>: broadcast to this address (default = local loopback)\n" );
|
||||
qtss_printf(" -p <port>: broadcast to this port (default = 8000)\n" );
|
||||
qtss_printf(" -c <path>: path to config file\n" );
|
||||
qtss_printf(" -l <path>: path to playlist (overrides config file)\n" );
|
||||
qtss_printf(" -w <path>: path to dir to create temp lists (overrides config file)\n" );
|
||||
qtss_printf(" -e <path>: print output to error file\n" );
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
bool daemonize = true;
|
||||
bool useICY = false;
|
||||
char* ipaddr = NULL;
|
||||
int port = 0;
|
||||
char* config = NULL;
|
||||
char* playList = NULL;
|
||||
char* workingDir = NULL;
|
||||
char ch;
|
||||
bool preflight = false;
|
||||
bool checkMP3s = false;
|
||||
char* errorlog = NULL;
|
||||
// extern int optind;
|
||||
extern char* optarg;
|
||||
|
||||
#ifdef __Win32__
|
||||
//
|
||||
// Start Win32 DLLs
|
||||
WORD wsVersion = MAKEWORD(1, 1);
|
||||
WSADATA wsData;
|
||||
(void)::WSAStartup(wsVersion, &wsData);
|
||||
#endif
|
||||
|
||||
OS::Initialize();
|
||||
OSThread::Initialize();
|
||||
OSMemory::SetMemoryError(ENOMEM);
|
||||
Socket::Initialize();
|
||||
SocketUtils::Initialize(false);
|
||||
|
||||
if (SocketUtils::GetNumIPAddrs() == 0)
|
||||
{
|
||||
qtss_printf("Network initialization failed. IP must be enabled to run MP3Broadcaster\n");
|
||||
::exit(0);
|
||||
}
|
||||
|
||||
while ( (ch = getopt(argc,argv, "vdixXa:p:c:l:e:w:")) != EOF ) // opt: means requires option
|
||||
{
|
||||
switch(ch)
|
||||
{
|
||||
case 'v':
|
||||
::version();
|
||||
::usage();
|
||||
return 0;
|
||||
|
||||
case 'd':
|
||||
daemonize = false;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
useICY = true;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
ipaddr = (char*)malloc(strlen(optarg)+1);
|
||||
strcpy(ipaddr, optarg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
config = (char*)malloc(strlen(optarg)+1);
|
||||
strcpy(config, optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
playList = (char*)malloc(strlen(optarg)+1);
|
||||
strcpy(playList, optarg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
workingDir = (char*)malloc(strlen(optarg)+1);
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
preflight = true;
|
||||
daemonize = false;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
preflight = true;
|
||||
daemonize = false;
|
||||
checkMP3s = true;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
errorlog = (char*)malloc(strlen(optarg)+1);
|
||||
strcpy(errorlog, optarg);
|
||||
break;
|
||||
|
||||
default:
|
||||
::usage();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (config == NULL)
|
||||
{
|
||||
qtss_printf("missing -c option\n");
|
||||
::usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (errorlog != NULL)
|
||||
{
|
||||
if (preflight)
|
||||
freopen(errorlog, "w", stdout);
|
||||
else
|
||||
freopen(errorlog, "a", stdout);
|
||||
::setvbuf(stdout, (char *)NULL, _IONBF, 0);
|
||||
}
|
||||
|
||||
gBroadcaster = new MP3Broadcaster(ipaddr, port, config, playList, workingDir, useICY);
|
||||
|
||||
if (!gBroadcaster->IsValid())
|
||||
{
|
||||
qtss_printf("Bad config--exiting\n");
|
||||
qtss_printf("Warnings: 0\nErrors: 1\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
RegisterEventHandlers();
|
||||
gBroadcaster->PreFlightOrBroadcast(preflight, daemonize, false, false, checkMP3s, errorlog);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void RegisterEventHandlers()
|
||||
{
|
||||
#ifdef __Win32__
|
||||
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) SignalEventHandler, true);
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifndef __Win32__
|
||||
|
||||
struct sigaction act;
|
||||
|
||||
#if defined(sun) || defined(i386) || defined(__MacOSX__) || defined(__sgi__) || defined(__osf__) || defined(__hpux__) || defined(__linux) || defined(__linuxppc__)
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
act.sa_handler = (void(*)(int))&SignalEventHandler;
|
||||
#else
|
||||
act.sa_mask = 0;
|
||||
act.sa_flags = 0;
|
||||
act.sa_handler = (void(*)(...))&SignalEventHandler;
|
||||
#endif
|
||||
|
||||
if ( ::signal(SIGTERM, SIG_IGN) != SIG_IGN)
|
||||
{ // from kill...
|
||||
if ( ::sigaction(SIGTERM, &act, NULL) != 0 )
|
||||
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ::signal(SIGINT, SIG_IGN) != SIG_IGN)
|
||||
{ // ^C signal
|
||||
if ( ::sigaction(SIGINT, &act, NULL) != 0 )
|
||||
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ::signal(SIGPIPE, SIG_IGN) != SIG_IGN)
|
||||
{ // broken pipe probably from a failed RTSP session (the server went down?)
|
||||
if ( ::sigaction(SIGPIPE, &act, NULL) != 0 )
|
||||
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ::signal(SIGHUP, SIG_IGN) != SIG_IGN)
|
||||
{ // catch any SIGHUP
|
||||
if ( ::sigaction(SIGHUP, &act, NULL) != 0)
|
||||
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ::signal(SIGALRM, SIG_IGN) != SIG_IGN)
|
||||
{ // catch any SIGALRM
|
||||
if ( ::sigaction(SIGALRM, &act, NULL) != 0)
|
||||
{ qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* Signal and error handler.
|
||||
*/
|
||||
static void SignalEventHandler( int signalID )
|
||||
{
|
||||
if (gBroadcaster)
|
||||
{
|
||||
#ifdef __Win32__
|
||||
if ( (signalID != SIGINT) && (signalID != SIGTERM) )
|
||||
#else
|
||||
if ( (signalID == SIGALRM) || (signalID == SIGHUP) ) // unexpected SIGALRM || SIGHUP
|
||||
|
||||
{
|
||||
|
||||
qtss_printf( "- PlaylistBroadcaster: Unexpected signal type (%"_SPOINTERSIZEARG_").\n", signalID );
|
||||
|
||||
// just ignore it...
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (signalID == SIGPIPE) // broken pipe from server
|
||||
#endif
|
||||
|
||||
gBroadcaster->Cleanup(true);
|
||||
else // kill or ^C
|
||||
gBroadcaster->Cleanup(false);
|
||||
}
|
||||
::exit(-1);
|
||||
}
|
1048
MP3Broadcaster/MP3Broadcaster.cpp
Executable file
1048
MP3Broadcaster/MP3Broadcaster.cpp
Executable file
File diff suppressed because it is too large
Load diff
124
MP3Broadcaster/MP3Broadcaster.h
Executable file
124
MP3Broadcaster/MP3Broadcaster.h
Executable file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MP3Broadcaster_H__
|
||||
#define __MP3Broadcaster_H__
|
||||
|
||||
#include "OSHeaders.h"
|
||||
#include "TCPSocket.h"
|
||||
#include "SocketUtils.h"
|
||||
#include "PickerFromFile.h"
|
||||
#include "MP3BroadcasterLog.h"
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 1024
|
||||
#endif
|
||||
|
||||
class MP3Broadcaster
|
||||
{
|
||||
public:
|
||||
MP3Broadcaster(char* ipaddr, int port, char* config, char* playList, char* workingDir, Bool16 useICY = false);
|
||||
~MP3Broadcaster(){}
|
||||
|
||||
//void SetBroadcastAddr(char* addrString);
|
||||
//void SetConfigPath(char* path);
|
||||
//void SetBitRate(int bitRate);
|
||||
//void SetPlayList(char* path);
|
||||
|
||||
//Bool16 CheckConfig();
|
||||
int ConnectToServer();
|
||||
void PreFlightOrBroadcast( bool preflight, bool daemonize, bool showMovieList, bool currentMovie, bool checkMP3s, const char* errorlog);
|
||||
|
||||
Bool16 IsValid() { return (Bool16) mValid; }
|
||||
|
||||
char* GetPIDFilePath() { return mPIDFile; }
|
||||
|
||||
void Cleanup(bool signalHandler = false);
|
||||
|
||||
private:
|
||||
static Bool16 ConfigSetter( const char* paramName, const char* paramValue[], void * userData );
|
||||
static Bool16 SetEnabled( const char* value, Bool16* field);
|
||||
static void PrintPlaylistElement(PLDoubleLinkedListNode<SimplePlayListElement> *node,void *file);
|
||||
|
||||
void CreateWorkingFilePath(char* extension, char* result);
|
||||
bool FileCreateAndCheckAccess(char *theFileName);
|
||||
void CreateCurrentAndUpcomingFiles();
|
||||
void UpdatePlaylistFiles(PlaylistPicker *picker,PlaylistPicker *insertPicker);
|
||||
void UpdateCurrentFile(char *thePick);
|
||||
void ShowPlaylistElements(PlaylistPicker *picker,FILE *file);
|
||||
//char* GetBroadcastDirPath(const char * setupFilePath);
|
||||
PlaylistPicker* MakePickerFromConfig();
|
||||
int SendXAudioCastHeaders();
|
||||
void ShowSetupParams();
|
||||
void RemoveFiles();
|
||||
char* MapErrorToString(int error);
|
||||
|
||||
Bool16 mValid;
|
||||
|
||||
char mIPAddr[256];
|
||||
int mPort;
|
||||
int mBitRate;
|
||||
int mFrequency;
|
||||
char mPlayListPath[PATH_MAX];
|
||||
char mWorkingDirPath[PATH_MAX];
|
||||
char mCurrentFile[PATH_MAX];
|
||||
char mUpcomingFile[PATH_MAX];
|
||||
char mReplaceFile[PATH_MAX];
|
||||
char mStopFile[PATH_MAX];
|
||||
char mInsertFile[PATH_MAX];
|
||||
char mLogFile[PATH_MAX];
|
||||
char mPIDFile[PATH_MAX];
|
||||
|
||||
char mPlayMode[256];
|
||||
int mUpcomingSongsListSize;
|
||||
int mRecentSongsListSize;
|
||||
char mName[256];
|
||||
char mGenre[256];
|
||||
char mPassword[256];
|
||||
char mURL[PATH_MAX];
|
||||
char mMountPoint[PATH_MAX];
|
||||
|
||||
Bool16 mLogging;
|
||||
Bool16 mShowCurrent;
|
||||
Bool16 mShowUpcoming;
|
||||
|
||||
SInt32 mNumErrors;
|
||||
SInt32 mNumWarnings;
|
||||
bool mPreflight;
|
||||
bool mCleanupDone;
|
||||
|
||||
PlaylistPicker* mTempPicker;
|
||||
int mElementCount;
|
||||
|
||||
TCPSocket mSocket;
|
||||
MP3BroadcasterLog* mLog;
|
||||
Bool16 mUseICY;
|
||||
|
||||
static MP3Broadcaster* sBroadcaster;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
259
MP3Broadcaster/MP3BroadcasterLog.cpp
Normal file
259
MP3Broadcaster/MP3BroadcasterLog.cpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
|
||||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "MP3BroadcasterLog.h"
|
||||
|
||||
#ifndef kVersionString
|
||||
#include "../revision.h"
|
||||
#endif
|
||||
|
||||
static Bool16 sLogTimeInGMT = false;
|
||||
|
||||
static char* sLogHeader = "#Software: %s\n"
|
||||
"#Version: %s\n" //%s == version
|
||||
"#Date: %s\n" //%s == date/time
|
||||
"#Remark: All date values are in %s.\n" //%s == "GMT" or "local time"
|
||||
"#Fields: date time filepath title artist album duration result\n";
|
||||
|
||||
MP3BroadcasterLog::MP3BroadcasterLog( char* defaultPath, char* logName, Bool16 enabled )
|
||||
: QTSSRollingLog()
|
||||
{
|
||||
this->SetTaskName("MP3BroadcasterLog");
|
||||
*mDirPath = 0;
|
||||
*mLogFileName = 0;
|
||||
mWantsLogging = false;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
mWantsLogging = true;
|
||||
|
||||
// check if logName is a full path
|
||||
::strcpy( mDirPath, logName );
|
||||
char* nameBegins = ::strrchr( mDirPath, kPathDelimiterChar );
|
||||
if ( nameBegins )
|
||||
{
|
||||
*nameBegins = 0; // terminate mDirPath at the last PathDelimeter
|
||||
nameBegins++;
|
||||
::strcpy( mLogFileName, nameBegins );
|
||||
}
|
||||
else
|
||||
{ // it was just a file name, no dir spec'd
|
||||
::strcpy( mDirPath, defaultPath );
|
||||
::strcpy( mLogFileName, logName );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this->SetLoggingEnabled(mWantsLogging);
|
||||
|
||||
}
|
||||
|
||||
time_t MP3BroadcasterLog::WriteLogHeader(FILE *inFile)
|
||||
{
|
||||
// Write a W3C compatable log header
|
||||
time_t calendarTime = ::time(NULL);
|
||||
Assert(-1 != calendarTime);
|
||||
if (-1 == calendarTime)
|
||||
return -1;
|
||||
|
||||
struct tm timeResult;
|
||||
struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult);
|
||||
Assert(NULL != theLocalTime);
|
||||
if (NULL == theLocalTime)
|
||||
return -1;
|
||||
|
||||
char tempBuffer[1024] = { 0 };
|
||||
qtss_strftime(tempBuffer, sizeof(tempBuffer), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime);
|
||||
this->WriteToLog(tempBuffer, !kAllowLogToRoll);
|
||||
tempBuffer[0] = '\0';
|
||||
|
||||
// format a date for the startup time
|
||||
|
||||
char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes] = { 0 };
|
||||
Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false);
|
||||
|
||||
if (result)
|
||||
{
|
||||
qtss_sprintf(tempBuffer, sLogHeader, "MP3Broadcaster" , kVersionString,
|
||||
theDateBuffer, sLogTimeInGMT ? "GMT" : "local time");
|
||||
this->WriteToLog(tempBuffer, !kAllowLogToRoll);
|
||||
}
|
||||
|
||||
return calendarTime;
|
||||
}
|
||||
|
||||
|
||||
void MP3BroadcasterLog::LogInfo( const char* infoStr )
|
||||
{
|
||||
// log a generic comment
|
||||
char strBuff[1024] = "";
|
||||
char dateBuff[80] = "";
|
||||
|
||||
if ( this->FormatDate( dateBuff, false ) )
|
||||
{
|
||||
if ( (NULL != infoStr)
|
||||
&& ( ( strlen(infoStr) + strlen(strBuff) + strlen(dateBuff) ) < 800)
|
||||
)
|
||||
{
|
||||
qtss_sprintf(strBuff,"#Remark: %s %s\n",dateBuff, infoStr);
|
||||
this->WriteToLog( strBuff, kAllowLogToRoll );
|
||||
}
|
||||
else
|
||||
{
|
||||
::strcat(strBuff,dateBuff);
|
||||
::strcat(strBuff," internal error in LogInfo\n");
|
||||
this->WriteToLog( strBuff, kAllowLogToRoll );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MP3BroadcasterLog::LogMediaError( const char* path, const char* errStr , const char* messageStr)
|
||||
{
|
||||
// log movie play info
|
||||
char strBuff[1024] = "";
|
||||
char dateBuff[80] = "";
|
||||
|
||||
if ( this->FormatDate( dateBuff, false ) )
|
||||
{
|
||||
if ( (NULL != path)
|
||||
&& ( (strlen(path) + strlen(dateBuff) ) < 800)
|
||||
)
|
||||
{
|
||||
|
||||
qtss_sprintf(strBuff,"#Remark: %s %s ",dateBuff, path);
|
||||
|
||||
if ( errStr )
|
||||
{ if ( (strlen(strBuff) + strlen(errStr) ) < 1000 )
|
||||
{
|
||||
::strcat(strBuff,"Error:");
|
||||
::strcat(strBuff,errStr);
|
||||
}
|
||||
}
|
||||
else
|
||||
if ( (NULL != messageStr)
|
||||
&&
|
||||
( (strlen(strBuff) + strlen(messageStr) ) < 1000 )
|
||||
)
|
||||
{ ::strcat(strBuff,messageStr);
|
||||
}
|
||||
else
|
||||
::strcat(strBuff,"OK");
|
||||
|
||||
::strcat(strBuff,"\n");
|
||||
this->WriteToLog(strBuff, kAllowLogToRoll );
|
||||
}
|
||||
else
|
||||
{
|
||||
::strcat(strBuff,dateBuff);
|
||||
::strcat(strBuff," internal error in LogMediaError\n");
|
||||
this->WriteToLog( strBuff, kAllowLogToRoll );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MP3BroadcasterLog::LogMediaData( const char* song, const char* title, const char* artist, const char* album,
|
||||
UInt32 duration, SInt16 result)
|
||||
{
|
||||
// log movie play info
|
||||
char strBuff[1024] = "";
|
||||
char dateBuff[80] = "";
|
||||
|
||||
if ( this->FormatDate( dateBuff, false ) )
|
||||
{
|
||||
if ( (NULL != song)
|
||||
&& ( (strlen(song) + strlen(dateBuff) ) < 800)
|
||||
)
|
||||
{
|
||||
|
||||
qtss_sprintf(strBuff,"%s '%s'",dateBuff, song);
|
||||
|
||||
if ( title || title[0] != 0)
|
||||
{ if ( (strlen(strBuff) + strlen(title) ) < 1000 )
|
||||
{
|
||||
::strcat(strBuff," '");
|
||||
::strcat(strBuff,title);
|
||||
::strcat(strBuff,"'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
::strcat(strBuff," -");
|
||||
}
|
||||
|
||||
if ( artist || artist[0] != 0)
|
||||
{ if ( (strlen(strBuff) + strlen(artist) ) < 1000 )
|
||||
{
|
||||
::strcat(strBuff," '");
|
||||
::strcat(strBuff,artist);
|
||||
::strcat(strBuff,"'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
::strcat(strBuff," -");
|
||||
}
|
||||
|
||||
if ( album || album[0] != 0)
|
||||
{ if ( (strlen(strBuff) + strlen(album) ) < 1000 )
|
||||
{
|
||||
::strcat(strBuff," '");
|
||||
::strcat(strBuff,album);
|
||||
::strcat(strBuff,"'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
::strcat(strBuff," -");
|
||||
}
|
||||
|
||||
// add the duration in seconds
|
||||
qtss_sprintf(dateBuff, " %"_S32BITARG_" ", duration);
|
||||
::strcat(strBuff,dateBuff);
|
||||
|
||||
// add the result code
|
||||
qtss_sprintf(dateBuff, " %d\n", result);
|
||||
::strcat(strBuff,dateBuff);
|
||||
|
||||
this->WriteToLog(strBuff, kAllowLogToRoll );
|
||||
}
|
||||
else
|
||||
{
|
||||
::strcat(strBuff,dateBuff);
|
||||
::strcat(strBuff," internal error in LogMediaData\n");
|
||||
this->WriteToLog( strBuff, kAllowLogToRoll );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
84
MP3Broadcaster/MP3BroadcasterLog.h
Normal file
84
MP3Broadcaster/MP3BroadcasterLog.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MP3BroadcasterLog_h__
|
||||
#define __MP3BroadcasterLog_h__
|
||||
|
||||
#include "QTSSRollingLog.h"
|
||||
#include "StrPtrLen.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
class MP3BroadcasterLog : public QTSSRollingLog
|
||||
{
|
||||
enum { eLogMaxBytes = 0, eLogMaxDays = 0 };
|
||||
|
||||
public:
|
||||
MP3BroadcasterLog( char* defaultPath, char* logName, Bool16 enabled );
|
||||
virtual ~MP3BroadcasterLog() {}
|
||||
|
||||
virtual char* GetLogName()
|
||||
{ // RTSPRollingLog wants to see a "new'd" copy of the file name
|
||||
char* name = new char[strlen( mLogFileName ) + 1 ];
|
||||
|
||||
if ( name )
|
||||
::strcpy( name, mLogFileName );
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
virtual char* GetLogDir()
|
||||
{ // RTSPRollingLog wants to see a "new'd" copy of the file name
|
||||
char *name = new char[strlen( mDirPath ) + 1 ];
|
||||
|
||||
if ( name )
|
||||
::strcpy( name, mDirPath );
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
virtual UInt32 GetRollIntervalInDays() { return eLogMaxDays; /* we dont' roll*/ }
|
||||
|
||||
virtual UInt32 GetMaxLogBytes() { return eLogMaxBytes; /* we dont' roll*/ }
|
||||
|
||||
void LogInfo( const char* infoStr );
|
||||
void LogMediaError( const char* path, const char* errStr, const char* messageStr);
|
||||
void LogMediaData( const char* song, const char* title, const char* artist, const char* album,
|
||||
UInt32 duration, SInt16 result);
|
||||
|
||||
bool WantsLogging() { return mWantsLogging; }
|
||||
const char* LogFileName() { return mLogFileName; }
|
||||
const char* LogDirName() { return mDirPath; }
|
||||
|
||||
virtual time_t WriteLogHeader(FILE *inFile);
|
||||
|
||||
protected:
|
||||
char mDirPath[256];
|
||||
char mLogFileName[256];
|
||||
bool mWantsLogging;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
593
MP3Broadcaster/MP3FileBroadcaster.cpp
Executable file
593
MP3Broadcaster/MP3FileBroadcaster.cpp
Executable file
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MP3FileBroadcaster.h"
|
||||
#include <fcntl.h>
|
||||
//#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "OS.h"
|
||||
#include "OSThread.h"
|
||||
|
||||
int gBitRateArray[] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 };
|
||||
int gFrequencyArray[] = { 44100, 48000, 32000, 0 };
|
||||
|
||||
int gBitRateArrayv2[] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 };
|
||||
int gFrequencyArrayv2[] = { 22050, 24000, 16000, 0 };
|
||||
int gFrequencyArrayv2_5[] = { 11025, 12000, 8000, 0 };
|
||||
|
||||
MP3FileBroadcaster::MP3FileBroadcaster(TCPSocket* socket, int bitrate, int frequency, int bufferSize) :
|
||||
mSocket(socket),
|
||||
mBitRate(bitrate),
|
||||
mBufferSize(bufferSize),
|
||||
mNumFramesSent(0),
|
||||
mBroadcastStartTime(0),
|
||||
mBuffer(NULL),
|
||||
mUpdater(NULL),
|
||||
mDesiredBitRate(bitrate),
|
||||
mDesiredFrequency(frequency)
|
||||
{
|
||||
mBuffer = new unsigned char[bufferSize];
|
||||
mTitle[0] = 0;
|
||||
mArtist[0] = 0;
|
||||
mSong[0] = 0;
|
||||
}
|
||||
|
||||
MP3FileBroadcaster::~MP3FileBroadcaster()
|
||||
{
|
||||
delete [] mBuffer;
|
||||
}
|
||||
|
||||
int MP3FileBroadcaster::PlaySong(char *fileName, char *currentFile, bool preflight, bool fastpreflight)
|
||||
{
|
||||
UInt32 length, lengthSent;
|
||||
|
||||
if (mBuffer == NULL)
|
||||
return -1;
|
||||
|
||||
mFile.Set(fileName);
|
||||
if (!mFile.IsValid())
|
||||
return kCouldntOpenFile;
|
||||
|
||||
CheckForTags();
|
||||
|
||||
if (strlen(mTitle) == 0)
|
||||
{
|
||||
char* temp = fileName+strlen(fileName);
|
||||
while ((temp > fileName) && (*(temp-1) != kPathDelimiterChar))
|
||||
temp--;
|
||||
|
||||
::strncpy(mTitle, temp,sizeof(mTitle) -1);
|
||||
mTitle[sizeof(mTitle) -1] = 0;
|
||||
}
|
||||
|
||||
if (strlen(mArtist) != 0 && strlen(mAlbum) != 0)
|
||||
qtss_sprintf(mSong, "%s - %s (%s)", mTitle, mArtist, mAlbum);
|
||||
else if (strlen(mArtist) != 0)
|
||||
qtss_sprintf(mSong, "%s - %s", mTitle, mArtist);
|
||||
else
|
||||
::strcpy(mSong, mTitle);
|
||||
|
||||
if (preflight)
|
||||
qtss_printf("Preflighting %s\n", mSong);
|
||||
|
||||
// skip all the padding at the beginning of the file
|
||||
mFile.Seek(mStartByte);
|
||||
bool done = false;
|
||||
|
||||
OS_Error err = mFile.Read(mBuffer, mBufferSize, &length);
|
||||
if (err != OS_NoErr)
|
||||
return -1;
|
||||
|
||||
for(UInt32 i = 0; i<length-1000; i++)
|
||||
{
|
||||
if ((mBuffer[i] == 0xff) && CheckHeaders(mBuffer + i) )
|
||||
{
|
||||
mStartByte += i;
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!done)
|
||||
{
|
||||
mFile.Close();
|
||||
return kBadFileFormat;
|
||||
}
|
||||
|
||||
if (mDesiredBitRate && (mBitRate != mDesiredBitRate))
|
||||
{
|
||||
qtss_printf("File %s : ",fileName);
|
||||
qtss_printf("Bitrate = %dkbits, frequency = %dKHz\n", mBitRate, mFrequency/1000);
|
||||
|
||||
mFile.Close();
|
||||
return kWrongBitRate;
|
||||
}
|
||||
|
||||
if (mDesiredFrequency == -1)
|
||||
{
|
||||
mDesiredFrequency = mFrequency;
|
||||
qtss_printf("Setting required frequency to %dKHz\n", mFrequency/1000);
|
||||
}
|
||||
|
||||
if (mDesiredFrequency && (mFrequency != mDesiredFrequency))
|
||||
{
|
||||
qtss_printf("File %s : ",fileName);
|
||||
qtss_printf("Bitrate = %dkbits, frequency = %dKHz\n", mBitRate, mFrequency/1000);
|
||||
|
||||
mFile.Close();
|
||||
return kWrongFrequency;
|
||||
}
|
||||
|
||||
if (mUpdater)
|
||||
mUpdater->RequestMetaInfoUpdate(mSong);
|
||||
|
||||
mFile.Seek(mStartByte);
|
||||
|
||||
if (mBroadcastStartTime == 0)
|
||||
mBroadcastStartTime = OS::Milliseconds();
|
||||
//unused UInt64 startTime = OS::Milliseconds();
|
||||
int totalBytes = 0;
|
||||
//unused int numFrames = 0; // amount of play time this buffer represents
|
||||
int leftOver = 0; // we may have a partial buffer left over from last read
|
||||
SInt64 properElapsedTime;
|
||||
while(true)
|
||||
{
|
||||
if (!preflight)
|
||||
{
|
||||
// the time length each frame represents depends on the frequency
|
||||
int numSamplesPerFrame = mIsMPEG2 ? 576 : 1152; // these are MP3 standards
|
||||
properElapsedTime = mNumFramesSent * numSamplesPerFrame * 1000 / mFrequency; // frequency is samples per second
|
||||
SInt64 nextSendTime = mBroadcastStartTime + properElapsedTime;
|
||||
|
||||
SInt64 currentTime = OS::Milliseconds();
|
||||
if (nextSendTime > currentTime)
|
||||
OSThread::Sleep( (UInt32) (nextSendTime - currentTime));
|
||||
}
|
||||
|
||||
length = 0;
|
||||
err = mFile.Read(mBuffer + leftOver, mBufferSize - leftOver, &length);
|
||||
if ((err != OS_NoErr) || (length == 0))
|
||||
break;
|
||||
|
||||
length += leftOver;
|
||||
mNumFramesSent += CountFrames(mBuffer, length, &leftOver);
|
||||
|
||||
if (!preflight)
|
||||
{
|
||||
OS_Error err = mSocket->Send((char*)mBuffer, length - leftOver, &lengthSent);
|
||||
if (err != 0)
|
||||
{
|
||||
mFile.Close();
|
||||
return kConnectionError;
|
||||
}
|
||||
}
|
||||
|
||||
totalBytes += length;
|
||||
if (leftOver > 0)
|
||||
::memcpy(mBuffer, mBuffer+length-leftOver, leftOver);
|
||||
|
||||
if (preflight && fastpreflight && (totalBytes > 20 * 1024))
|
||||
break; // just check first 20K of file
|
||||
}
|
||||
|
||||
// UInt64 elapsed = OS::Milliseconds() - startTime;
|
||||
// UInt64 rate = (UInt64)totalBytes * 8 * 1000 / elapsed;
|
||||
// qtss_printf("Sent %d bytes in %qd milliseconds = %qd bits per second\n", totalBytes, elapsed, rate);
|
||||
|
||||
if ((mDesiredFrequency == 0) || preflight)
|
||||
{
|
||||
// if we aren't fixing the frequency, then we need to time each song seperately
|
||||
mBroadcastStartTime = OS::Milliseconds();
|
||||
mNumFramesSent = 0;
|
||||
}
|
||||
|
||||
mFile.Close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MP3FileBroadcaster::CheckForTags()
|
||||
{
|
||||
mArtist[0] = 0;
|
||||
mTitle[0] = 0;
|
||||
mAlbum[0] = 0;
|
||||
mStartByte = 0;
|
||||
|
||||
if (ReadV2_3Tags())
|
||||
return;
|
||||
|
||||
if (ReadV2_2Tags())
|
||||
return;
|
||||
|
||||
if (ReadV1Tags())
|
||||
return;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool MP3FileBroadcaster::ReadV1Tags()
|
||||
{
|
||||
char buffer[128] = "";
|
||||
UInt32 length = 0;
|
||||
int i;
|
||||
|
||||
mFile.Seek(mFile.GetLength()-128);
|
||||
OS_Error err = mFile.Read(buffer, sizeof(buffer), &length);
|
||||
if ((err != OS_NoErr) || (length != 128))
|
||||
return false;
|
||||
|
||||
if (strncmp(buffer, "TAG", 3))
|
||||
return false;
|
||||
|
||||
// Song Title
|
||||
// stored as space padded 30 byte buffer (no null termination)
|
||||
memcpy(mTitle, buffer+3, 30);
|
||||
for (i = 29; i>=0; i--)
|
||||
if (mTitle[i] != ' ')
|
||||
{
|
||||
mTitle[i+1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Artist Name
|
||||
// stored as space padded 30 byte buffer (no null termination)
|
||||
memcpy(mArtist, buffer+33, 30);
|
||||
for (i = 29; i>=0; i--)
|
||||
if (mArtist[i] != ' ')
|
||||
{
|
||||
mArtist[i+1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Album Title
|
||||
// stored as space padded 30 byte buffer (no null termination)
|
||||
memcpy(mAlbum, buffer+63, 30);
|
||||
for (i = 29; i>=0; i--)
|
||||
if (mAlbum[i] != ' ')
|
||||
{
|
||||
mAlbum[i+1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MP3FileBroadcaster::ReadV2_2Tags()
|
||||
{
|
||||
char buffer[1024] = "";
|
||||
UInt32 length = 0;
|
||||
|
||||
mFile.Seek(0);
|
||||
OS_Error err = mFile.Read(buffer, sizeof(buffer), &length);
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
if (length < 4) // tag header size
|
||||
return false;
|
||||
|
||||
if (strncmp(buffer, "ID3", 3))
|
||||
return false;
|
||||
|
||||
if (buffer[3] != 2)
|
||||
return false;
|
||||
|
||||
// we have a valid v2.2 tag header
|
||||
char* ptr = buffer + 10;
|
||||
|
||||
// the total length of tags is encoded in this strange way to avoid being
|
||||
// interpreted as an MP3 "sync" flag (don't use the top bit of each byte).
|
||||
int totalTagLen = buffer[6]*2097152+buffer[7]*16384+buffer[8]*128+buffer[9];
|
||||
mStartByte = totalTagLen; // skip tags when streaming
|
||||
|
||||
// OK, I'm being lazy here, but if someone can't find a way to put the song
|
||||
// title and artist in the first 1K of header then they're just being plain mean.
|
||||
if (totalTagLen > 1024) totalTagLen = 1024;
|
||||
|
||||
while (ptr-buffer < totalTagLen)
|
||||
{
|
||||
if (*ptr == 0)
|
||||
break;
|
||||
|
||||
// next three bytes are length, so go two bytes, copy 4 and mask off one
|
||||
int fieldLen = ntohl(OS::GetUInt32FromMemory((UInt32*)(ptr+2))) & 0x00ffffff;
|
||||
|
||||
if (!strncmp(ptr, "TP1", 3)) // Artist
|
||||
{
|
||||
int len = fieldLen;
|
||||
if (len > 255) len = 255;
|
||||
if (ptr[6] == 0)
|
||||
{
|
||||
::memcpy(mArtist, ptr+7, len-1); // skip encoding byte
|
||||
mArtist[len-1] = 0;
|
||||
}
|
||||
else
|
||||
ConvertUTF16toASCII(ptr+7, len-1, mArtist, sizeof(mArtist));
|
||||
}
|
||||
|
||||
if (!strncmp(ptr, "TT2", 3)) // Title
|
||||
{
|
||||
int len = fieldLen;
|
||||
if (len > 255) len = 255;
|
||||
if (ptr[6] == 0)
|
||||
{
|
||||
::memcpy(mTitle, ptr+7, len-1); // skip encoding byte
|
||||
mTitle[len-1] = 0;
|
||||
}
|
||||
else
|
||||
ConvertUTF16toASCII(ptr+7, len-1, mTitle, sizeof(mTitle));
|
||||
}
|
||||
|
||||
if (!strncmp(ptr, "TAL", 3)) // Album
|
||||
{
|
||||
int len = fieldLen;
|
||||
if (len > 255) len = 255;
|
||||
if (ptr[6] == 0)
|
||||
{
|
||||
::memcpy(mAlbum, ptr+7, len-1); // skip encoding byte
|
||||
mAlbum[len-1] = 0;
|
||||
}
|
||||
else
|
||||
ConvertUTF16toASCII(ptr+7, len-1, mAlbum, sizeof(mAlbum));
|
||||
}
|
||||
|
||||
if ((strlen(mTitle) > 0) && (strlen(mArtist) > 0) && (strlen(mAlbum) > 0))
|
||||
break; // we found the tags we need
|
||||
|
||||
ptr += fieldLen + 6; // skip field and header
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MP3FileBroadcaster::ReadV2_3Tags()
|
||||
{
|
||||
char buffer[1024];
|
||||
UInt32 length;
|
||||
|
||||
mFile.Seek(0);
|
||||
OS_Error err = mFile.Read(buffer, sizeof(buffer), &length);
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
if (strncmp(buffer, "ID3", 3))
|
||||
return false;
|
||||
|
||||
if (buffer[3] != 3)
|
||||
return false;
|
||||
|
||||
// we have a valid v2.3 tag header
|
||||
char* ptr = buffer + 10;
|
||||
|
||||
// skip extended header if it exists
|
||||
if ((buffer[4] & 0x40) != 0)
|
||||
ptr += 10;
|
||||
|
||||
// if any other flags are set (like "unsychronization"), bail.
|
||||
// these can be supported in the future.
|
||||
if (((buffer[4] & 0xbf) != 0) || (buffer[5] != 0))
|
||||
return false;
|
||||
|
||||
// the total length of tags is encoded in this strange way to avoid being
|
||||
// interpreted as an MP3 "sync" flag (don't use the top bit of each byte).
|
||||
int totalTagLen = buffer[6]*2097152+buffer[7]*16384+buffer[8]*128+buffer[9];
|
||||
mStartByte = totalTagLen; // skip tags when streaming
|
||||
|
||||
// OK, I'm being lazy here, but if someone can't find a way to put the song
|
||||
// title and artist in the first 1K of header then they're just being plain mean.
|
||||
if (totalTagLen > 1024) totalTagLen = 1024;
|
||||
|
||||
while (ptr-buffer < totalTagLen)
|
||||
{
|
||||
if (*ptr == 0)
|
||||
break;
|
||||
|
||||
int fieldLen = ntohl(OS::GetUInt32FromMemory((UInt32*)(ptr+4)));
|
||||
|
||||
// should check compression and encryption flags for these fields, but I
|
||||
// wouldn't really expect them to be set for title or artist
|
||||
|
||||
if (!::strncmp(ptr, "TPE1", 4)) // Artist
|
||||
{
|
||||
int len = fieldLen;
|
||||
if (len > 255) len = 255;
|
||||
if (ptr[10] == 0)
|
||||
{
|
||||
::memcpy(mArtist, ptr+11, len-1); // skip encoding byte
|
||||
mArtist[len-1] = 0;
|
||||
}
|
||||
else
|
||||
ConvertUTF16toASCII(ptr+11, len-1, mArtist, sizeof(mArtist));
|
||||
}
|
||||
|
||||
if (!strncmp(ptr, "TIT2", 4)) // Title
|
||||
{
|
||||
int len = fieldLen;
|
||||
if (len > 255) len = 255;
|
||||
if (ptr[10] == 0)
|
||||
{
|
||||
::memcpy(mTitle, ptr+11, len-1); // skip encoding byte
|
||||
mTitle[len-1] = 0;
|
||||
}
|
||||
else
|
||||
ConvertUTF16toASCII(ptr+11, len-1, mTitle, sizeof(mTitle));
|
||||
}
|
||||
|
||||
if (!strncmp(ptr, "TALB", 4)) // Album
|
||||
{
|
||||
int len = fieldLen;
|
||||
if (len > 255) len = 255;
|
||||
if (ptr[10] == 0)
|
||||
{
|
||||
::memcpy(mAlbum, ptr+11, len-1); // skip encoding byte
|
||||
mAlbum[len-1] = 0;
|
||||
}
|
||||
else
|
||||
ConvertUTF16toASCII(ptr+11, len-1, mAlbum, sizeof(mAlbum));
|
||||
}
|
||||
|
||||
if ((::strlen(mTitle) > 0) && (::strlen(mArtist) > 0) && (::strlen(mAlbum) > 0))
|
||||
break; // we found the tags we need
|
||||
|
||||
ptr += fieldLen + 10; // skip field and header
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MP3FileBroadcaster::CheckHeaders(unsigned char * buffer)
|
||||
{
|
||||
int bitRate, bitRate2;
|
||||
int frequency, frequency2;
|
||||
int recordSize;
|
||||
|
||||
if (!ParseHeader(buffer, &bitRate, &frequency, &recordSize))
|
||||
return false;
|
||||
|
||||
if (!ParseHeader(buffer + recordSize, &bitRate2, &frequency2, &recordSize))
|
||||
return false;
|
||||
|
||||
if (frequency != frequency2)
|
||||
return false;
|
||||
|
||||
mBitRate = bitRate;
|
||||
mFrequency = frequency;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MP3FileBroadcaster::ParseHeader(unsigned char* buffer, int* bitRate, int* frequency, int* recordSize)
|
||||
{
|
||||
if ((buffer[0] != 0xff) || ((buffer[1] & 0xe6) != 0xe2))
|
||||
return false; // not a valid MP3 header (not valid or not layer 3)
|
||||
|
||||
int version = (buffer[1] & 0x18) >> 3;
|
||||
|
||||
mIsMPEG2 = (version != 3);
|
||||
|
||||
if (mIsMPEG2)
|
||||
*bitRate = gBitRateArrayv2[buffer[2] >> 4]; // MPEG2
|
||||
else
|
||||
*bitRate = gBitRateArray[buffer[2] >> 4]; // MPEG 1
|
||||
|
||||
if (version == 3)
|
||||
*frequency = gFrequencyArray[(buffer[2] & 0x0a) >> 2];
|
||||
else if (version == 2)
|
||||
*frequency = gFrequencyArrayv2[(buffer[2] & 0x0a) >> 2];
|
||||
else
|
||||
*frequency = gFrequencyArrayv2_5[(buffer[2] & 0x0a) >> 2];
|
||||
|
||||
if ((*bitRate == 0) || (*frequency == 0))
|
||||
return false;
|
||||
|
||||
int pad = (buffer[2] & 0x02) >> 1;
|
||||
|
||||
*recordSize = 144000 * *bitRate / *frequency; // standard MP3 calculation
|
||||
|
||||
// MPEG 2 (and 2.5) frames encode half the number of samples
|
||||
if (mIsMPEG2)
|
||||
*recordSize /= 2;
|
||||
|
||||
*recordSize += pad;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int MP3FileBroadcaster::CountFrames(unsigned char* buffer, UInt32 length, int* leftOver)
|
||||
{
|
||||
int bitRate;
|
||||
int frequency;
|
||||
int recordSize;
|
||||
|
||||
UInt32 offset = 0;
|
||||
int numFrames = 0;
|
||||
while ( offset < length)
|
||||
{
|
||||
if (length - offset < 4)
|
||||
break; // we don't have a whole header left, so move on
|
||||
|
||||
if (!ParseHeader(buffer + offset, &bitRate, &frequency, &recordSize))
|
||||
{
|
||||
// Oops, we lost our stream, so advance byte by byte looking for a frame header
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ((UInt32) recordSize + offset) > length)
|
||||
break; // we don't have the whole frame in this buffer.
|
||||
|
||||
numFrames++;
|
||||
|
||||
offset += recordSize;
|
||||
}
|
||||
|
||||
// usually there will be a partial frame left over. We leave this for next time.
|
||||
*leftOver = length - offset;
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
// We really should be converting from Unicode to Latin-1, but the conversion of the high byte characters isn't
|
||||
// easy. If I find some code or tables to do this I can include it later.
|
||||
bool MP3FileBroadcaster::ConvertUTF16toASCII(char* sourceStr,int sourceSize, char* dest, int destSize)
|
||||
{
|
||||
unsigned short *sourceStart = (unsigned short *)sourceStr;
|
||||
unsigned short *sourceEnd = (unsigned short *)(sourceStart + sourceSize);
|
||||
unsigned char *targetStart = (unsigned char *)dest;
|
||||
unsigned char *targetEnd = targetStart + destSize;
|
||||
|
||||
bool result = true;
|
||||
unsigned short *source = sourceStart;
|
||||
unsigned char *target = targetStart;
|
||||
unsigned short ch;
|
||||
bool doSwap = false;
|
||||
ch = *source++;
|
||||
Assert((ch == 0xfffe) || (ch == 0xfeff));
|
||||
if (ch != 0xfeff)
|
||||
doSwap = true;
|
||||
|
||||
while ((source < sourceEnd) && (target <= targetEnd))
|
||||
{
|
||||
ch = *source++;
|
||||
if (doSwap)
|
||||
{
|
||||
unsigned short low = (ch & 0xff) << 8;
|
||||
ch = (ch >> 8) | low;
|
||||
}
|
||||
|
||||
if (ch < 0x80)
|
||||
{
|
||||
*target = (UInt8) ch;
|
||||
target++;
|
||||
}
|
||||
}
|
||||
|
||||
if (target <= targetEnd)
|
||||
*target = 0;
|
||||
else
|
||||
result = false;
|
||||
|
||||
return result;
|
||||
}
|
94
MP3Broadcaster/MP3FileBroadcaster.h
Executable file
94
MP3Broadcaster/MP3FileBroadcaster.h
Executable file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MP3FileBroadcaster_H__
|
||||
#define __MP3FileBroadcaster_H__
|
||||
|
||||
#include "TCPSocket.h"
|
||||
#include "MP3MetaInfoUpdater.h"
|
||||
#include "OSFileSource.h"
|
||||
|
||||
class MP3FileBroadcaster
|
||||
{
|
||||
public:
|
||||
MP3FileBroadcaster(TCPSocket* socket, int bitrate, int frequency, int bufferSize = 7000);
|
||||
~MP3FileBroadcaster();
|
||||
|
||||
void SetInfoUpdater(MP3MetaInfoUpdater* updater) { mUpdater = updater; }
|
||||
|
||||
int PlaySong(char *fileName, char *currentFile, bool preflight = false, bool fastpreflight = false);
|
||||
|
||||
enum
|
||||
{
|
||||
kBadFileFormat = 1,
|
||||
kWrongFrequency = 2,
|
||||
kWrongBitRate = 3,
|
||||
kConnectionError = 4,
|
||||
kCouldntOpenFile = 5
|
||||
};
|
||||
|
||||
char* GetTitle() { return mTitle; }
|
||||
char* GetArtist() { return mArtist; }
|
||||
char* GetAlbum() { return mAlbum; }
|
||||
char* GetSong() { return mSong; }
|
||||
|
||||
private:
|
||||
void CheckForTags();
|
||||
bool ReadV1Tags();
|
||||
bool ReadV2_2Tags();
|
||||
bool ReadV2_3Tags();
|
||||
|
||||
void UpdateMetaInfo();
|
||||
|
||||
bool CheckHeaders(unsigned char * buffer);
|
||||
bool ParseHeader(unsigned char* buffer, int* bitRate, int* frequency, int* recordSize);
|
||||
int CountFrames(unsigned char* buffer, UInt32 length, int* leftOver);
|
||||
|
||||
bool ConvertUTF16toASCII(char* sourceStr,int sourceSize, char* dest, int destSize);
|
||||
|
||||
TCPSocket* mSocket;
|
||||
int mBitRate;
|
||||
int mFrequency;
|
||||
bool mIsMPEG2;
|
||||
int mBufferSize;
|
||||
int mDelay;
|
||||
UInt64 mNumFramesSent;
|
||||
UInt64 mBroadcastStartTime;
|
||||
unsigned char* mBuffer;
|
||||
MP3MetaInfoUpdater* mUpdater;
|
||||
|
||||
int mDesiredBitRate;
|
||||
int mDesiredFrequency;
|
||||
|
||||
OSFileSource mFile;
|
||||
int mStartByte;
|
||||
|
||||
char mTitle[256];
|
||||
char mArtist[256];
|
||||
char mAlbum[256];
|
||||
char mSong[780];
|
||||
};
|
||||
|
||||
#endif
|
95
MP3Broadcaster/MP3MetaInfoUpdater.cpp
Normal file
95
MP3Broadcaster/MP3MetaInfoUpdater.cpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MP3MetaInfoUpdater.h"
|
||||
#include "StringTranslator.h"
|
||||
|
||||
MP3MetaInfoUpdater::MP3MetaInfoUpdater(char* password, char* mountPoint, UInt32 addr, UInt16 port)
|
||||
: mPassword(NULL),
|
||||
mMountPoint(NULL),
|
||||
mSocket(NULL, 0),
|
||||
mAddr(addr),
|
||||
mPort(port),
|
||||
mFirstTime(true)
|
||||
{
|
||||
mPassword = new char[strlen(password) + 1];
|
||||
strcpy(mPassword, password);
|
||||
mMountPoint = new char[strlen(mountPoint) + 1];
|
||||
strcpy(mMountPoint, mountPoint);
|
||||
}
|
||||
|
||||
MP3MetaInfoUpdater::~MP3MetaInfoUpdater()
|
||||
{
|
||||
delete [] mPassword;
|
||||
delete [] mMountPoint;
|
||||
SendStopRequest();
|
||||
mCond.Signal();
|
||||
}
|
||||
|
||||
void MP3MetaInfoUpdater::Entry()
|
||||
{
|
||||
while(!IsStopRequested())
|
||||
{
|
||||
mMutex.Lock();
|
||||
mCond.Wait(&mMutex);
|
||||
mMutex.Unlock();
|
||||
if (!IsStopRequested())
|
||||
{
|
||||
if (mFirstTime)
|
||||
{
|
||||
Sleep(3000); // give the stream a chance to get established (icecast isn't happy otherwise)
|
||||
mFirstTime = false;
|
||||
}
|
||||
DoUpdateMetaInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MP3MetaInfoUpdater::RequestMetaInfoUpdate(char* song)
|
||||
{
|
||||
char temp[600];
|
||||
strcpy(temp, song);
|
||||
StringTranslator::EncodeURL(temp, strlen(temp) + 1, mSong, sizeof(mSong));
|
||||
mCond.Signal();
|
||||
}
|
||||
|
||||
void MP3MetaInfoUpdater::DoUpdateMetaInfo()
|
||||
{
|
||||
mSocket.Open();
|
||||
int err = mSocket.Connect(mAddr, mPort);
|
||||
|
||||
if (!err)
|
||||
{
|
||||
UInt32 len;
|
||||
char* buffer = new char[100 + strlen(mSong) + strlen(mPassword) + strlen(mMountPoint)];
|
||||
qtss_sprintf(buffer, "GET /admin.cgi?mode=updinfo&pass=%s&mount=%s&song=%s HTTP/1.0\r\nUser-Agent: Darwin MP3Broadcaster\r\n\r\n",
|
||||
mPassword, mMountPoint, mSong);
|
||||
|
||||
mSocket.Send(buffer, strlen(buffer), &len);
|
||||
delete [] buffer;
|
||||
}
|
||||
|
||||
mSocket.Cleanup();
|
||||
}
|
57
MP3Broadcaster/MP3MetaInfoUpdater.h
Normal file
57
MP3Broadcaster/MP3MetaInfoUpdater.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MP3MetaInfoUpdater_H__
|
||||
#define __MP3MetaInfoUpdater_H__
|
||||
|
||||
#include "OSThread.h"
|
||||
#include "OSCond.h"
|
||||
#include "TCPSocket.h"
|
||||
|
||||
class MP3MetaInfoUpdater : public OSThread
|
||||
{
|
||||
public:
|
||||
MP3MetaInfoUpdater(char* password, char* mountPoint, UInt32 addr, UInt16 port);
|
||||
~MP3MetaInfoUpdater();
|
||||
|
||||
void Entry();
|
||||
|
||||
void RequestMetaInfoUpdate(char* song);
|
||||
|
||||
private:
|
||||
void DoUpdateMetaInfo();
|
||||
|
||||
OSCond mCond;
|
||||
OSMutex mMutex;
|
||||
char mSong[600];
|
||||
char* mPassword;
|
||||
char* mMountPoint;
|
||||
TCPSocket mSocket;
|
||||
UInt32 mAddr;
|
||||
UInt16 mPort;
|
||||
bool mFirstTime;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue