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-2012 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 "InputFile.hh"
00025 #include "OutputFile.hh"
00026 #include "H263plusVideoRTPSource.hh" // for the special header
00027 #include "MPEG4GenericRTPSource.hh" //for "samplingFrequencyFromAudioSpecificConfig()"
00028 #include "MPEG4LATMAudioRTPSource.hh" // for "parseGeneralConfigStr()"
00029 #include "Base64.hh"
00030 
00031 #include <ctype.h>
00032 
00033 #define fourChar(x,y,z,w) ( ((x)<<24)|((y)<<16)|((z)<<8)|(w) )
00034 
00035 #define H264_IDR_FRAME 0x65  //bit 8 == 0, bits 7-6 (ref) == 3, bits 5-0 (type) == 5
00036 
00038 // A structure used to represent the I/O state of each input 'subsession':
00039 
00040 class ChunkDescriptor {
00041 public:
00042   ChunkDescriptor(int64_t offsetInFile, unsigned size,
00043                   unsigned frameSize, unsigned frameDuration,
00044                   struct timeval presentationTime);
00045   virtual ~ChunkDescriptor();
00046 
00047   ChunkDescriptor* extendChunk(int64_t newOffsetInFile, unsigned newSize,
00048                                unsigned newFrameSize,
00049                                unsigned newFrameDuration,
00050                                struct timeval newPresentationTime);
00051       // this may end up allocating a new chunk instead
00052 public:
00053   ChunkDescriptor* fNextChunk;
00054   int64_t fOffsetInFile;
00055   unsigned fNumFrames;
00056   unsigned fFrameSize;
00057   unsigned fFrameDuration;
00058   struct timeval fPresentationTime; // of the start of the data
00059 };
00060 
00061 class SubsessionBuffer {
00062 public:
00063   SubsessionBuffer(unsigned bufferSize)
00064     : fBufferSize(bufferSize) {
00065     reset();
00066     fData = new unsigned char[bufferSize];
00067   }
00068   virtual ~SubsessionBuffer() { delete[] fData; }
00069   void reset() { fBytesInUse = 0; }
00070   void addBytes(unsigned numBytes) { fBytesInUse += numBytes; }
00071 
00072   unsigned char* dataStart() { return &fData[0]; }
00073   unsigned char* dataEnd() { return &fData[fBytesInUse]; }
00074   unsigned bytesInUse() const { return fBytesInUse; }
00075   unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; }
00076 
00077   void setPresentationTime(struct timeval const& presentationTime) {
00078     fPresentationTime = presentationTime;
00079   }
00080   struct timeval const& presentationTime() const {return fPresentationTime;}
00081 
00082 private:
00083   unsigned fBufferSize;
00084   struct timeval fPresentationTime;
00085   unsigned char* fData;
00086   unsigned fBytesInUse;
00087 };
00088 
00089 class SyncFrame {
00090 public:
00091   SyncFrame(unsigned frameNum);
00092   virtual ~SyncFrame();
00093 
00094 public:
00095   class SyncFrame *nextSyncFrame;
00096   unsigned sfFrameNum;  
00097 };
00098 
00099 // A 64-bit counter, used below:
00100 class Count64 {
00101 public:
00102   Count64()
00103     : hi(0), lo(0) {
00104   }
00105 
00106   void operator+=(unsigned arg);
00107 
00108   u_int32_t hi, lo;
00109 };
00110 
00111 class SubsessionIOState {
00112 public:
00113   SubsessionIOState(QuickTimeFileSink& sink, MediaSubsession& subsession);
00114   virtual ~SubsessionIOState();
00115 
00116   Boolean setQTstate();
00117   void setFinalQTstate();
00118 
00119   void afterGettingFrame(unsigned packetDataSize,
00120                          struct timeval presentationTime);
00121   void onSourceClosure();
00122 
00123   Boolean syncOK(struct timeval presentationTime);
00124       // returns true iff data is usable despite a sync check
00125 
00126   static void setHintTrack(SubsessionIOState* hintedTrack,
00127                            SubsessionIOState* hintTrack);
00128   Boolean isHintTrack() const { return fTrackHintedByUs != NULL; }
00129   Boolean hasHintTrack() const { return fHintTrackForUs != NULL; }
00130 
00131   UsageEnvironment& envir() const { return fOurSink.envir(); }
00132 
00133 public:
00134   static unsigned fCurrentTrackNumber;
00135   unsigned fTrackID;
00136   SubsessionIOState* fHintTrackForUs; SubsessionIOState* fTrackHintedByUs;
00137 
00138   SubsessionBuffer *fBuffer, *fPrevBuffer;
00139   QuickTimeFileSink& fOurSink;
00140   MediaSubsession& fOurSubsession;
00141 
00142   unsigned short fLastPacketRTPSeqNum;
00143   Boolean fOurSourceIsActive;
00144 
00145   Boolean fHaveBeenSynced; // used in synchronizing with other streams
00146   struct timeval fSyncTime;
00147 
00148   Boolean fQTEnableTrack;
00149   unsigned fQTcomponentSubtype;
00150   char const* fQTcomponentName;
00151   typedef unsigned (QuickTimeFileSink::*atomCreationFunc)();
00152   atomCreationFunc fQTMediaInformationAtomCreator;
00153   atomCreationFunc fQTMediaDataAtomCreator;
00154   char const* fQTAudioDataType;
00155   unsigned short fQTSoundSampleVersion;
00156   unsigned fQTTimeScale;
00157   unsigned fQTTimeUnitsPerSample;
00158   unsigned fQTBytesPerFrame;
00159   unsigned fQTSamplesPerFrame;
00160   // These next fields are derived from the ones above,
00161   // plus the information from each chunk:
00162   unsigned fQTTotNumSamples;
00163   unsigned fQTDurationM; // in media time units
00164   unsigned fQTDurationT; // in track time units
00165   int64_t fTKHD_durationPosn;
00166       // position of the duration in the output 'tkhd' atom
00167   unsigned fQTInitialOffsetDuration;
00168       // if there's a pause at the beginning
00169 
00170   ChunkDescriptor *fHeadChunk, *fTailChunk;
00171   unsigned fNumChunks;
00172   SyncFrame *fHeadSyncFrame, *fTailSyncFrame;
00173 
00174   // Counters to be used in the hint track's 'udta'/'hinf' atom;
00175   struct hinf {
00176     Count64 trpy;
00177     Count64 nump;
00178     Count64 tpyl;
00179     // Is 'maxr' needed? Computing this would be a PITA. #####
00180     Count64 dmed;
00181     Count64 dimm;
00182     // 'drep' is always 0
00183     // 'tmin' and 'tmax' are always 0
00184     unsigned pmax;
00185     unsigned dmax;
00186   } fHINF;
00187 
00188 private:
00189   void useFrame(SubsessionBuffer& buffer);
00190   void useFrameForHinting(unsigned frameSize,
00191                           struct timeval presentationTime,
00192                           unsigned startSampleNumber);
00193 
00194   // used by the above two routines:
00195   unsigned useFrame1(unsigned sourceDataSize,
00196                      struct timeval presentationTime,
00197                      unsigned frameDuration, int64_t destFileOffset);
00198       // returns the number of samples in this data
00199 
00200 private:
00201   // A structure used for temporarily storing frame state:
00202   struct {
00203     unsigned frameSize;
00204     struct timeval presentationTime;
00205     int64_t destFileOffset; // used for non-hint tracks only
00206 
00207     // The remaining fields are used for hint tracks only:
00208     unsigned startSampleNumber;
00209     unsigned short seqNum;
00210     unsigned rtpHeader;
00211     unsigned char numSpecialHeaders; // used when our RTP source has special headers
00212     unsigned specialHeaderBytesLength; // ditto
00213     unsigned char specialHeaderBytes[SPECIAL_HEADER_BUFFER_SIZE]; // ditto
00214     unsigned packetSizes[256];
00215   } fPrevFrameState;
00216 };
00217 
00218 
00220 
00221 QuickTimeFileSink::QuickTimeFileSink(UsageEnvironment& env,
00222                                      MediaSession& inputSession,
00223                                      char const* outputFileName,
00224                                      unsigned bufferSize,
00225                                      unsigned short movieWidth,
00226                                      unsigned short movieHeight,
00227                                      unsigned movieFPS,
00228                                      Boolean packetLossCompensate,
00229                                      Boolean syncStreams,
00230                                      Boolean generateHintTracks,
00231                                      Boolean generateMP4Format)
00232   : Medium(env), fInputSession(inputSession),
00233     fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate),
00234     fSyncStreams(syncStreams), fGenerateMP4Format(generateMP4Format),
00235     fAreCurrentlyBeingPlayed(False),
00236     fLargestRTPtimestampFrequency(0),
00237     fNumSubsessions(0), fNumSyncedSubsessions(0),
00238     fHaveCompletedOutputFile(False),
00239     fMovieWidth(movieWidth), fMovieHeight(movieHeight),
00240     fMovieFPS(movieFPS), fMaxTrackDurationM(0) {
00241   fOutFid = OpenOutputFile(env, outputFileName);
00242   if (fOutFid == NULL) return;
00243 
00244   fNewestSyncTime.tv_sec = fNewestSyncTime.tv_usec = 0;
00245   fFirstDataTime.tv_sec = fFirstDataTime.tv_usec = (unsigned)(~0);
00246 
00247   // Set up I/O state for each input subsession:
00248   MediaSubsessionIterator iter(fInputSession);
00249   MediaSubsession* subsession;
00250   while ((subsession = iter.next()) != NULL) {
00251     // Ignore subsessions without a data source:
00252     FramedSource* subsessionSource = subsession->readSource();
00253     if (subsessionSource == NULL) continue;
00254 
00255     // If "subsession's" SDP description specified screen dimension
00256     // or frame rate parameters, then use these.  (Note that this must
00257     // be done before the call to "setQTState()" below.)
00258     if (subsession->videoWidth() != 0) {
00259       fMovieWidth = subsession->videoWidth();
00260     }
00261     if (subsession->videoHeight() != 0) {
00262       fMovieHeight = subsession->videoHeight();
00263     }
00264     if (subsession->videoFPS() != 0) {
00265       fMovieFPS = subsession->videoFPS();
00266     }
00267 
00268     SubsessionIOState* ioState
00269       = new SubsessionIOState(*this, *subsession);
00270     if (ioState == NULL || !ioState->setQTstate()) {
00271       // We're not able to output a QuickTime track for this subsession
00272       delete ioState; ioState = NULL;
00273       continue;
00274     }
00275     subsession->miscPtr = (void*)ioState;
00276 
00277     if (generateHintTracks) {
00278       // Also create a hint track for this track:
00279       SubsessionIOState* hintTrack
00280         = new SubsessionIOState(*this, *subsession);
00281       SubsessionIOState::setHintTrack(ioState, hintTrack);
00282       if (!hintTrack->setQTstate()) {
00283         delete hintTrack;
00284         SubsessionIOState::setHintTrack(ioState, NULL);
00285       }
00286     }
00287 
00288     // Also set a 'BYE' handler for this subsession's RTCP instance:
00289     if (subsession->rtcpInstance() != NULL) {
00290       subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState);
00291     }
00292 
00293     unsigned rtpTimestampFrequency = subsession->rtpTimestampFrequency();
00294     if (rtpTimestampFrequency > fLargestRTPtimestampFrequency) {
00295       fLargestRTPtimestampFrequency = rtpTimestampFrequency;
00296     }
00297 
00298     ++fNumSubsessions;
00299   }
00300 
00301   // Use the current time as the file's creation and modification
00302   // time.  Use Apple's time format: seconds since January 1, 1904
00303 
00304   gettimeofday(&fStartTime, NULL);
00305   fAppleCreationTime = fStartTime.tv_sec - 0x83dac000;
00306 
00307   // Begin by writing a "mdat" atom at the start of the file.
00308   // (Later, when we've finished copying data to the file, we'll come
00309   // back and fill in its size.)
00310   fMDATposition = TellFile64(fOutFid);
00311   addAtomHeader64("mdat");
00312   // add 64Bit offset
00313   fMDATposition += 8;
00314 }
00315 
00316 QuickTimeFileSink::~QuickTimeFileSink() {
00317   completeOutputFile();
00318 
00319   // Then, delete each active "SubsessionIOState":
00320   MediaSubsessionIterator iter(fInputSession);
00321   MediaSubsession* subsession;
00322   while ((subsession = iter.next()) != NULL) {
00323     SubsessionIOState* ioState
00324       = (SubsessionIOState*)(subsession->miscPtr);
00325     if (ioState == NULL) continue;
00326 
00327     delete ioState->fHintTrackForUs; // if any
00328     delete ioState;
00329   }
00330 
00331   // Finally, close our output file:
00332   CloseOutputFile(fOutFid);
00333 }
00334 
00335 QuickTimeFileSink*
00336 QuickTimeFileSink::createNew(UsageEnvironment& env,
00337                              MediaSession& inputSession,
00338                              char const* outputFileName,
00339                              unsigned bufferSize,
00340                              unsigned short movieWidth,
00341                              unsigned short movieHeight,
00342                              unsigned movieFPS,
00343                              Boolean packetLossCompensate,
00344                              Boolean syncStreams,
00345                              Boolean generateHintTracks,
00346                              Boolean generateMP4Format) {
00347   QuickTimeFileSink* newSink = 
00348     new QuickTimeFileSink(env, inputSession, outputFileName, bufferSize, movieWidth, movieHeight, movieFPS,
00349                           packetLossCompensate, syncStreams, generateHintTracks, generateMP4Format);
00350   if (newSink == NULL || newSink->fOutFid == NULL) {
00351     Medium::close(newSink);
00352     return NULL;
00353   }
00354 
00355   return newSink;
00356 }
00357 
00358 Boolean QuickTimeFileSink::startPlaying(afterPlayingFunc* afterFunc,
00359                                         void* afterClientData) {
00360   // Make sure we're not already being played:
00361   if (fAreCurrentlyBeingPlayed) {
00362     envir().setResultMsg("This sink has already been played");
00363     return False;
00364   }
00365 
00366   fAreCurrentlyBeingPlayed = True;
00367   fAfterFunc = afterFunc;
00368   fAfterClientData = afterClientData;
00369 
00370   return continuePlaying();
00371 }
00372 
00373 Boolean QuickTimeFileSink::continuePlaying() {
00374   // Run through each of our input session's 'subsessions',
00375   // asking for a frame from each one:
00376   Boolean haveActiveSubsessions = False;
00377   MediaSubsessionIterator iter(fInputSession);
00378   MediaSubsession* subsession;
00379   while ((subsession = iter.next()) != NULL) {
00380     FramedSource* subsessionSource = subsession->readSource();
00381     if (subsessionSource == NULL) continue;
00382 
00383     if (subsessionSource->isCurrentlyAwaitingData()) continue;
00384 
00385     SubsessionIOState* ioState
00386       = (SubsessionIOState*)(subsession->miscPtr);
00387     if (ioState == NULL) continue;
00388 
00389     haveActiveSubsessions = True;
00390     unsigned char* toPtr = ioState->fBuffer->dataEnd();
00391     unsigned toSize = ioState->fBuffer->bytesAvailable();
00392     subsessionSource->getNextFrame(toPtr, toSize,
00393                                    afterGettingFrame, ioState,
00394                                    onSourceClosure, ioState);
00395   }
00396   if (!haveActiveSubsessions) {
00397     envir().setResultMsg("No subsessions are currently active");
00398     return False;
00399   }
00400 
00401   return True;
00402 }
00403 
00404 void QuickTimeFileSink
00405 ::afterGettingFrame(void* clientData, unsigned packetDataSize,
00406                     unsigned numTruncatedBytes,
00407                     struct timeval presentationTime,
00408                     unsigned /*durationInMicroseconds*/) {
00409   SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00410   if (!ioState->syncOK(presentationTime)) {
00411     // Ignore this data:
00412     ioState->fOurSink.continuePlaying();
00413     return;
00414   }
00415   if (numTruncatedBytes > 0) {
00416     ioState->envir() << "QuickTimeFileSink::afterGettingFrame(): The input frame data was too large for our buffer.  "
00417                      << numTruncatedBytes
00418                      << " bytes of trailing data was dropped!  Correct this by increasing the \"bufferSize\" parameter in the \"createNew()\" call.\n";
00419   }
00420   ioState->afterGettingFrame(packetDataSize, presentationTime);
00421 }
00422 
00423 void QuickTimeFileSink::onSourceClosure(void* clientData) {
00424   SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00425   ioState->onSourceClosure();
00426 }
00427 
00428 void QuickTimeFileSink::onSourceClosure1() {
00429   // Check whether *all* of the subsession sources have closed.
00430   // If not, do nothing for now:
00431   MediaSubsessionIterator iter(fInputSession);
00432   MediaSubsession* subsession;
00433   while ((subsession = iter.next()) != NULL) {
00434     SubsessionIOState* ioState
00435       = (SubsessionIOState*)(subsession->miscPtr);
00436     if (ioState == NULL) continue;
00437 
00438     if (ioState->fOurSourceIsActive) return; // this source hasn't closed
00439   }
00440 
00441   completeOutputFile();
00442 
00443   // Call our specified 'after' function:
00444   if (fAfterFunc != NULL) {
00445     (*fAfterFunc)(fAfterClientData);
00446   }
00447 }
00448 
00449 void QuickTimeFileSink::onRTCPBye(void* clientData) {
00450   SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00451 
00452   struct timeval timeNow;
00453   gettimeofday(&timeNow, NULL);
00454   unsigned secsDiff
00455     = timeNow.tv_sec - ioState->fOurSink.fStartTime.tv_sec;
00456 
00457   MediaSubsession& subsession = ioState->fOurSubsession;
00458   ioState->envir() << "Received RTCP \"BYE\" on \""
00459                    << subsession.mediumName()
00460                    << "/" << subsession.codecName()
00461                    << "\" subsession (after "
00462                    << secsDiff << " seconds)\n";
00463 
00464   // Handle the reception of a RTCP "BYE" as if the source had closed:
00465   ioState->onSourceClosure();
00466 }
00467 
00468 static Boolean timevalGE(struct timeval const& tv1,
00469                          struct timeval const& tv2) {
00470   return (unsigned)tv1.tv_sec > (unsigned)tv2.tv_sec
00471     || (tv1.tv_sec == tv2.tv_sec
00472         && (unsigned)tv1.tv_usec >= (unsigned)tv2.tv_usec);
00473 }
00474 
00475 void QuickTimeFileSink::completeOutputFile() {
00476   if (fHaveCompletedOutputFile || fOutFid == NULL) return;
00477 
00478   // Begin by filling in the initial "mdat" atom with the current
00479   // file size:
00480   int64_t curFileSize = TellFile64(fOutFid);
00481   setWord64(fMDATposition, (u_int64_t)curFileSize);
00482 
00483   // Then, note the time of the first received data:
00484   MediaSubsessionIterator iter(fInputSession);
00485   MediaSubsession* subsession;
00486   while ((subsession = iter.next()) != NULL) {
00487     SubsessionIOState* ioState
00488       = (SubsessionIOState*)(subsession->miscPtr);
00489     if (ioState == NULL) continue;
00490 
00491     ChunkDescriptor* const headChunk = ioState->fHeadChunk;
00492     if (headChunk != NULL
00493         && timevalGE(fFirstDataTime, headChunk->fPresentationTime)) {
00494       fFirstDataTime = headChunk->fPresentationTime;
00495     }
00496   }
00497 
00498   // Then, update the QuickTime-specific state for each active track:
00499   iter.reset();
00500   while ((subsession = iter.next()) != NULL) {
00501     SubsessionIOState* ioState
00502       = (SubsessionIOState*)(subsession->miscPtr);
00503     if (ioState == NULL) continue;
00504 
00505     ioState->setFinalQTstate();
00506     // Do the same for a hint track (if any):
00507     if (ioState->hasHintTrack()) {
00508       ioState->fHintTrackForUs->setFinalQTstate();
00509     }
00510   }
00511 
00512   if (fGenerateMP4Format) {
00513     // Begin with a "ftyp" atom:
00514     addAtom_ftyp();
00515   }
00516 
00517   // Then, add a "moov" atom for the file metadata:
00518   addAtom_moov();
00519 
00520   // We're done:
00521   fHaveCompletedOutputFile = True;
00522 }
00523 
00524 
00526 
00527 unsigned SubsessionIOState::fCurrentTrackNumber = 0;
00528 
00529 SubsessionIOState::SubsessionIOState(QuickTimeFileSink& sink,
00530                                      MediaSubsession& subsession)
00531   : fHintTrackForUs(NULL), fTrackHintedByUs(NULL),
00532     fOurSink(sink), fOurSubsession(subsession),
00533     fLastPacketRTPSeqNum(0), fHaveBeenSynced(False), fQTTotNumSamples(0), 
00534     fHeadChunk(NULL), fTailChunk(NULL), fNumChunks(0),
00535     fHeadSyncFrame(NULL), fTailSyncFrame(NULL) {
00536   fTrackID = ++fCurrentTrackNumber;
00537 
00538   fBuffer = new SubsessionBuffer(fOurSink.fBufferSize);
00539   fPrevBuffer = sink.fPacketLossCompensate
00540     ? new SubsessionBuffer(fOurSink.fBufferSize) : NULL;
00541 
00542   FramedSource* subsessionSource = subsession.readSource();
00543   fOurSourceIsActive = subsessionSource != NULL;
00544 
00545   fPrevFrameState.presentationTime.tv_sec = 0;
00546   fPrevFrameState.presentationTime.tv_usec = 0;
00547   fPrevFrameState.seqNum = 0;
00548 }
00549 
00550 SubsessionIOState::~SubsessionIOState() {
00551   delete fBuffer; delete fPrevBuffer;
00552   delete fHeadChunk; delete fHeadSyncFrame;
00553 }
00554 
00555 Boolean SubsessionIOState::setQTstate() {
00556   char const* noCodecWarning1 = "Warning: We don't implement a QuickTime ";
00557   char const* noCodecWarning2 = " Media Data Type for the \"";
00558   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";
00559 
00560   do {
00561     fQTEnableTrack = True; // enable this track in the movie by default
00562     fQTTimeScale = fOurSubsession.rtpTimestampFrequency(); // by default
00563     fQTTimeUnitsPerSample = 1; // by default
00564     fQTBytesPerFrame = 0;
00565         // by default - indicates that the whole packet data is a frame
00566     fQTSamplesPerFrame = 1; // by default
00567 
00568     // Make sure our subsession's medium is one that we know how to
00569     // represent in a QuickTime file:
00570     if (isHintTrack()) {
00571       // Hint tracks are treated specially
00572       fQTEnableTrack = False; // hint tracks are marked as inactive
00573       fQTcomponentSubtype = fourChar('h','i','n','t');
00574       fQTcomponentName = "hint media handler";
00575       fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_gmhd;
00576       fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_rtp;
00577     } else if (strcmp(fOurSubsession.mediumName(), "audio") == 0) {
00578       fQTcomponentSubtype = fourChar('s','o','u','n');
00579       fQTcomponentName = "Apple Sound Media Handler";
00580       fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_smhd;
00581       fQTMediaDataAtomCreator
00582         = &QuickTimeFileSink::addAtom_soundMediaGeneral; // by default
00583       fQTSoundSampleVersion = 0; // by default
00584 
00585       // Make sure that our subsession's codec is one that we can handle:
00586       if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00587           strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00588         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00589       } else if (strcmp(fOurSubsession.codecName(), "PCMU") == 0) {
00590         fQTAudioDataType = "ulaw";
00591         fQTBytesPerFrame = 1;
00592       } else if (strcmp(fOurSubsession.codecName(), "GSM") == 0) {
00593         fQTAudioDataType = "agsm";
00594         fQTBytesPerFrame = 33;
00595         fQTSamplesPerFrame = 160;
00596       } else if (strcmp(fOurSubsession.codecName(), "PCMA") == 0) {
00597         fQTAudioDataType = "alaw";
00598         fQTBytesPerFrame = 1;
00599       } else if (strcmp(fOurSubsession.codecName(), "QCELP") == 0) {
00600         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_Qclp;
00601         fQTSamplesPerFrame = 160;
00602       } else if (strcmp(fOurSubsession.codecName(), "MPEG4-GENERIC") == 0 ||
00603                  strcmp(fOurSubsession.codecName(), "MP4A-LATM") == 0) {
00604         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4a;
00605         fQTTimeUnitsPerSample = 1024; // QT considers each frame to be a 'sample'
00606         // The time scale (frequency) comes from the 'config' information.
00607         // It might be different from the RTP timestamp frequency (e.g., aacPlus).
00608         unsigned frequencyFromConfig
00609           = samplingFrequencyFromAudioSpecificConfig(fOurSubsession.fmtp_config());
00610         if (frequencyFromConfig != 0) fQTTimeScale = frequencyFromConfig;
00611       } else {
00612         envir() << noCodecWarning1 << "Audio" << noCodecWarning2
00613                 << fOurSubsession.codecName() << noCodecWarning3;
00614         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00615         fQTEnableTrack = False; // disable this track in the movie
00616       }
00617     } else if (strcmp(fOurSubsession.mediumName(), "video") == 0) {
00618       fQTcomponentSubtype = fourChar('v','i','d','e');
00619       fQTcomponentName = "Apple Video Media Handler";
00620       fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_vmhd;
00621 
00622       // Make sure that our subsession's codec is one that we can handle:
00623       if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00624           strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00625         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00626       } else if (strcmp(fOurSubsession.codecName(), "H263-1998") == 0 ||
00627                  strcmp(fOurSubsession.codecName(), "H263-2000") == 0) {
00628         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_h263;
00629         fQTTimeScale = 600;
00630         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00631       } else if (strcmp(fOurSubsession.codecName(), "H264") == 0) {
00632         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_avc1;
00633         fQTTimeScale = 600;
00634         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00635       } else if (strcmp(fOurSubsession.codecName(), "MP4V-ES") == 0) {
00636         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4v;
00637         fQTTimeScale = 600;
00638         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00639       } else {
00640         envir() << noCodecWarning1 << "Video" << noCodecWarning2
00641                 << fOurSubsession.codecName() << noCodecWarning3;
00642         fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00643         fQTEnableTrack = False; // disable this track in the movie
00644       }
00645     } else {
00646       envir() << "Warning: We don't implement a QuickTime Media Handler for media type \""
00647               << fOurSubsession.mediumName() << "\"";
00648       break;
00649     }
00650 
00651 #ifdef QT_SUPPORT_PARTIALLY_ONLY
00652     envir() << "Warning: We don't have sufficient codec-specific information (e.g., sample sizes) to fully generate the \""
00653             << fOurSubsession.mediumName() << "/" << fOurSubsession.codecName()
00654             << "\" 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";
00655     fQTEnableTrack = False; // disable this track in the movie
00656 #endif
00657 
00658     return True;
00659   } while (0);
00660 
00661   envir() << ", so a track for the \"" << fOurSubsession.mediumName()
00662           << "/" << fOurSubsession.codecName()
00663           << "\" subsession will not be included in the output QuickTime file\n";
00664   return False;
00665 }
00666 
00667 void SubsessionIOState::setFinalQTstate() {
00668   // Compute derived parameters, by running through the list of chunks:
00669   fQTDurationT = 0;
00670 
00671   ChunkDescriptor* chunk = fHeadChunk;
00672   while (chunk != NULL) {
00673     unsigned const numFrames = chunk->fNumFrames;
00674     unsigned const dur = numFrames*chunk->fFrameDuration;
00675     fQTDurationT += dur;
00676 
00677     chunk = chunk->fNextChunk;
00678   }
00679 
00680   // Convert this duration from track to movie time scale:
00681   double scaleFactor = fOurSink.movieTimeScale()/(double)fQTTimeScale;
00682   fQTDurationM = (unsigned)(fQTDurationT*scaleFactor);
00683 
00684   if (fQTDurationM > fOurSink.fMaxTrackDurationM) {
00685     fOurSink.fMaxTrackDurationM = fQTDurationM;
00686   }
00687 }
00688 
00689 void SubsessionIOState::afterGettingFrame(unsigned packetDataSize,
00690                                           struct timeval presentationTime) {
00691   // Begin by checking whether there was a gap in the RTP stream.
00692   // If so, try to compensate for this (if desired):
00693   unsigned short rtpSeqNum
00694     = fOurSubsession.rtpSource()->curPacketRTPSeqNum();
00695   if (fOurSink.fPacketLossCompensate && fPrevBuffer->bytesInUse() > 0) {
00696     short seqNumGap = rtpSeqNum - fLastPacketRTPSeqNum;
00697     for (short i = 1; i < seqNumGap; ++i) {
00698       // Insert a copy of the previous frame, to compensate for the loss:
00699       useFrame(*fPrevBuffer);
00700     }
00701   }
00702   fLastPacketRTPSeqNum = rtpSeqNum;
00703 
00704   // Now, continue working with the frame that we just got
00705   if (fBuffer->bytesInUse() == 0) {
00706     fBuffer->setPresentationTime(presentationTime);
00707   }
00708   fBuffer->addBytes(packetDataSize);
00709 
00710   // If our RTP source is a "QuickTimeGenericRTPSource", then
00711   // use its 'qtState' to set some parameters that we need:
00712   if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_genericMedia){
00713     QuickTimeGenericRTPSource* rtpSource
00714       = (QuickTimeGenericRTPSource*)fOurSubsession.rtpSource();
00715     QuickTimeGenericRTPSource::QTState& qtState = rtpSource->qtState;
00716     fQTTimeScale = qtState.timescale;
00717     if (qtState.width != 0) {
00718       fOurSink.fMovieWidth = qtState.width;
00719     }
00720     if (qtState.height != 0) {
00721       fOurSink.fMovieHeight = qtState.height;
00722     }
00723 
00724     // Also, if the media type in the "sdAtom" is one that we recognize
00725     // to have a special parameters, then fix this here:
00726     if (qtState.sdAtomSize >= 8) {
00727       char const* atom = qtState.sdAtom;
00728       unsigned mediaType = fourChar(atom[4],atom[5],atom[6],atom[7]);
00729       switch (mediaType) {
00730       case fourChar('a','g','s','m'): {
00731         fQTBytesPerFrame = 33;
00732         fQTSamplesPerFrame = 160;
00733         break;
00734       }
00735       case fourChar('Q','c','l','p'): {
00736         fQTBytesPerFrame = 35;
00737         fQTSamplesPerFrame = 160;
00738         break;
00739       }
00740       case fourChar('H','c','l','p'): {
00741         fQTBytesPerFrame = 17;
00742         fQTSamplesPerFrame = 160;
00743         break;
00744       }
00745       case fourChar('h','2','6','3'): {
00746         fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00747         break;
00748       }
00749       }
00750     }
00751   } else if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_Qclp) {
00752     // For QCELP data, make a note of the frame size (even though it's the
00753     // same as the packet data size), because it varies depending on the
00754     // 'rate' of the stream, and this size gets used later when setting up
00755     // the 'Qclp' QuickTime atom:
00756     fQTBytesPerFrame = packetDataSize;
00757   }
00758 
00759   useFrame(*fBuffer);
00760   if (fOurSink.fPacketLossCompensate) {
00761     // Save this frame, in case we need it for recovery:
00762     SubsessionBuffer* tmp = fPrevBuffer; // assert: != NULL
00763     fPrevBuffer = fBuffer;
00764     fBuffer = tmp;
00765   }
00766   fBuffer->reset(); // for the next input
00767 
00768   // Now, try getting more frames:
00769   fOurSink.continuePlaying();
00770 }
00771 
00772 void SubsessionIOState::useFrame(SubsessionBuffer& buffer) {
00773   unsigned char* const frameSource = buffer.dataStart();
00774   unsigned const frameSize = buffer.bytesInUse();
00775   struct timeval const& presentationTime = buffer.presentationTime();
00776   int64_t const destFileOffset = TellFile64(fOurSink.fOutFid);
00777   unsigned sampleNumberOfFrameStart = fQTTotNumSamples + 1;
00778   Boolean avcHack = fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_avc1;
00779 
00780   // If we're not syncing streams, or this subsession is not video, then
00781   // just give this frame a fixed duration:
00782   if (!fOurSink.fSyncStreams
00783       || fQTcomponentSubtype != fourChar('v','i','d','e')) {
00784     unsigned const frameDuration = fQTTimeUnitsPerSample*fQTSamplesPerFrame;
00785     unsigned frameSizeToUse = frameSize;
00786     if (avcHack) frameSizeToUse += 4; // H.264/AVC gets the frame size prefix
00787 
00788     fQTTotNumSamples += useFrame1(frameSizeToUse, presentationTime, frameDuration, destFileOffset);
00789   } else {
00790     // For synced video streams, we use the difference between successive
00791     // frames' presentation times as the 'frame duration'.  So, record
00792     // information about the *previous* frame:
00793     struct timeval const& ppt = fPrevFrameState.presentationTime; //abbrev
00794     if (ppt.tv_sec != 0 || ppt.tv_usec != 0) {
00795       // There has been a previous frame.
00796       double duration = (presentationTime.tv_sec - ppt.tv_sec)
00797         + (presentationTime.tv_usec - ppt.tv_usec)/1000000.0;
00798       if (duration < 0.0) duration = 0.0;
00799       unsigned frameDuration
00800         = (unsigned)((2*duration*fQTTimeScale+1)/2); // round
00801       unsigned frameSizeToUse = fPrevFrameState.frameSize;
00802       if (avcHack) frameSizeToUse += 4; // H.264/AVC gets the frame size prefix
00803 
00804       unsigned numSamples
00805         = useFrame1(frameSizeToUse, ppt, frameDuration, fPrevFrameState.destFileOffset);
00806       fQTTotNumSamples += numSamples;
00807       sampleNumberOfFrameStart = fQTTotNumSamples + 1;
00808     }
00809 
00810     if (avcHack && (*frameSource == H264_IDR_FRAME)) {
00811       SyncFrame* newSyncFrame = new SyncFrame(fQTTotNumSamples + 1);
00812       if (fTailSyncFrame == NULL) {
00813         fHeadSyncFrame = newSyncFrame;
00814       } else {
00815         fTailSyncFrame->nextSyncFrame = newSyncFrame;
00816       }
00817       fTailSyncFrame = newSyncFrame;
00818     }
00819 
00820     // Remember the current frame for next time:
00821     fPrevFrameState.frameSize = frameSize;
00822     fPrevFrameState.presentationTime = presentationTime;
00823     fPrevFrameState.destFileOffset = destFileOffset;
00824   }
00825 
00826   if (avcHack) fOurSink.addWord(frameSize);
00827 
00828   // Write the data into the file:
00829   fwrite(frameSource, 1, frameSize, fOurSink.fOutFid);
00830 
00831   // If we have a hint track, then write to it also:
00832   if (hasHintTrack()) {
00833     // Because presentation times are used for RTP packet timestamps,
00834     // we don't starting writing to the hint track until we've been synced:
00835     if (!fHaveBeenSynced) {
00836       fHaveBeenSynced
00837         = fOurSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP();
00838     }
00839     if (fHaveBeenSynced) {
00840       fHintTrackForUs->useFrameForHinting(frameSize, presentationTime,
00841                                           sampleNumberOfFrameStart);
00842     }
00843   }
00844 }
00845 
00846 void SubsessionIOState::useFrameForHinting(unsigned frameSize,
00847                                            struct timeval presentationTime,
00848                                            unsigned startSampleNumber) {
00849   // At this point, we have a single, combined frame - not individual packets.
00850   // For the hint track, we need to split the frame back up into separate packets.
00851   // However, for some RTP sources, then we also need to reuse the special
00852   // header bytes that were at the start of each of the RTP packets.
00853   Boolean hack263 = strcmp(fOurSubsession.codecName(), "H263-1998") == 0;
00854   Boolean hackm4a_generic = strcmp(fOurSubsession.mediumName(), "audio") == 0
00855     && strcmp(fOurSubsession.codecName(), "MPEG4-GENERIC") == 0;
00856   Boolean hackm4a_latm = strcmp(fOurSubsession.mediumName(), "audio") == 0
00857     && strcmp(fOurSubsession.codecName(), "MP4A-LATM") == 0;
00858   Boolean hackm4a = hackm4a_generic || hackm4a_latm;
00859   Boolean haveSpecialHeaders = (hack263 || hackm4a_generic);
00860 
00861   // If there has been a previous frame, then output a 'hint sample' for it.
00862   // (We use the current frame's presentation time to compute the previous
00863   // hint sample's duration.)
00864   RTPSource* const rs = fOurSubsession.rtpSource(); // abbrev
00865   struct timeval const& ppt = fPrevFrameState.presentationTime; //abbrev
00866   if (ppt.tv_sec != 0 || ppt.tv_usec != 0) {
00867     double duration = (presentationTime.tv_sec - ppt.tv_sec)
00868       + (presentationTime.tv_usec - ppt.tv_usec)/1000000.0;
00869     if (duration < 0.0) duration = 0.0;
00870     unsigned msDuration = (unsigned)(duration*1000); // milliseconds
00871     if (msDuration > fHINF.dmax) fHINF.dmax = msDuration;
00872     unsigned hintSampleDuration
00873       = (unsigned)((2*duration*fQTTimeScale+1)/2); // round
00874     if (hackm4a) {
00875       // Because multiple AAC frames can appear in a RTP packet, the presentation
00876       // times of the second and subsequent frames will not be accurate.
00877       // So, use the known "hintSampleDuration" instead:
00878       hintSampleDuration = fTrackHintedByUs->fQTTimeUnitsPerSample;
00879 
00880       // Also, if the 'time scale' was different from the RTP timestamp frequency,
00881       // (as can happen with aacPlus), then we need to scale "hintSampleDuration"
00882       // accordingly:
00883       if (fTrackHintedByUs->fQTTimeScale != fOurSubsession.rtpTimestampFrequency()) {
00884         unsigned const scalingFactor
00885           = fOurSubsession.rtpTimestampFrequency()/fTrackHintedByUs->fQTTimeScale ;
00886         hintSampleDuration *= scalingFactor;
00887       }
00888     }
00889 
00890     int64_t const hintSampleDestFileOffset = TellFile64(fOurSink.fOutFid);
00891 
00892     unsigned const maxPacketSize = 1450;
00893     unsigned short numPTEntries
00894       = (fPrevFrameState.frameSize + (maxPacketSize-1))/maxPacketSize; // normal case
00895     unsigned char* immediateDataPtr = NULL;
00896     unsigned immediateDataBytesRemaining = 0;
00897     if (haveSpecialHeaders) { // special case
00898       numPTEntries = fPrevFrameState.numSpecialHeaders;
00899       immediateDataPtr = fPrevFrameState.specialHeaderBytes;
00900       immediateDataBytesRemaining
00901         = fPrevFrameState.specialHeaderBytesLength;
00902     }
00903     unsigned hintSampleSize
00904       = fOurSink.addHalfWord(numPTEntries);// Entry count
00905     hintSampleSize += fOurSink.addHalfWord(0x0000); // Reserved
00906 
00907     unsigned offsetWithinSample = 0;
00908     for (unsigned i = 0; i < numPTEntries; ++i) {
00909       // Output a Packet Table entry (representing a single RTP packet):
00910       unsigned short numDTEntries = 1;
00911       unsigned short seqNum = fPrevFrameState.seqNum++;
00912           // Note: This assumes that the input stream had no packets lost #####
00913       unsigned rtpHeader = fPrevFrameState.rtpHeader;
00914       if (i+1 < numPTEntries) {
00915         // This is not the last RTP packet, so clear the marker bit:
00916         rtpHeader &=~ (1<<23);
00917       }
00918       unsigned dataFrameSize = (i+1 < numPTEntries)
00919         ? maxPacketSize : fPrevFrameState.frameSize - i*maxPacketSize; // normal case
00920       unsigned sampleNumber = fPrevFrameState.startSampleNumber;
00921 
00922       unsigned char immediateDataLen = 0;
00923       if (haveSpecialHeaders) { // special case
00924         ++numDTEntries; // to include a Data Table entry for the special hdr
00925         if (immediateDataBytesRemaining > 0) {
00926           if (hack263) {
00927             immediateDataLen = *immediateDataPtr++;
00928             --immediateDataBytesRemaining;
00929             if (immediateDataLen > immediateDataBytesRemaining) {
00930               // shouldn't happen (length byte was bad)
00931               immediateDataLen = immediateDataBytesRemaining;
00932             }
00933           } else {
00934             immediateDataLen = fPrevFrameState.specialHeaderBytesLength;
00935           }
00936         }
00937         dataFrameSize = fPrevFrameState.packetSizes[i] - immediateDataLen;
00938 
00939         if (hack263) {
00940           Boolean PbitSet
00941             = immediateDataLen >= 1 && (immediateDataPtr[0]&0x4) != 0;
00942           if (PbitSet) {
00943             offsetWithinSample += 2; // to omit the two leading 0 bytes
00944           }
00945         }
00946       }
00947 
00948       // Output the Packet Table:
00949       hintSampleSize += fOurSink.addWord(0); // Relative transmission time
00950       hintSampleSize += fOurSink.addWord(rtpHeader|seqNum);
00951           // RTP header info + RTP sequence number
00952       hintSampleSize += fOurSink.addHalfWord(0x0000); // Flags
00953       hintSampleSize += fOurSink.addHalfWord(numDTEntries); // Entry count
00954       unsigned totalPacketSize = 0;
00955 
00956       // Output the Data Table:
00957       if (haveSpecialHeaders) {
00958         //   use the "Immediate Data" format (1):
00959         hintSampleSize += fOurSink.addByte(1); // Source
00960         unsigned char len = immediateDataLen > 14 ? 14 : immediateDataLen;
00961         hintSampleSize += fOurSink.addByte(len); // Length
00962         totalPacketSize += len; fHINF.dimm += len;
00963         unsigned char j;
00964         for (j = 0; j < len; ++j) {
00965           hintSampleSize += fOurSink.addByte(immediateDataPtr[j]); // Data
00966         }
00967         for (j = len; j < 14; ++j) {
00968           hintSampleSize += fOurSink.addByte(0); // Data (padding)
00969         }
00970 
00971         immediateDataPtr += immediateDataLen;
00972         immediateDataBytesRemaining -= immediateDataLen;
00973       }
00974       //   use the "Sample Data" format (2):
00975       hintSampleSize += fOurSink.addByte(2); // Source
00976       hintSampleSize += fOurSink.addByte(0); // Track ref index
00977       hintSampleSize += fOurSink.addHalfWord(dataFrameSize); // Length
00978       totalPacketSize += dataFrameSize; fHINF.dmed += dataFrameSize;
00979       hintSampleSize += fOurSink.addWord(sampleNumber); // Sample number
00980       hintSampleSize += fOurSink.addWord(offsetWithinSample); // Offset
00981       // Get "bytes|samples per compression block" from the hinted track:
00982       unsigned short const bytesPerCompressionBlock
00983         = fTrackHintedByUs->fQTBytesPerFrame;
00984       unsigned short const samplesPerCompressionBlock
00985         = fTrackHintedByUs->fQTSamplesPerFrame;
00986       hintSampleSize += fOurSink.addHalfWord(bytesPerCompressionBlock);
00987       hintSampleSize += fOurSink.addHalfWord(samplesPerCompressionBlock);
00988 
00989       offsetWithinSample += dataFrameSize;// for the next iteration (if any)
00990 
00991       // Tally statistics for this packet:
00992       fHINF.nump += 1;
00993       fHINF.tpyl += totalPacketSize;
00994       totalPacketSize += 12; // add in the size of the RTP header
00995       fHINF.trpy += totalPacketSize;
00996       if (totalPacketSize > fHINF.pmax) fHINF.pmax = totalPacketSize;
00997     }
00998 
00999     // Make note of this completed hint sample frame:
01000     fQTTotNumSamples += useFrame1(hintSampleSize, ppt, hintSampleDuration,
01001                                   hintSampleDestFileOffset);
01002   }
01003 
01004   // Remember this frame for next time:
01005   fPrevFrameState.frameSize = frameSize;
01006   fPrevFrameState.presentationTime = presentationTime;
01007   fPrevFrameState.startSampleNumber = startSampleNumber;
01008   fPrevFrameState.rtpHeader
01009     = rs->curPacketMarkerBit()<<23
01010     | (rs->rtpPayloadFormat()&0x7F)<<16;
01011   if (hack263) {
01012     H263plusVideoRTPSource* rs_263 = (H263plusVideoRTPSource*)rs;
01013     fPrevFrameState.numSpecialHeaders = rs_263->fNumSpecialHeaders;
01014     fPrevFrameState.specialHeaderBytesLength = rs_263->fSpecialHeaderBytesLength;
01015     unsigned i;
01016     for (i = 0; i < rs_263->fSpecialHeaderBytesLength; ++i) {
01017       fPrevFrameState.specialHeaderBytes[i] = rs_263->fSpecialHeaderBytes[i];
01018     }
01019     for (i = 0; i < rs_263->fNumSpecialHeaders; ++i) {
01020       fPrevFrameState.packetSizes[i] = rs_263->fPacketSizes[i];
01021     }
01022   } else if (hackm4a_generic) {
01023     // Synthesize a special header, so that this frame can be in its own RTP packet.
01024     unsigned const sizeLength = fOurSubsession.fmtp_sizelength();
01025     unsigned const indexLength = fOurSubsession.fmtp_indexlength();
01026     if (sizeLength + indexLength != 16) {
01027       envir() << "Warning: unexpected 'sizeLength' " << sizeLength
01028               << " and 'indexLength' " << indexLength
01029               << "seen when creating hint track\n";
01030     }
01031     fPrevFrameState.numSpecialHeaders = 1;
01032     fPrevFrameState.specialHeaderBytesLength = 4;
01033     fPrevFrameState.specialHeaderBytes[0] = 0; // AU_headers_length (high byte)
01034     fPrevFrameState.specialHeaderBytes[1] = 16; // AU_headers_length (low byte)
01035     fPrevFrameState.specialHeaderBytes[2] = ((frameSize<<indexLength)&0xFF00)>>8;
01036     fPrevFrameState.specialHeaderBytes[3] = (frameSize<<indexLength);
01037     fPrevFrameState.packetSizes[0]
01038       = fPrevFrameState.specialHeaderBytesLength + frameSize;
01039   }
01040 }
01041 
01042 unsigned SubsessionIOState::useFrame1(unsigned sourceDataSize,
01043                                       struct timeval presentationTime,
01044                                       unsigned frameDuration,
01045                                       int64_t destFileOffset) {
01046   // Figure out the actual frame size for this data:
01047   unsigned frameSize = fQTBytesPerFrame;
01048   if (frameSize == 0) {
01049     // The entire packet data is assumed to be a frame:
01050     frameSize = sourceDataSize;
01051   }
01052   unsigned const numFrames = sourceDataSize/frameSize;
01053   unsigned const numSamples = numFrames*fQTSamplesPerFrame;
01054 
01055   // Record the information about which 'chunk' this data belongs to:
01056   ChunkDescriptor* newTailChunk;
01057   if (fTailChunk == NULL) {
01058     newTailChunk = fHeadChunk
01059       = new ChunkDescriptor(destFileOffset, sourceDataSize,
01060                             frameSize, frameDuration, presentationTime);
01061   } else {
01062     newTailChunk = fTailChunk->extendChunk(destFileOffset, sourceDataSize,
01063                                            frameSize, frameDuration,
01064                                            presentationTime);
01065   }
01066   if (newTailChunk != fTailChunk) {
01067    // This data created a new chunk, rather than extending the old one
01068     ++fNumChunks;
01069     fTailChunk = newTailChunk;
01070   }
01071 
01072   return numSamples;
01073 }
01074 
01075 void SubsessionIOState::onSourceClosure() {
01076   fOurSourceIsActive = False;
01077   fOurSink.onSourceClosure1();
01078 }
01079 
01080 Boolean SubsessionIOState::syncOK(struct timeval presentationTime) {
01081   QuickTimeFileSink& s = fOurSink; // abbreviation
01082   if (!s.fSyncStreams) return True; // we don't care
01083 
01084   if (s.fNumSyncedSubsessions < s.fNumSubsessions) {
01085     // Not all subsessions have yet been synced.  Check whether ours was
01086     // one of the unsynced ones, and, if so, whether it is now synced:
01087     if (!fHaveBeenSynced) {
01088       // We weren't synchronized before
01089       if (fOurSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) {
01090         // H264 ?
01091         if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_avc1) {
01092           // special case: audio + H264 video: wait until audio is in sync
01093           if ((s.fNumSubsessions == 2) && (s.fNumSyncedSubsessions < (s.fNumSubsessions - 1))) return False;
01094 
01095           // if audio is in sync, wait for the next IDR frame to start
01096           unsigned char* const frameSource = fBuffer->dataStart();
01097           if (*frameSource != H264_IDR_FRAME) return False;
01098         }
01099         // But now we are
01100         fHaveBeenSynced = True;
01101         fSyncTime = presentationTime;
01102         ++s.fNumSyncedSubsessions;
01103 
01104         if (timevalGE(fSyncTime, s.fNewestSyncTime)) {
01105           s.fNewestSyncTime = fSyncTime;
01106         }
01107       }
01108     }
01109   }
01110 
01111   // Check again whether all subsessions have been synced:
01112   if (s.fNumSyncedSubsessions < s.fNumSubsessions) return False;
01113 
01114   // Allow this data if it is more recent than the newest sync time:
01115   return timevalGE(presentationTime, s.fNewestSyncTime);
01116 }
01117 
01118 void SubsessionIOState::setHintTrack(SubsessionIOState* hintedTrack,
01119                                      SubsessionIOState* hintTrack) {
01120   if (hintedTrack != NULL) hintedTrack->fHintTrackForUs = hintTrack;
01121   if (hintTrack != NULL) hintTrack->fTrackHintedByUs = hintedTrack;
01122 }
01123 
01124 SyncFrame::SyncFrame(unsigned frameNum)
01125   : nextSyncFrame(NULL), sfFrameNum(frameNum) {
01126 }  
01127 
01128 SyncFrame::~SyncFrame() {
01129   delete nextSyncFrame;
01130 }
01131 
01132 void Count64::operator+=(unsigned arg) {
01133   unsigned newLo = lo + arg;
01134   if (newLo < lo) { // lo has overflowed
01135     ++hi;
01136   }
01137   lo = newLo;
01138 }
01139 
01140 ChunkDescriptor
01141 ::ChunkDescriptor(int64_t offsetInFile, unsigned size,
01142                   unsigned frameSize, unsigned frameDuration,
01143                   struct timeval presentationTime)
01144   : fNextChunk(NULL), fOffsetInFile(offsetInFile),
01145     fNumFrames(size/frameSize),
01146     fFrameSize(frameSize), fFrameDuration(frameDuration),
01147     fPresentationTime(presentationTime) {
01148 }
01149 
01150 ChunkDescriptor::~ChunkDescriptor() {
01151   delete fNextChunk;
01152 }
01153 
01154 ChunkDescriptor* ChunkDescriptor
01155 ::extendChunk(int64_t newOffsetInFile, unsigned newSize,
01156               unsigned newFrameSize, unsigned newFrameDuration,
01157               struct timeval newPresentationTime) {
01158   // First, check whether the new space is just at the end of this
01159   // existing chunk:
01160   if (newOffsetInFile == fOffsetInFile + fNumFrames*fFrameSize) {
01161     // We can extend this existing chunk, provided that the frame size
01162     // and frame duration have not changed:
01163     if (newFrameSize == fFrameSize && newFrameDuration == fFrameDuration) {
01164       fNumFrames += newSize/fFrameSize;
01165       return this;
01166     }
01167   }
01168 
01169   // We'll allocate a new ChunkDescriptor, and link it to the end of us:
01170   ChunkDescriptor* newDescriptor
01171     = new ChunkDescriptor(newOffsetInFile, newSize,
01172                           newFrameSize, newFrameDuration,
01173                           newPresentationTime);
01174 
01175   fNextChunk = newDescriptor;
01176 
01177   return newDescriptor;
01178 }
01179 
01180 
01182 
01183 unsigned QuickTimeFileSink::addWord64(u_int64_t word) {
01184   addByte((unsigned char)(word>>56)); addByte((unsigned char)(word>>48));
01185   addByte((unsigned char)(word>>40)); addByte((unsigned char)(word>>32));
01186   addByte((unsigned char)(word>>24)); addByte((unsigned char)(word>>16));
01187   addByte((unsigned char)(word>>8)); addByte((unsigned char)(word));
01188 
01189   return 8;
01190 }
01191 
01192 unsigned QuickTimeFileSink::addWord(unsigned word) {
01193   addByte(word>>24); addByte(word>>16);
01194   addByte(word>>8); addByte(word);
01195 
01196   return 4;
01197 }
01198 
01199 unsigned QuickTimeFileSink::addHalfWord(unsigned short halfWord) {
01200   addByte((unsigned char)(halfWord>>8)); addByte((unsigned char)halfWord);
01201 
01202   return 2;
01203 }
01204 
01205 unsigned QuickTimeFileSink::addZeroWords(unsigned numWords) {
01206   for (unsigned i = 0; i < numWords; ++i) {
01207     addWord(0);
01208   }
01209 
01210   return numWords*4;
01211 }
01212 
01213 unsigned QuickTimeFileSink::add4ByteString(char const* str) {
01214   addByte(str[0]); addByte(str[1]); addByte(str[2]); addByte(str[3]);
01215 
01216   return 4;
01217 }
01218 
01219 unsigned QuickTimeFileSink::addArbitraryString(char const* str,
01220                                                Boolean oneByteLength) {
01221   unsigned size = 0;
01222   if (oneByteLength) {
01223     // Begin with a byte containing the string length:
01224     unsigned strLength = strlen(str);
01225     if (strLength >= 256) {
01226       envir() << "QuickTimeFileSink::addArbitraryString(\""
01227               << str << "\") saw string longer than we know how to handle ("
01228               << strLength << "\n";
01229     }
01230     size += addByte((unsigned char)strLength);
01231   }
01232 
01233   while (*str != '\0') {
01234     size += addByte(*str++);
01235   }
01236 
01237   return size;
01238 }
01239 
01240 unsigned QuickTimeFileSink::addAtomHeader(char const* atomName) {
01241   // Output a placeholder for the 4-byte size:
01242   addWord(0);
01243 
01244   // Output the 4-byte atom name:
01245   add4ByteString(atomName);
01246 
01247   return 8;
01248 }
01249 
01250 unsigned QuickTimeFileSink::addAtomHeader64(char const* atomName) {
01251   // Output 64Bit size marker
01252   addWord(1);
01253 
01254   // Output the 4-byte atom name:
01255   add4ByteString(atomName);
01256 
01257   addWord64(0);
01258 
01259   return 16;
01260 }
01261 
01262 void QuickTimeFileSink::setWord(int64_t filePosn, unsigned size) {
01263   do {
01264     if (SeekFile64(fOutFid, filePosn, SEEK_SET) < 0) break;
01265     addWord(size);
01266     if (SeekFile64(fOutFid, 0, SEEK_END) < 0) break; // go back to where we were
01267 
01268     return;
01269   } while (0);
01270 
01271   // One of the SeekFile64()s failed, probable because we're not a seekable file
01272   envir() << "QuickTimeFileSink::setWord(): SeekFile64 failed (err "
01273           << envir().getErrno() << ")\n";
01274 }
01275 
01276 void QuickTimeFileSink::setWord64(int64_t filePosn, u_int64_t size) {
01277   do {
01278     if (SeekFile64(fOutFid, filePosn, SEEK_SET) < 0) break;
01279     addWord64(size);
01280     if (SeekFile64(fOutFid, 0, SEEK_END) < 0) break; // go back to where we were
01281 
01282     return;
01283   } while (0);
01284 
01285   // One of the SeekFile64()s failed, probable because we're not a seekable file
01286   envir() << "QuickTimeFileSink::setWord(): SeekFile64 failed (err "
01287           << envir().getErrno() << ")\n";
01288 }
01289 
01290 // Methods for writing particular atoms.  Note the following macros:
01291 
01292 #define addAtom(name) \
01293     unsigned QuickTimeFileSink::addAtom_##name() { \
01294     int64_t initFilePosn = TellFile64(fOutFid); \
01295     unsigned size = addAtomHeader("" #name "")
01296 
01297 #define addAtomEnd \
01298   setWord(initFilePosn, size); \
01299   return size; \
01300 }
01301 
01302 addAtom(ftyp);
01303   size += add4ByteString("mp42");
01304   size += addWord(0x00000000);
01305   size += add4ByteString("mp42");
01306   size += add4ByteString("isom");
01307 addAtomEnd;
01308 
01309 addAtom(moov);
01310   size += addAtom_mvhd();
01311 
01312   if (fGenerateMP4Format) {
01313     size += addAtom_iods();
01314   }
01315 
01316   // Add a 'trak' atom for each subsession:
01317   // (For some unknown reason, QuickTime Player (5.0 at least)
01318   //  doesn't display the movie correctly unless the audio track
01319   //  (if present) appears before the video track.  So ensure this here.)
01320   MediaSubsessionIterator iter(fInputSession);
01321   MediaSubsession* subsession;
01322   while ((subsession = iter.next()) != NULL) {
01323     fCurrentIOState = (SubsessionIOState*)(subsession->miscPtr);
01324     if (fCurrentIOState == NULL) continue;
01325     if (strcmp(subsession->mediumName(), "audio") != 0) continue;
01326 
01327     size += addAtom_trak();
01328 
01329     if (fCurrentIOState->hasHintTrack()) {
01330       // This track has a hint track; output it also:
01331       fCurrentIOState = fCurrentIOState->fHintTrackForUs;
01332       size += addAtom_trak();
01333     }
01334   }
01335   iter.reset();
01336   while ((subsession = iter.next()) != NULL) {
01337     fCurrentIOState = (SubsessionIOState*)(subsession->miscPtr);
01338     if (fCurrentIOState == NULL) continue;
01339     if (strcmp(subsession->mediumName(), "audio") == 0) continue;
01340 
01341     size += addAtom_trak();
01342 
01343     if (fCurrentIOState->hasHintTrack()) {
01344       // This track has a hint track; output it also:
01345       fCurrentIOState = fCurrentIOState->fHintTrackForUs;
01346       size += addAtom_trak();
01347     }
01348   }
01349 addAtomEnd;
01350 
01351 addAtom(mvhd);
01352   size += addWord(0x00000000); // Version + Flags
01353   size += addWord(fAppleCreationTime); // Creation time
01354   size += addWord(fAppleCreationTime); // Modification time
01355 
01356   // For the "Time scale" field, use the largest RTP timestamp frequency
01357   // that we saw in any of the subsessions.
01358   size += addWord(movieTimeScale()); // Time scale
01359 
01360   unsigned const duration = fMaxTrackDurationM;
01361   fMVHD_durationPosn = TellFile64(fOutFid);
01362   size += addWord(duration); // Duration
01363 
01364   size += addWord(0x00010000); // Preferred rate
01365   size += addWord(0x01000000); // Preferred volume + Reserved[0]
01366   size += addZeroWords(2); // Reserved[1-2]
01367   size += addWord(0x00010000); // matrix top left corner
01368   size += addZeroWords(3); // matrix
01369   size += addWord(0x00010000); // matrix center
01370   size += addZeroWords(3); // matrix
01371   size += addWord(0x40000000); // matrix bottom right corner
01372   size += addZeroWords(6); // various time fields
01373   size += addWord(SubsessionIOState::fCurrentTrackNumber+1);// Next track ID
01374 addAtomEnd;
01375 
01376 addAtom(iods);
01377   size += addWord(0x00000000); // Version + Flags
01378   size += addWord(0x10808080);
01379   size += addWord(0x07004FFF);
01380   size += addWord(0xFF0FFFFF);
01381 addAtomEnd;
01382 
01383 addAtom(trak);
01384   size += addAtom_tkhd();
01385 
01386   // If we're synchronizing the media streams (or are a hint track),
01387   // add an edit list that helps do this:
01388   if (fCurrentIOState->fHeadChunk != NULL
01389       && (fSyncStreams || fCurrentIOState->isHintTrack())) {
01390     size += addAtom_edts();
01391   }
01392 
01393   // If we're generating a hint track, add a 'tref' atom:
01394   if (fCurrentIOState->isHintTrack()) size += addAtom_tref();
01395 
01396   size += addAtom_mdia();
01397 
01398   // If we're generating a hint track, add a 'udta' atom:
01399   if (fCurrentIOState->isHintTrack()) size += addAtom_udta();
01400 addAtomEnd;
01401 
01402 addAtom(tkhd);
01403   if (fCurrentIOState->fQTEnableTrack) {
01404     size += addWord(0x0000000F); // Version +  Flags
01405   } else {
01406     // Disable this track in the movie:
01407     size += addWord(0x00000000); // Version +  Flags
01408   }
01409   size += addWord(fAppleCreationTime); // Creation time
01410   size += addWord(fAppleCreationTime); // Modification time
01411   size += addWord(fCurrentIOState->fTrackID); // Track ID
01412   size += addWord(0x00000000); // Reserved
01413 
01414   unsigned const duration = fCurrentIOState->fQTDurationM; // movie units
01415   fCurrentIOState->fTKHD_durationPosn = TellFile64(fOutFid);
01416   size += addWord(duration); // Duration
01417   size += addZeroWords(3); // Reserved+Layer+Alternate grp
01418   size += addWord(0x01000000); // Volume + Reserved
01419   size += addWord(0x00010000); // matrix top left corner
01420   size += addZeroWords(3); // matrix
01421   size += addWord(0x00010000); // matrix center
01422   size += addZeroWords(3); // matrix
01423   size += addWord(0x40000000); // matrix bottom right corner
01424   if (strcmp(fCurrentIOState->fOurSubsession.mediumName(), "video") == 0) {
01425     size += addWord(fMovieWidth<<16); // Track width
01426     size += addWord(fMovieHeight<<16); // Track height
01427   } else {
01428     size += addZeroWords(2); // not video: leave width and height fields zero
01429   }
01430 addAtomEnd;
01431 
01432 addAtom(edts);
01433   size += addAtom_elst();
01434 addAtomEnd;
01435 
01436 #define addEdit1(duration,trackPosition) do { \
01437       unsigned trackDuration \
01438         = (unsigned) ((2*(duration)*movieTimeScale()+1)/2); \
01439             /* in movie time units */ \
01440       size += addWord(trackDuration); /* Track duration */ \
01441       totalDurationOfEdits += trackDuration; \
01442       size += addWord(trackPosition); /* Media time */ \
01443       size += addWord(0x00010000); /* Media rate (1x) */ \
01444       ++numEdits; \
01445 } while (0)
01446 #define addEdit(duration) addEdit1((duration),editTrackPosition)
01447 #define addEmptyEdit(duration) addEdit1((duration),(~0))
01448 
01449 addAtom(elst);
01450   size += addWord(0x00000000); // Version + Flags
01451 
01452   // Add a dummy "Number of entries" field
01453   // (and remember its position).  We'll fill this field in later:
01454   int64_t numEntriesPosition = TellFile64(fOutFid);
01455   size += addWord(0); // dummy for "Number of entries"
01456   unsigned numEdits = 0;
01457   unsigned totalDurationOfEdits = 0; // in movie time units
01458 
01459   // Run through our chunks, looking at their presentation times.
01460   // From these, figure out the edits that need to be made to keep
01461   // the track media data in sync with the presentation times.
01462 
01463   double const syncThreshold = 0.1; // 100 ms
01464     // don't allow the track to get out of sync by more than this
01465 
01466   struct timeval editStartTime = fFirstDataTime;
01467   unsigned editTrackPosition = 0;
01468   unsigned currentTrackPosition = 0;
01469   double trackDurationOfEdit = 0.0;
01470   unsigned chunkDuration = 0;
01471 
01472   ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
01473   while (chunk != NULL) {
01474     struct timeval const& chunkStartTime = chunk->fPresentationTime;
01475     double movieDurationOfEdit
01476       = (chunkStartTime.tv_sec - editStartTime.tv_sec)
01477       + (chunkStartTime.tv_usec - editStartTime.tv_usec)/1000000.0;
01478     trackDurationOfEdit = (currentTrackPosition-editTrackPosition)
01479       / (double)(fCurrentIOState->fQTTimeScale);
01480 
01481     double outOfSync = movieDurationOfEdit - trackDurationOfEdit;
01482 
01483     if (outOfSync > syncThreshold) {
01484       // The track's data is too short, so end this edit, add a new
01485       // 'empty' edit after it, and start a new edit
01486       // (at the current track posn.):
01487       if (trackDurationOfEdit > 0.0) addEdit(trackDurationOfEdit);
01488       addEmptyEdit(outOfSync);
01489 
01490       editStartTime = chunkStartTime;
01491       editTrackPosition = currentTrackPosition;
01492     } else if (outOfSync < -syncThreshold) {
01493       // The track's data is too long, so end this edit, and start
01494       // a new edit (pointing at the current track posn.):
01495       if (movieDurationOfEdit > 0.0) addEdit(movieDurationOfEdit);
01496 
01497       editStartTime = chunkStartTime;
01498       editTrackPosition = currentTrackPosition;
01499     }
01500 
01501     // Note the duration of this chunk:
01502     unsigned numChannels = fCurrentIOState->fOurSubsession.numChannels();
01503     chunkDuration = chunk->fNumFrames*chunk->fFrameDuration/numChannels;
01504     currentTrackPosition += chunkDuration;
01505 
01506     chunk = chunk->fNextChunk;
01507   }
01508 
01509   // Write out the final edit
01510   trackDurationOfEdit
01511       += (double)chunkDuration/fCurrentIOState->fQTTimeScale;
01512   if (trackDurationOfEdit > 0.0) addEdit(trackDurationOfEdit);
01513 
01514   // Now go back and fill in the "Number of entries" field:
01515   setWord(numEntriesPosition, numEdits);
01516 
01517   // Also, if the sum of all of the edit durations exceeds the
01518   // track duration that we already computed (from sample durations),
01519   // then reset the track duration to this new value:
01520   if (totalDurationOfEdits > fCurrentIOState->fQTDurationM) {
01521     fCurrentIOState->fQTDurationM = totalDurationOfEdits;
01522     setWord(fCurrentIOState->fTKHD_durationPosn, totalDurationOfEdits);
01523 
01524     // Also, check whether the overall movie duration needs to change:
01525     if (totalDurationOfEdits > fMaxTrackDurationM) {
01526       fMaxTrackDurationM = totalDurationOfEdits;
01527       setWord(fMVHD_durationPosn, totalDurationOfEdits);
01528     }
01529 
01530     // Also, convert to track time scale:
01531     double scaleFactor
01532       = fCurrentIOState->fQTTimeScale/(double)movieTimeScale();
01533     fCurrentIOState->fQTDurationT
01534       = (unsigned)(totalDurationOfEdits*scaleFactor);
01535   }
01536 addAtomEnd;
01537 
01538 addAtom(tref);
01539   size += addAtom_hint();
01540 addAtomEnd;
01541 
01542 addAtom(hint);
01543   SubsessionIOState* hintedTrack = fCurrentIOState->fTrackHintedByUs;
01544     // Assert: hintedTrack != NULL
01545   size += addWord(hintedTrack->fTrackID);
01546 addAtomEnd;
01547 
01548 addAtom(mdia);
01549   size += addAtom_mdhd();
01550   size += addAtom_hdlr();
01551   size += addAtom_minf();
01552 addAtomEnd;
01553 
01554 addAtom(mdhd);
01555   size += addWord(0x00000000); // Version + Flags
01556   size += addWord(fAppleCreationTime); // Creation time
01557   size += addWord(fAppleCreationTime); // Modification time
01558 
01559   unsigned const timeScale = fCurrentIOState->fQTTimeScale;
01560   size += addWord(timeScale); // Time scale
01561 
01562   unsigned const duration = fCurrentIOState->fQTDurationT; // track units
01563   size += addWord(duration); // Duration
01564 
01565   size += addWord(0x00000000); // Language+Quality
01566 addAtomEnd;
01567 
01568 addAtom(hdlr);
01569   size += addWord(0x00000000); // Version + Flags
01570   size += add4ByteString("mhlr"); // Component type
01571   size += addWord(fCurrentIOState->fQTcomponentSubtype);
01572     // Component subtype
01573   size += add4ByteString("appl"); // Component manufacturer
01574   size += addWord(0x00000000); // Component flags
01575   size += addWord(0x00000000); // Component flags mask
01576   size += addArbitraryString(fCurrentIOState->fQTcomponentName);
01577     // Component name
01578 addAtomEnd;
01579 
01580 addAtom(minf);
01581   SubsessionIOState::atomCreationFunc mediaInformationAtomCreator
01582     = fCurrentIOState->fQTMediaInformationAtomCreator;
01583   size += (this->*mediaInformationAtomCreator)();
01584   size += addAtom_hdlr2();
01585   size += addAtom_dinf();
01586   size += addAtom_stbl();
01587 addAtomEnd;
01588 
01589 addAtom(smhd);
01590   size += addZeroWords(2); // Version+Flags+Balance+Reserved
01591 addAtomEnd;
01592 
01593 addAtom(vmhd);
01594   size += addWord(0x00000001); // Version + Flags
01595   size += addWord(0x00408000); // Graphics mode + Opcolor[red]
01596   size += addWord(0x80008000); // Opcolor[green} + Opcolor[blue]
01597 addAtomEnd;
01598 
01599 addAtom(gmhd);
01600   size += addAtom_gmin();
01601 addAtomEnd;
01602 
01603 addAtom(gmin);
01604   size += addWord(0x00000000); // Version + Flags
01605   // The following fields probably aren't used for hint tracks, so just
01606   // use values that I've seen in other files:
01607   size += addWord(0x00408000); // Graphics mode + Opcolor (1st 2 bytes)
01608   size += addWord(0x80008000); // Opcolor (last 4 bytes)
01609   size += addWord(0x00000000); // Balance + Reserved
01610 addAtomEnd;
01611 
01612 unsigned QuickTimeFileSink::addAtom_hdlr2() {
01613   int64_t initFilePosn = TellFile64(fOutFid);
01614   unsigned size = addAtomHeader("hdlr");
01615   size += addWord(0x00000000); // Version + Flags
01616   size += add4ByteString("dhlr"); // Component type
01617   size += add4ByteString("alis"); // Component subtype
01618   size += add4ByteString("appl"); // Component manufacturer
01619   size += addZeroWords(2); // Component flags+Component flags mask
01620   size += addArbitraryString("Apple Alias Data Handler"); // Component name
01621 addAtomEnd;
01622 
01623 addAtom(dinf);
01624   size += addAtom_dref();
01625 addAtomEnd;
01626 
01627 addAtom(dref);
01628   size += addWord(0x00000000); // Version + Flags
01629   size += addWord(0x00000001); // Number of entries
01630   size += addAtom_alis();
01631 addAtomEnd;
01632 
01633 addAtom(alis);
01634   size += addWord(0x00000001); // Version + Flags
01635 addAtomEnd;
01636 
01637 addAtom(stbl);
01638   size += addAtom_stsd();
01639   size += addAtom_stts();
01640   if (fCurrentIOState->fQTcomponentSubtype == fourChar('v','i','d','e')) {
01641     size += addAtom_stss(); // only for video streams
01642   }
01643   size += addAtom_stsc();
01644   size += addAtom_stsz();
01645   size += addAtom_co64();
01646 addAtomEnd;
01647 
01648 addAtom(stsd);
01649   size += addWord(0x00000000); // Version+Flags
01650   size += addWord(0x00000001); // Number of entries
01651   SubsessionIOState::atomCreationFunc mediaDataAtomCreator
01652     = fCurrentIOState->fQTMediaDataAtomCreator;
01653   size += (this->*mediaDataAtomCreator)();
01654 addAtomEnd;
01655 
01656 unsigned QuickTimeFileSink::addAtom_genericMedia() {
01657   int64_t initFilePosn = TellFile64(fOutFid);
01658 
01659   // Our source is assumed to be a "QuickTimeGenericRTPSource"
01660   // Use its "sdAtom" state for our contents:
01661   QuickTimeGenericRTPSource* rtpSource = (QuickTimeGenericRTPSource*)
01662     fCurrentIOState->fOurSubsession.rtpSource();
01663   QuickTimeGenericRTPSource::QTState& qtState = rtpSource->qtState;
01664   char const* from = qtState.sdAtom;
01665   unsigned size = qtState.sdAtomSize;
01666   for (unsigned i = 0; i < size; ++i) addByte(from[i]);
01667 addAtomEnd;
01668 
01669 unsigned QuickTimeFileSink::addAtom_soundMediaGeneral() {
01670   int64_t initFilePosn = TellFile64(fOutFid);
01671   unsigned size = addAtomHeader(fCurrentIOState->fQTAudioDataType);
01672 
01673 // General sample description fields:
01674   size += addWord(0x00000000); // Reserved
01675   size += addWord(0x00000001); // Reserved+Data reference index
01676 // Sound sample description fields:
01677   unsigned short const version = fCurrentIOState->fQTSoundSampleVersion;
01678   size += addWord(version<<16); // Version+Revision level
01679   size += addWord(0x00000000); // Vendor
01680   unsigned short numChannels
01681     = (unsigned short)(fCurrentIOState->fOurSubsession.numChannels());
01682   size += addHalfWord(numChannels); // Number of channels
01683   size += addHalfWord(0x0010); // Sample size
01684   //  size += addWord(0x00000000); // Compression ID+Packet size
01685   size += addWord(0xfffe0000); // Compression ID+Packet size #####
01686 
01687   unsigned const sampleRateFixedPoint = fCurrentIOState->fQTTimeScale << 16;
01688   size += addWord(sampleRateFixedPoint); // Sample rate
01689 addAtomEnd;
01690 
01691 unsigned QuickTimeFileSink::addAtom_Qclp() {
01692   // The beginning of this atom looks just like a general Sound Media atom,
01693   // except with a version field of 1:
01694   int64_t initFilePosn = TellFile64(fOutFid);
01695   fCurrentIOState->fQTAudioDataType = "Qclp";
01696   fCurrentIOState->fQTSoundSampleVersion = 1;
01697   unsigned size = addAtom_soundMediaGeneral();
01698 
01699   // Next, add the four fields that are particular to version 1:
01700   // (Later, parameterize these #####)
01701   size += addWord(0x000000a0); // samples per packet
01702   size += addWord(0x00000000); // ???
01703   size += addWord(0x00000000); // ???
01704   size += addWord(0x00000002); // bytes per sample (uncompressed)
01705 
01706   // Other special fields are in a 'wave' atom that follows:
01707   size += addAtom_wave();
01708 addAtomEnd;
01709 
01710 addAtom(wave);
01711   size += addAtom_frma();
01712   if (strcmp(fCurrentIOState->fQTAudioDataType, "Qclp") == 0) {
01713     size += addWord(0x00000014); // ???
01714     size += add4ByteString("Qclp"); // ???
01715     if (fCurrentIOState->fQTBytesPerFrame == 35) {
01716       size += addAtom_Fclp(); // full-rate QCELP
01717     } else {
01718       size += addAtom_Hclp(); // half-rate QCELP
01719     } // what about other QCELP 'rates'??? #####
01720     size += addWord(0x00000008); // ???
01721     size += addWord(0x00000000); // ???
01722     size += addWord(0x00000000); // ???
01723     size += addWord(0x00000008); // ???
01724   } else if (strcmp(fCurrentIOState->fQTAudioDataType, "mp4a") == 0) {
01725     size += addWord(0x0000000c); // ???
01726     size += add4ByteString("mp4a"); // ???
01727     size += addWord(0x00000000); // ???
01728     size += addAtom_esds(); // ESDescriptor
01729     size += addWord(0x00000008); // ???
01730     size += addWord(0x00000000); // ???
01731   }
01732 addAtomEnd;
01733 
01734 addAtom(frma);
01735   size += add4ByteString(fCurrentIOState->fQTAudioDataType); // ???
01736 addAtomEnd;
01737 
01738 addAtom(Fclp);
01739  size += addWord(0x00000000); // ???
01740 addAtomEnd;
01741 
01742 addAtom(Hclp);
01743  size += addWord(0x00000000); // ???
01744 addAtomEnd;
01745 
01746 unsigned QuickTimeFileSink::addAtom_mp4a() {
01747   unsigned size = 0;
01748   // The beginning of this atom looks just like a general Sound Media atom,
01749   // except with a version field of 1:
01750   int64_t initFilePosn = TellFile64(fOutFid);
01751   fCurrentIOState->fQTAudioDataType = "mp4a";
01752 
01753   if (fGenerateMP4Format) {
01754     fCurrentIOState->fQTSoundSampleVersion = 0;
01755     size = addAtom_soundMediaGeneral();
01756     size += addAtom_esds();
01757   } else {
01758     fCurrentIOState->fQTSoundSampleVersion = 1;
01759     size = addAtom_soundMediaGeneral();
01760 
01761     // Next, add the four fields that are particular to version 1:
01762     // (Later, parameterize these #####)
01763     size += addWord(fCurrentIOState->fQTTimeUnitsPerSample);
01764     size += addWord(0x00000001); // ???
01765     size += addWord(0x00000001); // ???
01766     size += addWord(0x00000002); // bytes per sample (uncompressed)
01767 
01768     // Other special fields are in a 'wave' atom that follows:
01769     size += addAtom_wave();
01770   }
01771 addAtomEnd;
01772 
01773 addAtom(esds);
01774   //#####
01775   MediaSubsession& subsession = fCurrentIOState->fOurSubsession;
01776   if (strcmp(subsession.mediumName(), "audio") == 0) {
01777     // MPEG-4 audio
01778     size += addWord(0x00000000); // ???
01779     size += addWord(0x03808080); // ???
01780     size += addWord(0x2a000000); // ???
01781     size += addWord(0x04808080); // ???
01782     size += addWord(0x1c401500); // ???
01783     size += addWord(0x18000000); // ???
01784     size += addWord(0x6d600000); // ???
01785     size += addWord(0x6d600580); // ???
01786     size += addByte(0x80); size += addByte(0x80); // ???
01787   } else if (strcmp(subsession.mediumName(), "video") == 0) {
01788     // MPEG-4 video
01789     size += addWord(0x00000000); // ???
01790     size += addWord(0x03330000); // ???
01791     size += addWord(0x1f042b20); // ???
01792     size += addWord(0x1104fd46); // ???
01793     size += addWord(0x000d4e10); // ???
01794     size += addWord(0x000d4e10); // ???
01795     size += addByte(0x05); // ???
01796   }
01797 
01798   // Add the source's 'config' information:
01799   unsigned configSize;
01800   unsigned char* config
01801     = parseGeneralConfigStr(subsession.fmtp_config(), configSize);
01802   size += addByte(configSize);
01803   for (unsigned i = 0; i < configSize; ++i) {
01804     size += addByte(config[i]);
01805   }
01806   delete[] config;
01807 
01808   if (strcmp(subsession.mediumName(), "audio") == 0) {
01809     // MPEG-4 audio
01810     size += addWord(0x06808080); // ???
01811     size += addHalfWord(0x0102); // ???
01812   } else {
01813     // MPEG-4 video
01814     size += addHalfWord(0x0601); // ???
01815     size += addByte(0x02); // ???
01816   }
01817   //#####
01818 addAtomEnd;
01819 
01820 addAtom(srcq);
01821   //#####
01822   size += addWord(0x00000040); // ???
01823   //#####
01824 addAtomEnd;
01825 
01826 addAtom(h263);
01827 // General sample description fields:
01828   size += addWord(0x00000000); // Reserved
01829   size += addWord(0x00000001); // Reserved+Data reference index
01830 // Video sample description fields:
01831   size += addWord(0x00020001); // Version+Revision level
01832   size += add4ByteString("appl"); // Vendor
01833   size += addWord(0x00000000); // Temporal quality
01834   size += addWord(0x000002fc); // Spatial quality
01835   unsigned const widthAndHeight = (fMovieWidth<<16)|fMovieHeight;
01836   size += addWord(widthAndHeight); // Width+height
01837   size += addWord(0x00480000); // Horizontal resolution
01838   size += addWord(0x00480000); // Vertical resolution
01839   size += addWord(0x00000000); // Data size
01840   size += addWord(0x00010548); // Frame count+Compressor name (start)
01841     // "H.263"
01842   size += addWord(0x2e323633); // Compressor name (continued)
01843   size += addZeroWords(6); // Compressor name (continued - zero)
01844   size += addWord(0x00000018); // Compressor name (final)+Depth
01845   size += addHalfWord(0xffff); // Color table id
01846 addAtomEnd;
01847 
01848 addAtom(avc1);
01849 // General sample description fields:
01850   size += addWord(0x00000000); // Reserved
01851   size += addWord(0x00000001); // Reserved+Data       reference index
01852 // Video sample       description     fields:
01853   size += addWord(0x00000000); // Version+Revision level
01854   size += add4ByteString("appl"); // Vendor
01855   size += addWord(0x00000000); // Temporal quality
01856   size += addWord(0x00000000); // Spatial quality
01857   unsigned const widthAndHeight       = (fMovieWidth<<16)|fMovieHeight;
01858   size += addWord(widthAndHeight); // Width+height
01859   size += addWord(0x00480000); // Horizontal resolution
01860   size += addWord(0x00480000); // Vertical resolution
01861   size += addWord(0x00000000); // Data size
01862   size += addWord(0x00010548); // Frame       count+Compressor name (start)
01863     // "H.264"
01864   size += addWord(0x2e323634); // Compressor name (continued)
01865   size += addZeroWords(6); // Compressor name (continued - zero)
01866   size += addWord(0x00000018); // Compressor name (final)+Depth
01867   size += addHalfWord(0xffff); // Color       table id
01868   size += addAtom_avcC();
01869 addAtomEnd;
01870 
01871 addAtom(avcC);
01872 // Begin by Base-64 decoding the "sprop" parameter sets strings:
01873   char* psets = strDup(fCurrentIOState->fOurSubsession.fmtp_spropparametersets());
01874   if (psets == NULL) return 0;
01875 
01876   size_t comma_pos = strcspn(psets, ",");
01877   psets[comma_pos] = '\0';
01878   char const* sps_b64 = psets;
01879   char const* pps_b64 = &psets[comma_pos+1];
01880   unsigned sps_count;
01881   unsigned char* sps_data = base64Decode(sps_b64, sps_count, false);
01882   unsigned pps_count;
01883   unsigned char* pps_data = base64Decode(pps_b64, pps_count, false);
01884 
01885 // Then add the decoded data:
01886   size += addByte(0x01); // configuration version
01887   size += addByte(sps_data[1]); // profile
01888   size += addByte(sps_data[2]); // profile compat
01889   size += addByte(sps_data[3]); // level
01890   size += addByte(0xff); /* 0b11111100 | lengthsize = 0x11 */
01891   size += addByte(0xe0 | (sps_count > 0 ? 1 : 0) );
01892   if (sps_count > 0) {
01893     size += addHalfWord(sps_count);
01894     for (unsigned i = 0; i < sps_count; i++) {
01895       size += addByte(sps_data[i]);
01896     }
01897   }
01898   size += addByte(pps_count > 0 ? 1 : 0);
01899   if (pps_count > 0) {
01900     size += addHalfWord(pps_count);
01901     for (unsigned i = 0; i < pps_count; i++) {
01902       size += addByte(pps_data[i]);
01903     }
01904   }
01905 
01906 // Finally, delete the data that we allocated:
01907   delete[] pps_data; delete[] sps_data;
01908   delete[] psets;
01909 addAtomEnd;
01910 
01911 addAtom(mp4v);
01912 // General sample description fields:
01913   size += addWord(0x00000000); // Reserved
01914   size += addWord(0x00000001); // Reserved+Data reference index
01915 // Video sample description fields:
01916   size += addWord(0x00020001); // Version+Revision level
01917   size += add4ByteString("appl"); // Vendor
01918   size += addWord(0x00000200); // Temporal quality
01919   size += addWord(0x00000400); // Spatial quality
01920   unsigned const widthAndHeight = (fMovieWidth<<16)|fMovieHeight;
01921   size += addWord(widthAndHeight); // Width+height
01922   size += addWord(0x00480000); // Horizontal resolution
01923   size += addWord(0x00480000); // Vertical resolution
01924   size += addWord(0x00000000); // Data size
01925   size += addWord(0x00010c4d); // Frame count+Compressor name (start)
01926     // "MPEG-4 Video"
01927   size += addWord(0x5045472d); // Compressor name (continued)
01928   size += addWord(0x34205669); // Compressor name (continued)
01929   size += addWord(0x64656f00); // Compressor name (continued)
01930   size += addZeroWords(4); // Compressor name (continued - zero)
01931   size += addWord(0x00000018); // Compressor name (final)+Depth
01932   size += addHalfWord(0xffff); // Color table id
01933   size += addAtom_esds(); // ESDescriptor
01934   size += addWord(0x00000000); // ???
01935 addAtomEnd;
01936 
01937 unsigned QuickTimeFileSink::addAtom_rtp() {
01938   int64_t initFilePosn = TellFile64(fOutFid);
01939   unsigned size = addAtomHeader("rtp ");
01940 
01941   size += addWord(0x00000000); // Reserved (1st 4 bytes)
01942   size += addWord(0x00000001); // Reserved (last 2 bytes) + Data ref index
01943   size += addWord(0x00010001); // Hint track version + Last compat htv
01944   size += addWord(1450); // Max packet size
01945 
01946   size += addAtom_tims();
01947 addAtomEnd;
01948 
01949 addAtom(tims);
01950   size += addWord(fCurrentIOState->fOurSubsession.rtpTimestampFrequency());
01951 addAtomEnd;
01952 
01953 addAtom(stts); // Time-to-Sample
01954   size += addWord(0x00000000); // Version+flags
01955 
01956   // First, add a dummy "Number of entries" field
01957   // (and remember its position).  We'll fill this field in later:
01958   int64_t numEntriesPosition = TellFile64(fOutFid);
01959   size += addWord(0); // dummy for "Number of entries"
01960 
01961   // Then, run through the chunk descriptors, and enter the entries
01962   // in this (compressed) Time-to-Sample table:
01963   unsigned numEntries = 0, numSamplesSoFar = 0;
01964   unsigned prevSampleDuration = 0;
01965   unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
01966   ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
01967   while (chunk != NULL) {
01968     unsigned const sampleDuration = chunk->fFrameDuration/samplesPerFrame;
01969     if (sampleDuration != prevSampleDuration) {
01970       // This chunk will start a new table entry,
01971       // so write out the old one (if any):
01972       if (chunk != fCurrentIOState->fHeadChunk) {
01973         ++numEntries;
01974         size += addWord(numSamplesSoFar); // Sample count
01975         size += addWord(prevSampleDuration); // Sample duration
01976         numSamplesSoFar = 0;
01977       }
01978     }
01979 
01980     unsigned const numSamples = chunk->fNumFrames*samplesPerFrame;
01981     numSamplesSoFar += numSamples;
01982     prevSampleDuration = sampleDuration;
01983     chunk = chunk->fNextChunk;
01984   }
01985 
01986   // Then, write out the last entry:
01987   ++numEntries;
01988   size += addWord(numSamplesSoFar); // Sample count
01989   size += addWord(prevSampleDuration); // Sample duration
01990 
01991   // Now go back and fill in the "Number of entries" field:
01992   setWord(numEntriesPosition, numEntries);
01993 addAtomEnd;
01994 
01995 addAtom(stss); // Sync-Sample
01996   size += addWord(0x00000000); // Version+flags
01997 
01998   // First, add a dummy "Number of entries" field
01999   // (and remember its position).  We'll fill this field in later:
02000   int64_t numEntriesPosition = TellFile64(fOutFid);
02001   size += addWord(0); // dummy for "Number of entries"
02002 
02003   unsigned numEntries = 0, numSamplesSoFar = 0;
02004   if (fCurrentIOState->fHeadSyncFrame != NULL) {
02005     SyncFrame* currentSyncFrame = fCurrentIOState->fHeadSyncFrame;
02006     while(currentSyncFrame != NULL) {
02007       ++numEntries;
02008       size += addWord(currentSyncFrame->sfFrameNum);
02009       currentSyncFrame = currentSyncFrame->nextSyncFrame;
02010     }
02011   } else {
02012     // Then, run through the chunk descriptors, counting up the total nuber of samples:
02013     unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
02014     ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02015     while (chunk != NULL) {
02016       unsigned const numSamples = chunk->fNumFrames*samplesPerFrame;
02017       numSamplesSoFar += numSamples;
02018       chunk = chunk->fNextChunk;
02019     }
02020   
02021     // Then, write out the sample numbers that we deem correspond to 'sync samples':
02022     unsigned i;
02023     for (i = 0; i < numSamplesSoFar; i += 12) {
02024       // For an explanation of the constant "12", see http://lists.live555.com/pipermail/live-devel/2009-July/010969.html
02025       // (Perhaps we should really try to keep track of which 'samples' ('frames' for video) really are 'key frames'?)
02026       size += addWord(i+1);
02027       ++numEntries;
02028     }
02029   
02030     // Then, write out the last entry (if we haven't already done so):
02031     if (i != (numSamplesSoFar - 1)) {
02032       size += addWord(numSamplesSoFar);
02033       ++numEntries;
02034     }
02035   }
02036 
02037   // Now go back and fill in the "Number of entries" field:
02038   setWord(numEntriesPosition, numEntries);
02039 addAtomEnd;
02040 
02041 addAtom(stsc); // Sample-to-Chunk
02042   size += addWord(0x00000000); // Version+flags
02043 
02044   // First, add a dummy "Number of entries" field
02045   // (and remember its position).  We'll fill this field in later:
02046   int64_t numEntriesPosition = TellFile64(fOutFid);
02047   size += addWord(0); // dummy for "Number of entries"
02048 
02049   // Then, run through the chunk descriptors, and enter the entries
02050   // in this (compressed) Sample-to-Chunk table:
02051   unsigned numEntries = 0, chunkNumber = 0;
02052   unsigned prevSamplesPerChunk = ~0;
02053   unsigned const samplesPerFrame = fCurrentIOState->fQTSamplesPerFrame;
02054   ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02055   while (chunk != NULL) {
02056     ++chunkNumber;
02057     unsigned const samplesPerChunk = chunk->fNumFrames*samplesPerFrame;
02058     if (samplesPerChunk != prevSamplesPerChunk) {
02059       // This chunk will be a new table entry:
02060       ++numEntries;
02061       size += addWord(chunkNumber); // Chunk number
02062       size += addWord(samplesPerChunk); // Samples per chunk
02063       size += addWord(0x00000001); // Sample description ID
02064 
02065       prevSamplesPerChunk = samplesPerChunk;
02066     }
02067     chunk = chunk->fNextChunk;
02068   }
02069 
02070   // Now go back and fill in the "Number of entries" field:
02071   setWord(numEntriesPosition, numEntries);
02072 addAtomEnd;
02073 
02074 addAtom(stsz); // Sample Size
02075   size += addWord(0x00000000); // Version+flags
02076 
02077   // Begin by checking whether our chunks all have the same
02078   // 'bytes-per-sample'.  This determines whether this atom's table
02079   // has just a single entry, or multiple entries.
02080   Boolean haveSingleEntryTable = True;
02081   double firstBPS = 0.0;
02082   ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02083   while (chunk != NULL) {
02084     double bps
02085       = (double)(chunk->fFrameSize)/(fCurrentIOState->fQTSamplesPerFrame);
02086     if (bps < 1.0) {
02087       // I don't think a multiple-entry table would make sense in
02088       // this case, so assume a single entry table ??? #####
02089       break;
02090     }
02091 
02092     if (firstBPS == 0.0) {
02093       firstBPS = bps;
02094     } else if (bps != firstBPS) {
02095       haveSingleEntryTable = False;
02096       break;
02097     }
02098 
02099     chunk = chunk->fNextChunk;
02100   }
02101 
02102   unsigned sampleSize;
02103   if (haveSingleEntryTable) {
02104     if (fCurrentIOState->isHintTrack()
02105         && fCurrentIOState->fHeadChunk != NULL) {
02106       sampleSize = fCurrentIOState->fHeadChunk->fFrameSize
02107                       / fCurrentIOState->fQTSamplesPerFrame;
02108     } else {
02109       // The following doesn't seem right, but seems to do the right thing:
02110       sampleSize = fCurrentIOState->fQTTimeUnitsPerSample; //???
02111     }
02112   } else {
02113     sampleSize = 0; // indicates a multiple-entry table
02114   }
02115   size += addWord(sampleSize); // Sample size
02116   unsigned const totNumSamples = fCurrentIOState->fQTTotNumSamples;
02117   size += addWord(totNumSamples); // Number of entries
02118 
02119   if (!haveSingleEntryTable) {
02120     // Multiple-entry table:
02121     // Run through the chunk descriptors, entering the sample sizes:
02122     ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02123     while (chunk != NULL) {
02124       unsigned numSamples
02125         = chunk->fNumFrames*(fCurrentIOState->fQTSamplesPerFrame);
02126       unsigned sampleSize
02127         = chunk->fFrameSize/(fCurrentIOState->fQTSamplesPerFrame);
02128       for (unsigned i = 0; i < numSamples; ++i) {
02129         size += addWord(sampleSize);
02130       }
02131 
02132       chunk = chunk->fNextChunk;
02133     }
02134   }
02135 addAtomEnd;
02136 
02137 addAtom(co64); // Chunk Offset
02138   size += addWord(0x00000000); // Version+flags
02139   size += addWord(fCurrentIOState->fNumChunks); // Number of entries
02140 
02141   // Run through the chunk descriptors, entering the file offsets:
02142   ChunkDescriptor* chunk = fCurrentIOState->fHeadChunk;
02143   while (chunk != NULL) {
02144     size += addWord64(chunk->fOffsetInFile);
02145 
02146     chunk = chunk->fNextChunk;
02147   }
02148 addAtomEnd;
02149 
02150 addAtom(udta);
02151   size += addAtom_name();
02152   size += addAtom_hnti();
02153   size += addAtom_hinf();
02154 addAtomEnd;
02155 
02156 addAtom(name);
02157   char description[100];
02158   sprintf(description, "Hinted %s track",
02159           fCurrentIOState->fOurSubsession.mediumName());
02160   size += addArbitraryString(description, False); // name of object
02161 addAtomEnd;
02162 
02163 addAtom(hnti);
02164   size += addAtom_sdp();
02165 addAtomEnd;
02166 
02167 unsigned QuickTimeFileSink::addAtom_sdp() {
02168   int64_t initFilePosn = TellFile64(fOutFid);
02169   unsigned size = addAtomHeader("sdp ");
02170 
02171   // Add this subsession's SDP lines:
02172   char const* sdpLines = fCurrentIOState->fOurSubsession.savedSDPLines();
02173   // We need to change any "a=control:trackID=" values to be this
02174   // track's actual track id:
02175   char* newSDPLines = new char[strlen(sdpLines)+100/*overkill*/];
02176   char const* searchStr = "a=control:trackid=";
02177   Boolean foundSearchString = False;
02178   char const *p1, *p2, *p3;
02179   for (p1 = sdpLines; *p1 != '\0'; ++p1) {
02180     for (p2 = p1,p3 = searchStr; tolower(*p2) == *p3; ++p2,++p3) {}
02181     if (*p3 == '\0') {
02182       // We found the end of the search string, at p2.
02183       int beforeTrackNumPosn = p2-sdpLines;
02184       // Look for the subsequent track number, and skip over it:
02185       int trackNumLength;
02186       if (sscanf(p2, " %*d%n", &trackNumLength) < 0) break;
02187       int afterTrackNumPosn = beforeTrackNumPosn + trackNumLength;
02188 
02189       // Replace the old track number with the correct one:
02190       int i;
02191       for (i = 0; i < beforeTrackNumPosn; ++i) newSDPLines[i] = sdpLines[i];
02192       sprintf(&newSDPLines[i], "%d", fCurrentIOState->fTrackID);
02193       i = afterTrackNumPosn;
02194       int j = i + strlen(&newSDPLines[i]);
02195       while (1) {
02196         if ((newSDPLines[j] = sdpLines[i]) == '\0') break;
02197         ++i; ++j;
02198       }
02199 
02200       foundSearchString = True;
02201       break;
02202     }
02203   }
02204 
02205   if (!foundSearchString) {
02206     // Because we didn't find a "a=control:trackID=<trackId>" line,
02207     // add one of our own:
02208     sprintf(newSDPLines, "%s%s%d\r\n",
02209             sdpLines, searchStr, fCurrentIOState->fTrackID);
02210   }
02211 
02212   size += addArbitraryString(newSDPLines, False);
02213   delete[] newSDPLines;
02214 addAtomEnd;
02215 
02216 addAtom(hinf);
02217   size += addAtom_totl();
02218   size += addAtom_npck();
02219   size += addAtom_tpay();
02220   size += addAtom_trpy();
02221   size += addAtom_nump();
02222   size += addAtom_tpyl();
02223   // Is 'maxr' required? #####
02224   size += addAtom_dmed();
02225   size += addAtom_dimm();
02226   size += addAtom_drep();
02227   size += addAtom_tmin();
02228   size += addAtom_tmax();
02229   size += addAtom_pmax();
02230   size += addAtom_dmax();
02231   size += addAtom_payt();
02232 addAtomEnd;
02233 
02234 addAtom(totl);
02235  size += addWord(fCurrentIOState->fHINF.trpy.lo);
02236 addAtomEnd;
02237 
02238 addAtom(npck);
02239  size += addWord(fCurrentIOState->fHINF.nump.lo);
02240 addAtomEnd;
02241 
02242 addAtom(tpay);
02243  size += addWord(fCurrentIOState->fHINF.tpyl.lo);
02244 addAtomEnd;
02245 
02246 addAtom(trpy);
02247  size += addWord(fCurrentIOState->fHINF.trpy.hi);
02248  size += addWord(fCurrentIOState->fHINF.trpy.lo);
02249 addAtomEnd;
02250 
02251 addAtom(nump);
02252  size += addWord(fCurrentIOState->fHINF.nump.hi);
02253  size += addWord(fCurrentIOState->fHINF.nump.lo);
02254 addAtomEnd;
02255 
02256 addAtom(tpyl);
02257  size += addWord(fCurrentIOState->fHINF.tpyl.hi);
02258  size += addWord(fCurrentIOState->fHINF.tpyl.lo);
02259 addAtomEnd;
02260 
02261 addAtom(dmed);
02262  size += addWord(fCurrentIOState->fHINF.dmed.hi);
02263  size += addWord(fCurrentIOState->fHINF.dmed.lo);
02264 addAtomEnd;
02265 
02266 addAtom(dimm);
02267  size += addWord(fCurrentIOState->fHINF.dimm.hi);
02268  size += addWord(fCurrentIOState->fHINF.dimm.lo);
02269 addAtomEnd;
02270 
02271 addAtom(drep);
02272  size += addWord(0);
02273  size += addWord(0);
02274 addAtomEnd;
02275 
02276 addAtom(tmin);
02277  size += addWord(0);
02278 addAtomEnd;
02279 
02280 addAtom(tmax);
02281  size += addWord(0);
02282 addAtomEnd;
02283 
02284 addAtom(pmax);
02285  size += addWord(fCurrentIOState->fHINF.pmax);
02286 addAtomEnd;
02287 
02288 addAtom(dmax);
02289  size += addWord(fCurrentIOState->fHINF.dmax);
02290 addAtomEnd;
02291 
02292 addAtom(payt);
02293   MediaSubsession& ourSubsession = fCurrentIOState->fOurSubsession;
02294   RTPSource* rtpSource = ourSubsession.rtpSource();
02295   size += addWord(rtpSource->rtpPayloadFormat());
02296 
02297   // Also, add a 'rtpmap' string: <mime-subtype>/<rtp-frequency>
02298   unsigned rtpmapStringLength = strlen(ourSubsession.codecName()) + 20;
02299   char* rtpmapString = new char[rtpmapStringLength];
02300   sprintf(rtpmapString, "%s/%d",
02301           ourSubsession.codecName(), rtpSource->timestampFrequency());
02302   size += addArbitraryString(rtpmapString);
02303   delete[] rtpmapString;
02304 addAtomEnd;
02305 
02306 // A dummy atom (with name "????"):
02307 unsigned QuickTimeFileSink::addAtom_dummy() {
02308     int64_t initFilePosn = TellFile64(fOutFid);
02309     unsigned size = addAtomHeader("????");
02310 addAtomEnd;

Generated on Thu May 17 07:11:47 2012 for live by  doxygen 1.5.2