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

Generated on Tue Jun 18 13:16:52 2013 for live by  doxygen 1.5.2