/* * * @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 //#include #include #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; iRequestMetaInfoUpdate(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; }