liveMedia/QuickTimeFileSink.cpp

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

Generated on Thu Feb 2 23:51:31 2012 for live by  doxygen 1.5.2