/liveMedia/MP3StreamState.cpp
C++ | 457 lines | 340 code | 64 blank | 53 comment | 96 complexity | 9da717a549724f6a73c961b11f9609f9 MD5 | raw file
Possible License(s): LGPL-2.1
- /**********
- This library is free software; you can redistribute it and/or modify it under
- the terms of the GNU Lesser General Public License as published by the
- Free Software Foundation; either version 2.1 of the License, or (at your
- option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
- This library 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 Lesser General Public License for
- more details.
- You should have received a copy of the GNU Lesser General Public License
- along with this library; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- **********/
- // "liveMedia"
- // Copyright (c) 1996-2011 Live Networks, Inc. All rights reserved.
- // A class encapsulating the state of a MP3 stream
- // Implementation
- #include "MP3StreamState.hh"
- #include "InputFile.hh"
- #include "GroupsockHelper.hh"
- #if defined(__WIN32__) || defined(_WIN32)
- #define snprintf _snprintf
- #endif
- #define MILLION 1000000
- MP3StreamState::MP3StreamState(UsageEnvironment& env)
- : fEnv(env), fFid(NULL), fPresentationTimeScale(1) {
- }
- MP3StreamState::~MP3StreamState() {
- // Close our open file or socket:
- if (fFid != NULL && fFid != stdin) {
- if (fFidIsReallyASocket) {
- long fid_long = (long)fFid;
- closeSocket((int)fid_long);
- } else {
- CloseInputFile(fFid);
- }
- }
- }
- void MP3StreamState::assignStream(FILE* fid, unsigned fileSize) {
- fFid = fid;
- if (fileSize == (unsigned)(-1)) { /*HACK#####*/
- fFidIsReallyASocket = 1;
- fFileSize = 0;
- } else {
- fFidIsReallyASocket = 0;
- fFileSize = fileSize;
- }
- fNumFramesInFile = 0; // until we know otherwise
- fIsVBR = fHasXingTOC = False; // ditto
- // Set the first frame's 'presentation time' to the current wall time:
- gettimeofday(&fNextFramePresentationTime, NULL);
- }
- struct timeval MP3StreamState::currentFramePlayTime() const {
- unsigned const numSamples = 1152;
- unsigned const freq = fr().samplingFreq*(1 + fr().isMPEG2);
- // result is numSamples/freq
- unsigned const uSeconds
- = ((numSamples*2*MILLION)/freq + 1)/2; // rounds to nearest integer
- struct timeval result;
- result.tv_sec = uSeconds/MILLION;
- result.tv_usec = uSeconds%MILLION;
- return result;
- }
- float MP3StreamState::filePlayTime() const {
- unsigned numFramesInFile = fNumFramesInFile;
- if (numFramesInFile == 0) {
- // Estimate the number of frames from the file size, and the
- // size of the current frame:
- numFramesInFile = fFileSize/(4 + fCurrentFrame.frameSize);
- }
- struct timeval const pt = currentFramePlayTime();
- return numFramesInFile*(pt.tv_sec + pt.tv_usec/(float)MILLION);
- }
- unsigned MP3StreamState::getByteNumberFromPositionFraction(float fraction) {
- if (fHasXingTOC) {
- // The file is VBR, with a Xing TOC; use it to determine which byte to seek to:
- float percent = fraction*100.0f;
- unsigned a = (unsigned)percent;
- if (a > 99) a = 99;
- unsigned fa = fXingTOC[a];
- unsigned fb;
- if (a < 99) {
- fb = fXingTOC[a+1];
- } else {
- fb = 256;
- }
- fraction = (fa + (fb-fa)*(percent-a))/256.0f;
- }
- return (unsigned)(fraction*fFileSize);
- }
- void MP3StreamState::seekWithinFile(unsigned seekByteNumber) {
- if (fFidIsReallyASocket) return; // it's not seekable
- SeekFile64(fFid, seekByteNumber, SEEK_SET);
- }
- unsigned MP3StreamState::findNextHeader(struct timeval& presentationTime) {
- presentationTime = fNextFramePresentationTime;
- if (!findNextFrame()) return 0;
- // From this frame, figure out the *next* frame's presentation time:
- struct timeval framePlayTime = currentFramePlayTime();
- if (fPresentationTimeScale > 1) {
- // Scale this value
- unsigned secondsRem = framePlayTime.tv_sec % fPresentationTimeScale;
- framePlayTime.tv_sec -= secondsRem;
- framePlayTime.tv_usec += secondsRem*MILLION;
- framePlayTime.tv_sec /= fPresentationTimeScale;
- framePlayTime.tv_usec /= fPresentationTimeScale;
- }
- fNextFramePresentationTime.tv_usec += framePlayTime.tv_usec;
- fNextFramePresentationTime.tv_sec
- += framePlayTime.tv_sec + fNextFramePresentationTime.tv_usec/MILLION;
- fNextFramePresentationTime.tv_usec %= MILLION;
- return fr().hdr;
- }
- Boolean MP3StreamState::readFrame(unsigned char* outBuf, unsigned outBufSize,
- unsigned& resultFrameSize,
- unsigned& resultDurationInMicroseconds) {
- /* We assume that "mp3FindNextHeader()" has already been called */
- resultFrameSize = 4 + fr().frameSize;
- if (outBufSize < resultFrameSize) {
- #ifdef DEBUG_ERRORS
- fprintf(stderr, "Insufficient buffer size for reading input frame (%d, need %d)\n",
- outBufSize, resultFrameSize);
- #endif
- if (outBufSize < 4) outBufSize = 0;
- resultFrameSize = outBufSize;
- return False;
- }
- if (resultFrameSize >= 4) {
- unsigned& hdr = fr().hdr;
- *outBuf++ = (unsigned char)(hdr>>24);
- *outBuf++ = (unsigned char)(hdr>>16);
- *outBuf++ = (unsigned char)(hdr>>8);
- *outBuf++ = (unsigned char)(hdr);
- memmove(outBuf, fr().frameBytes, resultFrameSize-4);
- }
- struct timeval const pt = currentFramePlayTime();
- resultDurationInMicroseconds = pt.tv_sec*MILLION + pt.tv_usec;
- return True;
- }
- void MP3StreamState::getAttributes(char* buffer, unsigned bufferSize) const {
- char const* formatStr
- = "bandwidth %d MPEGnumber %d MPEGlayer %d samplingFrequency %d isStereo %d playTime %d isVBR %d";
- unsigned fpt = (unsigned)(filePlayTime() + 0.5); // rounds to nearest integer
- #if defined(IRIX) || defined(ALPHA) || defined(_QNX4) || defined(IMN_PIM) || defined(CRIS)
- /* snprintf() isn't defined, so just use sprintf() - ugh! */
- sprintf(buffer, formatStr,
- fr().bitrate, fr().isMPEG2 ? 2 : 1, fr().layer, fr().samplingFreq, fr().isStereo,
- fpt, fIsVBR);
- #else
- snprintf(buffer, bufferSize, formatStr,
- fr().bitrate, fr().isMPEG2 ? 2 : 1, fr().layer, fr().samplingFreq, fr().isStereo,
- fpt, fIsVBR);
- #endif
- }
- void MP3StreamState::writeGetCmd(char const* hostName,
- unsigned short portNum,
- char const* fileName) {
- char const* const getCmdFmt = "GET %s HTTP/1.1\r\nHost: %s:%d\r\n\r\n";
- if (fFidIsReallyASocket) {
- long fid_long = (long)fFid;
- int sock = (int)fid_long;
- char writeBuf[100];
- #if defined(IRIX) || defined(ALPHA) || defined(_QNX4) || defined(IMN_PIM) || defined(CRIS)
- /* snprintf() isn't defined, so just use sprintf() */
- /* This is a security risk if filename can come from an external user */
- sprintf(writeBuf, getCmdFmt, fileName, hostName, portNum);
- #else
- snprintf(writeBuf, sizeof writeBuf, getCmdFmt,
- fileName, hostName, portNum);
- #endif
- send(sock, writeBuf, strlen(writeBuf), 0);
- } else {
- fprintf(fFid, getCmdFmt, fileName, hostName, portNum);
- fflush(fFid);
- }
- }
- // This is crufty old code that needs to be cleaned up #####
- #define HDRCMPMASK 0xfffffd00
- Boolean MP3StreamState::findNextFrame() {
- unsigned char hbuf[8];
- unsigned l; int i;
- int attempt = 0;
- read_again:
- if (readFromStream(hbuf, 4) != 4) return False;
- fr().hdr = ((unsigned long) hbuf[0] << 24)
- | ((unsigned long) hbuf[1] << 16)
- | ((unsigned long) hbuf[2] << 8)
- | (unsigned long) hbuf[3];
- #ifdef DEBUG_PARSE
- fprintf(stderr, "fr().hdr: 0x%08x\n", fr().hdr);
- #endif
- if (fr().oldHdr != fr().hdr || !fr().oldHdr) {
- i = 0;
- init_resync:
- #ifdef DEBUG_PARSE
- fprintf(stderr, "init_resync: fr().hdr: 0x%08x\n", fr().hdr);
- #endif
- if ( (fr().hdr & 0xffe00000) != 0xffe00000
- || (fr().hdr & 0x00060000) == 0 // undefined 'layer' field
- || (fr().hdr & 0x0000F000) == 0 // 'free format' bitrate index
- || (fr().hdr & 0x0000F000) == 0x0000F000 // undefined bitrate index
- || (fr().hdr & 0x00000C00) == 0x00000C00 // undefined frequency index
- || (fr().hdr & 0x00000003) != 0x00000000 // 'emphasis' field unexpectedly set
- ) {
- /* RSF: Do the following test even if we're not at the
- start of the file, in case we have two or more
- separate MP3 files cat'ed together:
- */
- /* Check for RIFF hdr */
- if (fr().hdr == ('R'<<24)+('I'<<16)+('F'<<8)+'F') {
- unsigned char buf[70 /*was: 40*/];
- #ifdef DEBUG_ERRORS
- fprintf(stderr,"Skipped RIFF header\n");
- #endif
- readFromStream(buf, 66); /* already read 4 */
- goto read_again;
- }
- /* Check for ID3 hdr */
- if ((fr().hdr&0xFFFFFF00) == ('I'<<24)+('D'<<16)+('3'<<8)) {
- unsigned tagSize, bytesToSkip;
- unsigned char buf[1000];
- readFromStream(buf, 6); /* already read 4 */
- tagSize = ((buf[2]&0x7F)<<21) + ((buf[3]&0x7F)<<14) + ((buf[4]&0x7F)<<7) + (buf[5]&0x7F);
- bytesToSkip = tagSize;
- while (bytesToSkip > 0) {
- unsigned bytesToRead = sizeof buf;
- if (bytesToRead > bytesToSkip) {
- bytesToRead = bytesToSkip;
- }
- readFromStream(buf, bytesToRead);
- bytesToSkip -= bytesToRead;
- }
- #ifdef DEBUG_ERRORS
- fprintf(stderr,"Skipped %d-byte ID3 header\n", tagSize);
- #endif
- goto read_again;
- }
- /* give up after 20,000 bytes */
- if (i++ < 20000/*4096*//*1024*/) {
- memmove (&hbuf[0], &hbuf[1], 3);
- if (readFromStream(hbuf+3,1) != 1) {
- return False;
- }
- fr().hdr <<= 8;
- fr().hdr |= hbuf[3];
- fr().hdr &= 0xffffffff;
- #ifdef DEBUG_PARSE
- fprintf(stderr, "calling init_resync %d\n", i);
- #endif
- goto init_resync;
- }
- #ifdef DEBUG_ERRORS
- fprintf(stderr,"Giving up searching valid MPEG header\n");
- #endif
- return False;
- #ifdef DEBUG_ERRORS
- fprintf(stderr,"Illegal Audio-MPEG-Header 0x%08lx at offset 0x%lx.\n",
- fr().hdr,tell_stream(str)-4);
- #endif
- /* Read more bytes until we find something that looks
- reasonably like a valid header. This is not a
- perfect strategy, but it should get us back on the
- track within a short time (and hopefully without
- too much distortion in the audio output). */
- do {
- attempt++;
- memmove (&hbuf[0], &hbuf[1], 7);
- if (readFromStream(&hbuf[3],1) != 1) {
- return False;
- }
- /* This is faster than combining fr().hdr from scratch */
- fr().hdr = ((fr().hdr << 8) | hbuf[3]) & 0xffffffff;
- if (!fr().oldHdr)
- goto init_resync; /* "considered harmful", eh? */
- } while ((fr().hdr & HDRCMPMASK) != (fr().oldHdr & HDRCMPMASK)
- && (fr().hdr & HDRCMPMASK) != (fr().firstHdr & HDRCMPMASK));
- #ifdef DEBUG_ERRORS
- fprintf (stderr, "Skipped %d bytes in input.\n", attempt);
- #endif
- }
- if (!fr().firstHdr) {
- fr().firstHdr = fr().hdr;
- }
- fr().setParamsFromHeader();
- fr().setBytePointer(fr().frameBytes, fr().frameSize);
- fr().oldHdr = fr().hdr;
- if (fr().isFreeFormat) {
- #ifdef DEBUG_ERRORS
- fprintf(stderr,"Free format not supported.\n");
- #endif
- return False;
- }
- #ifdef MP3_ONLY
- if (fr().layer != 3) {
- #ifdef DEBUG_ERRORS
- fprintf(stderr, "MPEG layer %d is not supported!\n", fr().layer);
- #endif
- return False;
- }
- #endif
- }
- if ((l = readFromStream(fr().frameBytes, fr().frameSize))
- != fr().frameSize) {
- if (l == 0) return False;
- memset(fr().frameBytes+1, 0, fr().frameSize-1);
- }
- return True;
- }
- static Boolean socketIsReadable(int socket) {
- const unsigned numFds = socket+1;
- fd_set rd_set;
- FD_ZERO(&rd_set);
- FD_SET((unsigned)socket, &rd_set);
- struct timeval timeout;
- timeout.tv_sec = timeout.tv_usec = 0;
- int result = select(numFds, &rd_set, NULL, NULL, &timeout);
- return result != 0; // not > 0, because windows can return -1 for file sockets
- }
- static char watchVariable;
- static void checkFunc(void* /*clientData*/) {
- watchVariable = ~0;
- }
- static void waitUntilSocketIsReadable(UsageEnvironment& env, int socket) {
- while (!socketIsReadable(socket)) {
- // Delay a short period of time before checking again.
- unsigned usecsToDelay = 1000; // 1 ms
- env.taskScheduler().scheduleDelayedTask(usecsToDelay,
- (TaskFunc*)checkFunc, (void*)NULL);
- watchVariable = 0;
- env.taskScheduler().doEventLoop(&watchVariable);
- // This allows other tasks to run while we're waiting:
- }
- }
- unsigned MP3StreamState::readFromStream(unsigned char* buf,
- unsigned numChars) {
- // Hack for doing socket I/O instead of file I/O (e.g., on Windows)
- if (fFidIsReallyASocket) {
- long fid_long = (long)fFid;
- int sock = (int)fid_long;
- unsigned totBytesRead = 0;
- do {
- waitUntilSocketIsReadable(fEnv, sock);
- int bytesRead
- = recv(sock, &((char*)buf)[totBytesRead], numChars-totBytesRead, 0);
- if (bytesRead < 0) return 0;
- totBytesRead += (unsigned)bytesRead;
- } while (totBytesRead < numChars);
- return totBytesRead;
- } else {
- waitUntilSocketIsReadable(fEnv, (int)fileno(fFid));
- return fread(buf, 1, numChars, fFid);
- }
- }
- #define XING_FRAMES_FLAG 0x0001
- #define XING_BYTES_FLAG 0x0002
- #define XING_TOC_FLAG 0x0004
- #define XING_VBR_SCALE_FLAG 0x0008
- void MP3StreamState::checkForXingHeader() {
- // Look for 'Xing' in the first 4 bytes after the 'side info':
- if (fr().frameSize < fr().sideInfoSize) return;
- unsigned bytesAvailable = fr().frameSize - fr().sideInfoSize;
- unsigned char* p = &(fr().frameBytes[fr().sideInfoSize]);
- if (bytesAvailable < 8) return;
- if (p[0] != 'X' || p[1] != 'i' || p[2] != 'n' || p[3] != 'g') return;
- // We found it.
- fIsVBR = True;
- u_int32_t flags = (p[4]<<24) | (p[5]<<16) | (p[6]<<8) | p[7];
- unsigned i = 8;
- bytesAvailable -= 8;
- if (flags&XING_FRAMES_FLAG) {
- // The next 4 bytes are the number of frames:
- if (bytesAvailable < 4) return;
- fNumFramesInFile = (p[i]<<24)|(p[i+1]<<16)|(p[i+2]<<8)|(p[i+3]);
- i += 4; bytesAvailable -= 4;
- }
- if (flags&XING_BYTES_FLAG) {
- // The next 4 bytes is the file size:
- if (bytesAvailable < 4) return;
- fFileSize = (p[i]<<24)|(p[i+1]<<16)|(p[i+2]<<8)|(p[i+3]);
- i += 4; bytesAvailable -= 4;
- }
- if (flags&XING_TOC_FLAG) {
- // Fill in the Xing 'table of contents':
- if (bytesAvailable < XING_TOC_LENGTH) return;
- fHasXingTOC = True;
- for (unsigned j = 0; j < XING_TOC_LENGTH; ++j) {
- fXingTOC[j] = p[i+j];
- }
- i += XING_TOC_FLAG; bytesAvailable -= XING_TOC_FLAG;
- }
- }