liveMedia/QuickTimeFileSink.cpp

Go to the documentation of this file.
00001 /**********
00002 This library is free software; you can redistribute it and/or modify it under
00003 the terms of the GNU Lesser General Public License as published by the
00004 Free Software Foundation; either version 2.1 of the License, or (at your
00005 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
00006 
00007 This library is distributed in the hope that it will be useful, but WITHOUT
00008 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00009 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
00010 more details.
00011 
00012 You should have received a copy of the GNU Lesser General Public License
00013 along with this library; if not, write to the Free Software Foundation, Inc.,
00014 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2008 Live Networks, Inc.  All rights reserved.
00018 // A sink that generates a QuickTime file from a composite media session
00019 // Implementation
00020 
00021 #include "QuickTimeFileSink.hh"
00022 #include "QuickTimeGenericRTPSource.hh"
00023 #include "GroupsockHelper.hh"
00024 #include "OutputFile.hh"
00025 #include "H263plusVideoRTPSource.hh" // for the special header
00026 #include "MPEG4GenericRTPSource.hh" //for "samplingFrequencyFromAudioSpecificConfig()"
00027 #include "MPEG4LATMAudioRTPSource.hh" // for "parseGeneralConfigStr()"
00028 #include "Base64.hh"
00029 
00030 #include <ctype.h>
00031 
00032 #define fourChar(x,y,z,w) ( ((x)<<24)|((y)<<16)|((z)<<8)|(w) )
00033 
00035 // A structure used to represent the I/O state of each input 'subsession':
00036 
00037 class ChunkDescriptor {
00038 public:
00039   ChunkDescriptor(unsigned offsetInFile, unsigned size,
00040                   unsigned frameSize, unsigned frameDuration,
00041                   struct timeval presentationTime);
00042   virtual ~ChunkDescriptor();
00043 
00044   ChunkDescriptor* extendChunk(unsigned newOffsetInFile, unsigned newSize,
00045                                unsigned newFrameSize,
00046                                unsigned newFrameDuration,
00047                                struct timeval newPresentationTime);
00048       // this may end up allocating a new chunk instead
00049 public:
00050   ChunkDescriptor* fNextChunk;
00051   unsigned fOffsetInFile;
00052   unsigned fNumFrames;
00053   unsigned fFrameSize;
00054   unsigned fFrameDuration;
00055   struct timeval fPresentationTime; // of the start of the data
00056 };
00057 
00058 class SubsessionBuffer {
00059 public:
00060   SubsessionBuffer(unsigned bufferSize)
00061     : fBufferSize(bufferSize) {
00062     reset();
00063     fData = new unsigned char[bufferSize];
00064   }
00065   virtual ~SubsessionBuffer() { delete fData; }
00066   void reset() { fBytesInUse = 0; }
00067   void addBytes(unsigned numBytes) { fBytesInUse += numBytes; }
00068 
00069   unsigned char* dataStart() { return &fData[0]; }
00070   unsigned char* dataEnd() { return &fData[fBytesInUse]; }
00071   unsigned bytesInUse() const { return fBytesInUse; }
00072   unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; }
00073 
00074   void setPresentationTime(struct timeval const& presentationTime) {
00075     fPresentationTime = presentationTime;
00076   }
00077   struct timeval const& presentationTime() const {return fPresentationTime;}
00078 
00079 private:
00080   unsigned fBufferSize;
00081   struct timeval fPresentationTime;
00082   unsigned char* fData;
00083   unsigned fBytesInUse;
00084 };
00085 
00086 // A 64-bit counter, used below:
00087 
00088 class Count64 {
00089 public:
00090   Count64() { hi = lo = 0; }
00091 
00092   void operator+=(unsigned arg);
00093 
00094   unsigned hi, lo; // each 32 bits
00095 };
00096 
00097 class SubsessionIOState {
00098 public:
00099   SubsessionIOState(QuickTimeFileSink& sink, MediaSubsession& subsession);
00100   virtual ~SubsessionIOState();
00101 
00102   Boolean setQTstate();
00103   void setFinalQTstate();
00104 
00105   void afterGettingFrame(unsigned packetDataSize,
00106                          struct timeval presentationTime);
00107   void onSourceClosure();
00108 
00109   Boolean syncOK(struct timeval presentationTime);
00110       // returns true iff data is usable despite a sync check
00111 
00112   static void setHintTrack(SubsessionIOState* hintedTrack,
00113                            SubsessionIOState* hintTrack);
00114   Boolean isHintTrack() const { return fTrackHintedByUs != NULL; }
00115   Boolean hasHintTrack() const { return fHintTrackForUs != NULL; }
00116 
00117   UsageEnvironment& envir() const { return fOurSink.envir(); }
00118 
00119 public:
00120   static unsigned fCurrentTrackNumber;
00121   unsigned fTrackID;
00122   SubsessionIOState* fHintTrackForUs; SubsessionIOState* fTrackHintedByUs;
00123 
00124   SubsessionBuffer *fBuffer, *fPrevBuffer;
00125   QuickTimeFileSink& fOurSink;
00126   MediaSubsession& fOurSubsession;
00127 
00128   unsigned short fLastPacketRTPSeqNum;
00129   Boolean fOurSourceIsActive;
00130 
00131   Boolean fHaveBeenSynced; // used in synchronizing with other streams
00132   struct timeval fSyncTime;
00133 
00134   Boolean fQTEnableTrack;
00135   unsigned fQTcomponentSubtype;
00136   char const* fQTcomponentName;
00137   typedef unsigned (QuickTimeFileSink::*atomCreationFunc)();
00138   atomCreationFunc fQTMediaInformationAtomCreator;
00139   atomCreationFunc fQTMediaDataAtomCreator;
00140   char const* fQTAudioDataType;
00141   unsigned short fQTSoundSampleVersion;
00142   unsigned fQTTimeScale;
00143   unsigned fQTTimeUnitsPerSample;
00144   unsigned fQTBytesPerFrame;
00145   unsigned fQTSamplesPerFrame;
00146   // These next fields are derived from the ones above,
00147   // plus the information from each chunk:
00148   unsigned fQTTotNumSamples;
00149   unsigned fQTDurationM; // in media time units
00150   unsigned fQTDurationT; // in track time units
00151   unsigned fTKHD_durationPosn;
00152       // position of the duration in the output 'tkhd' atom
00153   unsigned fQTInitialOffsetDuration;
00154       // if there's a pause at the beginning
00155 
00156   ChunkDescriptor *fHeadChunk, *fTailChunk;
00157   unsigned fNumChunks;
00158 
00159   // Counters to be used in the hint track's 'udta'/'hinf' atom;
00160   struct hinf {
00161     Count64 trpy;
00162     Count64 nump;
00163     Count64 tpyl;
00164     // Is 'maxr' needed? Computing this would be a PITA. #####
00165     Count64 dmed;
00166     Count64 dimm;
00167     // 'drep' is always 0
00168     // 'tmin' and 'tmax' are always 0
00169     unsigned pmax;
00170     unsigned dmax;
00171   } fHINF;
00172 
00173 private:
00174   void useFrame(SubsessionBuffer& buffer);
00175   void useFrameForHinting(unsigned frameSize,
00176                           struct timeval presentationTime,
00177                           unsigned startSampleNumber);
00178 
00179   // used by the above two routines:
00180   unsigned useFrame1(unsigned sourceDataSize,
00181                      struct timeval presentationTime,
00182                      unsigned frameDuration, unsigned destFileOffset);
00183       // returns the number of samples in this data
00184 
00185 private:
00186   // A structure used for temporarily storing frame state:
00187   struct {
00188     unsigned frameSize;
00189     struct timeval presentationTime;
00190     unsigned destFileOffset; // used for non-hint tracks only
00191 
00192     // The remaining fields are used for hint tracks only:
00193     unsigned startSampleNumber;
00194     unsigned short seqNum;
00195     unsigned rtpHeader;
00196     unsigned char numSpecialHeaders; // used when our RTP source has special headers
00197     unsigned specialHeaderBytesLength; // ditto
00198     unsigned char specialHeaderBytes[SPECIAL_HEADER_BUFFER_SIZE]; // ditto
00199     unsigned packetSizes[256];
00200   } fPrevFrameState;
00201 };
00202 
00203 
00205 
00206 QuickTimeFileSink::QuickTimeFileSink(UsageEnvironment& env,
00207                                      MediaSession& inputSession,
00208                                      char const* outputFileName,
00209                                      unsigned bufferSize,
00210                                      unsigned short movieWidth,
00211                                      unsigned short movieHeight,
00212                                      unsigned movieFPS,
00213                                      Boolean packetLossCompensate,
00214                                      Boolean syncStreams,
00215                                      Boolean generateHintTracks,
00216                                      Boolean generateMP4Format)
00217   : Medium(env), fInputSession(inputSession),
00218     fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate),
00219     fSyncStreams(syncStreams), fGenerateMP4Format(generateMP4Format),
00220     fAreCurrentlyBeingPlayed(False),
00221     fLargestRTPtimestampFrequency(0),
00222     fNumSubsessions(0), fNumSyncedSubsessions(0),
00223     fHaveCompletedOutputFile(False),
00224     fMovieWidth(movieWidth), fMovieHeight(movieHeight),
00225     fMovieFPS(movieFPS), fMaxTrackDurationM(0) {
00226   fOutFid = OpenOutputFile(env, outputFileName);
00227   if (fOutFid == NULL) return;
00228 
00229   fNewestSyncTime.tv_sec = fNewestSyncTime.tv_usec = 0;
00230   fFirstDataTime.tv_sec = fFirstDataTime.tv_usec = (unsigned)(~0);
00231 
00232   // Set up I/O state for each input subsession:
00233   MediaSubsessionIterator iter(fInputSession);
00234   MediaSubsession* subsession;
00235   while ((subsession = iter.next()) != NULL) {
00236     // Ignore subsessions without a data source:
00237     FramedSource* subsessionSource = subsession->readSource();
00238     if (subsessionSource == NULL) continue;
00239 
00240     // If "subsession's" SDP description specified screen dimension
00241     // or frame rate parameters, then use these.  (Note that this must
00242     // be done before the call to "setQTState()" below.)
00243     if (subsession->videoWidth() != 0) {
00244       fMovieWidth = subsession->videoWidth();
00245     }
00246     if (subsession->videoHeight() != 0) {
00247       fMovieHeight = subsession->videoHeight();
00248     }
00249     if (subsession->videoFPS() != 0) {
00250       fMovieFPS = subsession->videoFPS();
00251     }
00252 
00253     SubsessionIOState* ioState
00254       = new SubsessionIOState(*this, *subsession);
00255     if (ioState == NULL || !ioState->setQTstate()) {
00256       // We're not able to output a QuickTime track for this subsession
00257       delete ioState; ioState = NULL;
00258       continue;
00259     }
00260     subsession->miscPtr = (void*)ioState;
00261 
00262     if (generateHintTracks) {
00263       // Also create a hint track for this track:
00264       SubsessionIOState* hintTrack
00265         = new SubsessionIOState(*this, *subsession);
00266       SubsessionIOState::setHintTrack(ioState, hintTrack);
00267       if (!hintTrack->setQTstate()) {
00268         delete hintTrack;
00269         SubsessionIOState::setHintTrack(ioState, NULL);
00270       }
00271     }
00272 
00273     // Also set a 'BYE' handler for this subsession's RTCP instance:
00274     if (subsession->rtcpInstance() != NULL) {
00275       subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState);
00276     }
00277 
00278     unsigned rtpTimestampFrequency = subsession->rtpTimestampFrequency();
00279     if (rtpTimestampFrequency > fLargestRTPtimestampFrequency) {
00280       fLargestRTPtimestampFrequency = rtpTimestampFrequency;
00281     }
00282 
00283     ++fNumSubsessions;
00284   }
00285 
00286   // Use the current time as the file's creation and modification
00287   // time.  Use Apple's time format: seconds since January 1, 1904
00288 
00289   gettimeofday(&fStartTime, NULL);
00290   fAppleCreationTime = fStartTime.tv_sec - 0x83dac000;
00291 
00292   // Begin by writing a "mdat" atom at the start of the file.
00293   // (Later, when we've finished copying data to the file, we'll come
00294   // back and fill in its size.)
00295   fMDATposition = ftell(fOutFid);
00296   addAtomHeader("mdat");
00297 }
00298 
00299 QuickTimeFileSink::~QuickTimeFileSink() {
00300   completeOutputFile();
00301 
00302   // Then, delete each active "SubsessionIOState":
00303   MediaSubsessionIterator iter(fInputSession);
00304   MediaSubsession* subsession;
00305   while ((subsession = iter.next()) != NULL) {
00306     SubsessionIOState* ioState
00307       = (SubsessionIOState*)(subsession->miscPtr);
00308     if (ioState == NULL) continue;
00309 
00310     delete ioState->fHintTrackForUs; // if any
00311     delete ioState;
00312   }
00313 
00314   // Finally, close our output file:
00315   CloseOutputFile(fOutFid);
00316 }
00317 
00318 QuickTimeFileSink*
00319 QuickTimeFileSink::createNew(UsageEnvironment& env,
00320                              MediaSession& inputSession,
00321                              char const* outputFileName,
00322                              unsigned bufferSize,
00323                              unsigned short movieWidth,
00324                              unsigned short movieHeight,
00325                              unsigned movieFPS,
00326                              Boolean packetLossCompensate,
00327                              Boolean syncStreams,
00328                              Boolean generateHintTracks,
00329                              Boolean generateMP4Format) {
00330   QuickTimeFileSink* newSink = 
00331     new QuickTimeFileSink(env, inputSession, outputFileName, bufferSize, movieWidth, movieHeight, movieFPS,
00332                           packetLossCompensate, syncStreams, generateHintTracks, generateMP4Format);
00333   if (newSink == NULL || newSink->fOutFid == NULL) {
00334     Medium::close(newSink);
00335     return NULL;
00336   }
00337 
00338   return newSink;
00339 }
00340 
00341 Boolean QuickTimeFileSink::startPlaying(afterPlayingFunc* afterFunc,
00342                                         void* afterClientData) {
00343   // Make sure we're not already being played:
00344   if (fAreCurrentlyBeingPlayed) {
00345     envir().setResultMsg("This sink has already been played");
00346     return False;
00347   }
00348 
00349   fAreCurrentlyBeingPlayed = True;
00350   fAfterFunc = afterFunc;
00351   fAfterClientData = afterClientData;
00352 
00353   return continuePlaying();
00354 }
00355 
00356 Boolean QuickTimeFileSink::continuePlaying() {
00357   // Run through each of our input session's 'subsessions',
00358   // asking for a frame from each one:
00359   Boolean haveActiveSubsessions = False;
00360   MediaSubsessionIterator iter(fInputSession);
00361   MediaSubsession* subsession;
00362   while ((subsession = iter.next()) != NULL) {
00363     FramedSource* subsessionSource = subsession->readSource();
00364     if (subsessionSource == NULL) continue;
00365 
00366     if (subsessionSource->isCurrentlyAwaitingData()) continue;
00367 
00368     SubsessionIOState* ioState
00369       = (SubsessionIOState*)(subsession->miscPtr);
00370     if (ioState == NULL) continue;
00371 
00372     haveActiveSubsessions = True;
00373     unsigned char* toPtr = ioState->fBuffer->dataEnd();
00374     unsigned toSize = ioState->fBuffer->bytesAvailable();
00375     subsessionSource->getNextFrame(toPtr, toSize,
00376                                    afterGettingFrame, ioState,
00377                                    onSourceClosure, ioState);
00378   }
00379   if (!haveActiveSubsessions) {
00380     envir().setResultMsg("No subsessions are currently active");
00381     return False;
00382   }
00383 
00384   return True;
00385 }
00386 
00387 void QuickTimeFileSink
00388 ::afterGettingFrame(void* clientData, unsigned packetDataSize,
00389                     unsigned /*numTruncatedBytes*/,
00390                     struct timeval presentationTime,
00391                     unsigned /*durationInMicroseconds*/) {
00392   SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00393   if (!ioState->syncOK(presentationTime)) {
00394     // Ignore this data:
00395     ioState->fOurSink.continuePlaying();
00396     return;
00397   }
00398   ioState->afterGettingFrame(packetDataSize, presentationTime);
00399 }
00400 
00401 void QuickTimeFileSink::onSourceClosure(void* clientData) {
00402   SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00403   ioState->onSourceClosure();
00404 }
00405 
00406 void QuickTimeFileSink::onSourceClosure1() {
00407   // Check whether *all* of the subsession sources have closed.
00408   // If not, do nothing for now:
00409   MediaSubsessionIterator iter(fInputSession);
00410   MediaSubsession* subsession;
00411   while ((subsession = iter.next()) != NULL) {
00412     SubsessionIOState* ioState
00413       = (SubsessionIOState*)(subsession->miscPtr);
00414     if (ioState == NULL) continue;
00415 
00416     if (ioState->fOurSourceIsActive) return; // this source hasn't closed
00417   }
00418 
00419   completeOutputFile();
00420 
00421   // Call our specified 'after' function:
00422   if (fAfterFunc != NULL) {
00423     (*fAfterFunc)(fAfterClientData);
00424   }
00425 }
00426 
00427 void QuickTimeFileSink::onRTCPBye(void* clientData) {
00428   SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00429 
00430   struct timeval timeNow;
00431   gettimeofday(&timeNow, NULL);
00432   unsigned secsDiff
00433     = timeNow.tv_sec - ioState->fOurSink.fStartTime.tv_sec;
00434 
00435   MediaSubsession& subsession = ioState->fOurSubsession;
00436   ioState->envir() << "Received RTCP \"BYE\" on \""
00437                    << subsession.mediumName()
00438                    << "/" << subsession.codecName()
00439                    << "\" subsession (after "
00440                    << secsDiff << " seconds)\n";
00441 
00442   // Handle the reception of a RTCP "BYE" as if the source had closed:
00443   ioState->onSourceClosure();
00444 }
00445 
00446 static Boolean timevalGE(struct timeval const& tv1,
00447                          struct timeval const& tv2) {
00448   return (unsigned)tv1.tv_sec > (unsigned)tv2.tv_sec
00449     || (tv1.tv_sec == tv2.tv_sec
00450         && (unsigned)tv1.tv_usec >= (unsigned)tv2.tv_usec);
00451 }
00452 
00453 void QuickTimeFileSink::completeOutputFile() {
00454   if (fHaveCompletedOutputFile || fOutFid == NULL) return;
00455 
00456   // Begin by filling in the initial "mdat" atom with the current
00457   // file size:
00458   unsigned curFileSize = ftell(fOutFid);
00459   setWord(fMDATposition, curFileSize);
00460 
00461   // Then, note the time of the first received data:
00462   MediaSubsessionIterator iter(fInputSession);
00463   MediaSubsession* subsession;
00464   while ((subsession = iter.next()) != NULL) {
00465     SubsessionIOState* ioState
00466       = (SubsessionIOState*)(subsession->miscPtr);
00467     if (ioState == NULL) continue;
00468 
00469     ChunkDescriptor* const headChunk = ioState->fHeadChunk;
00470     if (headChunk != NULL
00471         && timevalGE(fFirstDataTime, headChunk->fPresentationTime)) {
00472       fFirstDataTime = headChunk->fPresentationTime;
00473     }
00474   }
00475 
00476   // Then, update the QuickTime-specific state for each active track:
00477   iter.reset();
00478   while ((subsession = iter.next()) != NULL) {
00479     SubsessionIOState* ioState
00480       = (SubsessionIOState*)(subsession->miscPtr);
00481     if (ioState == NULL) continue;
00482 
00483     ioState->setFinalQTstate();
00484     // Do the same for a hint track (if any):
00485     if (ioState->hasHintTrack()) {
00486       ioState->fHintTrackForUs->setFinalQTstate();
00487     }
00488   }
00489 
00490   if (fGenerateMP4Format) {
00491     // Begin with a "ftyp" atom:
00492     addAtom_ftyp();
00493   }
00494 
00495   // Then, add a "moov" atom for the file metadata:
00496   addAtom_moov();
00497 
00498   // We're done:
00499   fHaveCompletedOutputFile = True;
00500 }
00501 
00502 
00504 
00505 unsigned SubsessionIOState::fCurrentTrackNumber = 0;
00506 
00507 SubsessionIOState::SubsessionIOState(QuickTimeFileSink& sink,
00508                                      MediaSubsession& subsession)
00509   : fHintTrackForUs(NULL), fTrackHintedByUs(NULL),
00510     fOurSink(sink), fOurSubsession(subsession),
00511     fLastPacketRTPSeqNum(0), fHaveBeenSynced(False), fQTTotNumSamples(0),
00512     fHeadChunk(NULL), fTailChunk(NULL), fNumChunks(0) {
00513   fTrackID = ++fCurrentTrackNumber;
00514 
00515   fBuffer = new SubsessionBuffer(fOurSink.fBufferSize);
00516   fPrevBuffer = sink.fPacketLossCompensate
00517     ? new SubsessionBuffer(fOurSink.fBufferSize) : NULL;
00518 
00519   FramedSource* subsessionSource = subsession.readSource();
00520   fOurSourceIsActive = subsessionSource != NULL;
00521 
00522   fPrevFrameState.presentationTime.tv_sec = 0;
00523   fPrevFrameState.presentationTime.tv_usec = 0;
00524   fPrevFrameState.seqNum = 0;
00525 }
00526 
00527 SubsessionIOState::~SubsessionIOState() {
00528   delete fBuffer; delete fPrevBuffer;
00529   delete fHeadChunk;
00530 }
00531 
00532 Boolean SubsessionIOState::setQTstate() {
00533   char const* noCodecWarning1 = "Warning: We don't implement a QuickTime ";
00534   char const* noCodecWarning2 = " Media Data Type for the \"";
00535   char const* noCodecWarning3 = "\" track, so we'll insert a dummy \"????\" Media Data Atom instead.  A separate, codec-specific editing pass will be needed before this track can be played.\n";
00536 
00537   do {
00538     fQTEnableTrack = True; // enable this track in the movie by default
00539     fQTTimeScale = fOurSubsession.rtpTimestampFrequency(); // by default
00540     fQTTimeUnitsPerSample = 1; // by default
00541     fQTBytesPerFrame = 0;
00542         // by default - indicates that the whole packet data is a frame
00543     fQTSamplesPerFrame = 1; // by default
00544 
00545     // Make sure our subsession's medium is one that we know how to
00546     // represent in a QuickTime file:
00547     if (isHintTrack()) {
00548       // Hint tracks are treated specially
00549       fQTEnableTrack = False; // hint tracks are marked as inactive
00550       fQTcomponentSubtype = fourChar('h','i','n','t');
00551       fQTcomponentName = "hint media handler";
00552       fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_gmhd;
00553       fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_rtp;
00554     } else if (strcmp(fOurSubsession.mediumName(), "audio") == 0) {
00555       fQTcomponentSubtype = fourChar('s','o','u','n');
00556       fQTcomponentName = "Apple Sound Media Handler";
00557       fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_smhd;
00558       fQTMediaDataAtomCreator
00559         = &QuickTimeFileSink::addAtom_soundMediaGeneral; // by default
00560       fQTSoundSampleVersion = 0; // by default
00561 
00562       // Make sure that our subsession's codec is one that we can handle:
00563       if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00564           strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00565         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00566       } else if (strcmp(fOurSubsession.codecName(), "PCMU") == 0) {
00567         fQTAudioDataType = "ulaw";
00568         fQTBytesPerFrame = 1;
00569       } else if (strcmp(fOurSubsession.codecName(), "GSM") == 0) {
00570         fQTAudioDataType = "agsm";
00571         fQTBytesPerFrame = 33;
00572         fQTSamplesPerFrame = 160;
00573       } else if (strcmp(fOurSubsession.codecName(), "PCMA") == 0) {
00574         fQTAudioDataType = "alaw";
00575         fQTBytesPerFrame = 1;
00576       } else if (strcmp(fOurSubsession.codecName(), "QCELP") == 0) {
00577         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_Qclp;
00578         fQTSamplesPerFrame = 160;
00579       } else if (strcmp(fOurSubsession.codecName(), "MPEG4-GENERIC") == 0 ||
00580                  strcmp(fOurSubsession.codecName(), "MP4A-LATM") == 0) {
00581         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4a;
00582         fQTTimeUnitsPerSample = 1024; // QT considers each frame to be a 'sample'
00583         // The time scale (frequency) comes from the 'config' information.
00584         // It might be different from the RTP timestamp frequency (e.g., aacPlus).
00585         unsigned frequencyFromConfig
00586           = samplingFrequencyFromAudioSpecificConfig(fOurSubsession.fmtp_config());
00587         if (frequencyFromConfig != 0) fQTTimeScale = frequencyFromConfig;
00588       } else {
00589         envir() << noCodecWarning1 << "Audio" << noCodecWarning2
00590                 << fOurSubsession.codecName() << noCodecWarning3;
00591         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00592         fQTEnableTrack = False; // disable this track in the movie
00593       }
00594     } else if (strcmp(fOurSubsession.mediumName(), "video") == 0) {
00595       fQTcomponentSubtype = fourChar('v','i','d','e');
00596       fQTcomponentName = "Apple Video Media Handler";
00597       fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_vmhd;
00598 
00599       // Make sure that our subsession's codec is one that we can handle:
00600       if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00601           strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00602         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00603       } else if (strcmp(fOurSubsession.codecName(), "H263-1998") == 0 ||
00604                  strcmp(fOurSubsession.codecName(), "H263-2000") == 0) {
00605         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_h263;
00606         fQTTimeScale = 600;
00607         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00608       } else if (strcmp(fOurSubsession.codecName(), "H264") == 0) {
00609         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_avc1;
00610         fQTTimeScale = 600;
00611         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00612       } else if (strcmp(fOurSubsession.codecName(), "MP4V-ES") == 0) {
00613         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4v;
00614         fQTTimeScale = 600;
00615         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00616       } else {
00617         envir() << noCodecWarning1 << "Video" << noCodecWarning2
00618                 << fOurSubsession.codecName() << noCodecWarning3;
00619         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00620         fQTEnableTrack = False; // disable this track in the movie
00621       }
00622     } else {
00623       envir() << "Warning: We don't implement a QuickTime Media Handler for media type \""
00624               << fOurSubsession.mediumName() << "\"";
00625       break;
00626     }
00627 
00628 #ifdef QT_SUPPORT_PARTIALLY_ONLY
00629     envir() << "Warning: We don't have sufficient codec-specific information (e.g., sample sizes) to fully generate the \""
00630             << fOurSubsession.mediumName() << "/" << fOurSubsession.codecName()
00631             << "\" track, so we'll disable this track in the movie.  A separate, codec-specific editing pass will be needed before this track can be played\n";
00632     fQTEnableTrack = False; // disable this track in the movie
00633 #endif
00634 
00635     return True;
00636   } while (0);
00637 
00638   envir() << ", so a track for the \"" << fOurSubsession.mediumName()
00639           << "/" << fOurSubsession.codecName()
00640           << "\" subsession will not be included in the output QuickTime file\n";
00641   return False;
00642 }
00643 
00644 void SubsessionIOState::setFinalQTstate() {
00645   // Compute derived parameters, by running through the list of chunks:
00646   fQTDurationT = 0;
00647 
00648   ChunkDescriptor* chunk = fHeadChunk;
00649   while (chunk != NULL) {
00650     unsigned const numFrames = chunk->fNumFrames;
00651     unsigned const dur = numFrames*chunk->fFrameDuration;
00652     fQTDurationT += dur;
00653 
00654     chunk = chunk->fNextChunk;
00655   }
00656 
00657   // Convert this duration from track to movie time scale:
00658   double scaleFactor = fOurSink.movieTimeScale()/(double)fQTTimeScale;
00659   fQTDurationM = (unsigned)(fQTDurationT*scaleFactor);
00660 
00661   if (fQTDurationM > fOurSink.fMaxTrackDurationM) {
00662     fOurSink.fMaxTrackDurationM = fQTDurationM;
00663   }
00664 }
00665 
00666 void SubsessionIOState::afterGettingFrame(unsigned packetDataSize,
00667                                           struct timeval presentationTime) {
00668   // Begin by checking whether there was a gap in the RTP stream.
00669   // If so, try to compensate for this (if desired):
00670   unsigned short rtpSeqNum
00671     = fOurSubsession.rtpSource()->curPacketRTPSeqNum();
00672   if (fOurSink.fPacketLossCompensate && fPrevBuffer->bytesInUse() > 0) {
00673     short seqNumGap = rtpSeqNum - fLastPacketRTPSeqNum;
00674     for (short i = 1; i < seqNumGap; ++i) {
00675       // Insert a copy of the previous frame, to compensate for the loss:
00676       useFrame(*fPrevBuffer);
00677     }
00678   }
00679   fLastPacketRTPSeqNum = rtpSeqNum;
00680 
00681   // Now, continue working with the frame that we just got
00682   if (fBuffer->bytesInUse() == 0) {
00683     fBuffer->setPresentationTime(presentationTime);
00684   }
00685   fBuffer->addBytes(packetDataSize);
00686 
00687   // If our RTP source is a "QuickTimeGenericRTPSource", then
00688   // use its 'qtState' to set some parameters that we need:
00689   if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_genericMedia){
00690     QuickTimeGenericRTPSource* rtpSource
00691       = (QuickTimeGenericRTPSource*)fOurSubsession.rtpSource();
00692     QuickTimeGenericRTPSource::QTState& qtState = rtpSource->qtState;
00693     fQTTimeScale = qtState.timescale;
00694     if (qtState.width != 0) {
00695       fOurSink.fMovieWidth = qtState.width;
00696     }
00697     if (qtState.height != 0) {
00698       fOurSink.fMovieHeight = qtState.height;
00699     }
00700 
00701     // Also, if the media type in the "sdAtom" is one that we recognize
00702     // to have a special parameters, then fix this here:
00703     if (qtState.sdAtomSize >= 8) {
00704       char const* atom = qtState.sdAtom;
00705       unsigned mediaType = fourChar(atom[4],atom[5],atom[6],atom[7]);
00706       switch (mediaType) {
00707       case fourChar('a','g','s','m'): {
00708         fQTBytesPerFrame = 33;
00709         fQTSamplesPerFrame = 160;
00710         break;
00711       }
00712       case fourChar('Q','c','l','p'): {
00713         fQTBytesPerFrame = 35;
00714         fQTSamplesPerFrame = 160;
00715         break;
00716       }
00717       case fourChar('H','c','l','p'): {
00718         fQTBytesPerFrame = 17;
00719         fQTSamplesPerFrame = 160;
00720         break;
00721       }
00722       case fourChar('h','2','6','3'): {
00723         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00724         break;
00725       }
00726       }
00727     }
00728   } else if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_Qclp) {
00729     // For QCELP data, make a note of the frame size (even though it's the
00730     // same as the packet data size), because it varies depending on the
00731     // 'rate' of the stream, and this size gets used later when setting up
00732     // the 'Qclp' QuickTime atom:
00733     fQTBytesPerFrame = packetDataSize;
00734   }
00735 
00736   useFrame(*fBuffer);
00737   if (fOurSink.fPacketLossCompensate) {
00738     // Save this frame, in case we need it for recovery:
00739     SubsessionBuffer* tmp = fPrevBuffer; // assert: != NULL
00740     fPrevBuffer = fBuffer;
00741     fBuffer = tmp;
00742   }
00743   fBuffer->