liveMedia/AVIFileSink.cpp

Go to the documentation of this file.
00001 /**********
00002 This library is free software; you can redistribute it and/or modify it under
00003 the terms of the GNU Lesser General Public License as published by the
00004 Free Software Foundation; either version 2.1 of the License, or (at your
00005 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
00006 
00007 This library is distributed in the hope that it will be useful, but WITHOUT
00008 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00009 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
00010 more details.
00011 
00012 You should have received a copy of the GNU Lesser General Public License
00013 along with this library; if not, write to the Free Software Foundation, Inc.,
00014 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2008 Live Networks, Inc.  All rights reserved.
00018 // A sink that generates an AVI file from a composite media session
00019 // Implementation
00020 
00021 #include "AVIFileSink.hh"
00022 #include "OutputFile.hh"
00023 #include "GroupsockHelper.hh"
00024 
00025 #define fourChar(x,y,z,w) ( ((w)<<24)|((z)<<16)|((y)<<8)|(x) )/*little-endian*/
00026 
00028 // A structure used to represent the I/O state of each input 'subsession':
00029 
00030 class SubsessionBuffer {
00031 public:
00032   SubsessionBuffer(unsigned bufferSize)
00033     : fBufferSize(bufferSize) {
00034     reset();
00035     fData = new unsigned char[bufferSize];
00036   }
00037   virtual ~SubsessionBuffer() { delete[] fData; }
00038   void reset() { fBytesInUse = 0; }
00039   void addBytes(unsigned numBytes) { fBytesInUse += numBytes; }
00040 
00041   unsigned char* dataStart() { return &fData[0]; }
00042   unsigned char* dataEnd() { return &fData[fBytesInUse]; }
00043   unsigned bytesInUse() const { return fBytesInUse; }
00044   unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; }
00045 
00046   void setPresentationTime(struct timeval const& presentationTime) {
00047     fPresentationTime = presentationTime;
00048   }
00049   struct timeval const& presentationTime() const {return fPresentationTime;}
00050 
00051 private:
00052   unsigned fBufferSize;
00053   struct timeval fPresentationTime;
00054   unsigned char* fData;
00055   unsigned fBytesInUse;
00056 };
00057 
00058 class AVISubsessionIOState {
00059 public:
00060   AVISubsessionIOState(AVIFileSink& sink, MediaSubsession& subsession);
00061   virtual ~AVISubsessionIOState();
00062 
00063   void setAVIstate(unsigned subsessionIndex);
00064   void setFinalAVIstate();
00065 
00066   void afterGettingFrame(unsigned packetDataSize,
00067                          struct timeval presentationTime);
00068   void onSourceClosure();
00069 
00070   UsageEnvironment& envir() const { return fOurSink.envir(); }
00071 
00072 public:
00073   SubsessionBuffer *fBuffer, *fPrevBuffer;
00074   AVIFileSink& fOurSink;
00075   MediaSubsession& fOurSubsession;
00076 
00077   unsigned short fLastPacketRTPSeqNum;
00078   Boolean fOurSourceIsActive;
00079   struct timeval fPrevPresentationTime;
00080   unsigned fMaxBytesPerSecond;
00081   Boolean fIsVideo, fIsAudio, fIsByteSwappedAudio;
00082   unsigned fAVISubsessionTag;
00083   unsigned fAVICodecHandlerType;
00084   unsigned fAVISamplingFrequency; // for audio
00085   u_int16_t fWAVCodecTag; // for audio
00086   unsigned fAVIScale;
00087   unsigned fAVIRate;
00088   unsigned fAVISize;
00089   unsigned fNumFrames;
00090   unsigned fSTRHFrameCountPosition;
00091 
00092 private:
00093   void useFrame(SubsessionBuffer& buffer);
00094 };
00095 
00096 
00098 
00099 AVIFileSink::AVIFileSink(UsageEnvironment& env,
00100                          MediaSession& inputSession,
00101                          char const* outputFileName,
00102                          unsigned bufferSize,
00103                          unsigned short movieWidth, unsigned short movieHeight,
00104                          unsigned movieFPS, Boolean packetLossCompensate)
00105   : Medium(env), fInputSession(inputSession),
00106     fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate),
00107     fAreCurrentlyBeingPlayed(False), fNumSubsessions(0), fNumBytesWritten(0),
00108     fHaveCompletedOutputFile(False),
00109     fMovieWidth(movieWidth), fMovieHeight(movieHeight), fMovieFPS(movieFPS) {
00110   fOutFid = OpenOutputFile(env, outputFileName);
00111   if (fOutFid == NULL) return;
00112 
00113   // Set up I/O state for each input subsession:
00114   MediaSubsessionIterator iter(fInputSession);
00115   MediaSubsession* subsession;
00116   while ((subsession = iter.next()) != NULL) {
00117     // Ignore subsessions without a data source:
00118     FramedSource* subsessionSource = subsession->readSource();
00119     if (subsessionSource == NULL) continue;
00120 
00121     // If "subsession's" SDP description specified screen dimension
00122     // or frame rate parameters, then use these.
00123     if (subsession->videoWidth() != 0) {
00124       fMovieWidth = subsession->videoWidth();
00125     }
00126     if (subsession->videoHeight() != 0) {
00127       fMovieHeight = subsession->videoHeight();
00128     }
00129     if (subsession->videoFPS() != 0) {
00130       fMovieFPS = subsession->videoFPS();
00131     }
00132 
00133     AVISubsessionIOState* ioState
00134       = new AVISubsessionIOState(*this, *subsession);
00135     subsession->miscPtr = (void*)ioState;
00136 
00137     // Also set a 'BYE' handler for this subsession's RTCP instance:
00138     if (subsession->rtcpInstance() != NULL) {
00139       subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState);
00140     }
00141 
00142     ++fNumSubsessions;
00143   }
00144 
00145   // Begin by writing an AVI header:
00146   addFileHeader_AVI();
00147 }
00148 
00149 AVIFileSink::~AVIFileSink() {
00150   completeOutputFile();
00151 
00152   // Then, delete each active "AVISubsessionIOState":
00153   MediaSubsessionIterator iter(fInputSession);
00154   MediaSubsession* subsession;
00155   while ((subsession = iter.next()) != NULL) {
00156     AVISubsessionIOState* ioState
00157       = (AVISubsessionIOState*)(subsession->miscPtr);
00158     if (ioState == NULL) continue;
00159 
00160     delete ioState;
00161   }
00162 
00163   // Finally, close our output file:
00164   CloseOutputFile(fOutFid);
00165 }
00166 
00167 AVIFileSink* AVIFileSink
00168 ::createNew(UsageEnvironment& env, MediaSession& inputSession,
00169             char const* outputFileName,
00170             unsigned bufferSize,
00171             unsigned short movieWidth, unsigned short movieHeight,
00172             unsigned movieFPS, Boolean packetLossCompensate) {
00173   AVIFileSink* newSink =
00174     new AVIFileSink(env, inputSession, outputFileName, bufferSize,
00175                     movieWidth, movieHeight, movieFPS, packetLossCompensate);
00176   if (newSink == NULL || newSink->fOutFid == NULL) {
00177     Medium::close(newSink);
00178     return NULL;
00179   }
00180 
00181   return newSink;
00182 }
00183 
00184 Boolean AVIFileSink::startPlaying(afterPlayingFunc* afterFunc,
00185                                   void* afterClientData) {
00186   // Make sure we're not already being played:
00187   if (fAreCurrentlyBeingPlayed) {
00188     envir().setResultMsg("This sink has already been played");
00189     return False;
00190   }
00191 
00192   fAreCurrentlyBeingPlayed = True;
00193   fAfterFunc = afterFunc;
00194   fAfterClientData = afterClientData;
00195 
00196   return continuePlaying();
00197 }
00198 
00199 Boolean AVIFileSink::continuePlaying() {
00200   // Run through each of our input session's 'subsessions',
00201   // asking for a frame from each one:
00202   Boolean haveActiveSubsessions = False;
00203   MediaSubsessionIterator iter(fInputSession);
00204   MediaSubsession* subsession;
00205   while ((subsession = iter.next()) != NULL) {
00206     FramedSource* subsessionSource = subsession->readSource();
00207     if (subsessionSource == NULL) continue;
00208 
00209     if (subsessionSource->isCurrentlyAwaitingData()) continue;
00210 
00211     AVISubsessionIOState* ioState
00212       = (AVISubsessionIOState*)(subsession->miscPtr);
00213     if (ioState == NULL) continue;
00214 
00215     haveActiveSubsessions = True;
00216     unsigned char* toPtr = ioState->fBuffer->dataEnd();
00217     unsigned toSize = ioState->fBuffer->bytesAvailable();
00218     subsessionSource->getNextFrame(toPtr, toSize,
00219                                    afterGettingFrame, ioState,
00220                                    onSourceClosure, ioState);
00221   }
00222   if (!haveActiveSubsessions) {
00223     envir().setResultMsg("No subsessions are currently active");
00224     return False;
00225   }
00226 
00227   return True;
00228 }
00229 
00230 void AVIFileSink
00231 ::afterGettingFrame(void* clientData, unsigned packetDataSize,
00232                     unsigned /*numTruncatedBytes*/,
00233                     struct timeval presentationTime,
00234                     unsigned /*durationInMicroseconds*/) {
00235   AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData;
00236   ioState->afterGettingFrame(packetDataSize, presentationTime);
00237 }
00238 
00239 void AVIFileSink::onSourceClosure(void* clientData) {
00240   AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData;
00241   ioState->onSourceClosure();
00242 }
00243 
00244 void AVIFileSink::onSourceClosure1() {
00245   // Check whether *all* of the subsession sources have closed.
00246   // If not, do nothing for now:
00247   MediaSubsessionIterator iter(fInputSession);
00248   MediaSubsession* subsession;
00249   while ((subsession = iter.next()) != NULL) {
00250     AVISubsessionIOState* ioState
00251       = (AVISubsessionIOState*)(subsession->miscPtr);
00252     if (ioState == NULL) continue;
00253 
00254     if (ioState->fOurSourceIsActive) return; // this source hasn't closed
00255   }
00256 
00257   completeOutputFile();
00258 
00259   // Call our specified 'after' function:
00260   if (fAfterFunc != NULL) {
00261     (*fAfterFunc)(fAfterClientData);
00262   }
00263 }
00264 
00265 void AVIFileSink::onRTCPBye(void* clientData) {
00266   AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData;
00267 
00268   struct timeval timeNow;
00269   gettimeofday(&timeNow, NULL);
00270   unsigned secsDiff
00271     = timeNow.tv_sec - ioState->fOurSink.fStartTime.tv_sec;
00272 
00273   MediaSubsession& subsession = ioState->fOurSubsession;
00274   ioState->envir() << "Received RTCP \"BYE\" on \""
00275                    << subsession.mediumName()
00276                    << "/" << subsession.codecName()
00277                    << "\" subsession (after "
00278                    << secsDiff << " seconds)\n";
00279 
00280   // Handle the reception of a RTCP "BYE" as if the source had closed:
00281   ioState->onSourceClosure();
00282 }
00283 
00284 void AVIFileSink::completeOutputFile() {
00285   if (fHaveCompletedOutputFile || fOutFid == NULL) return;
00286 
00287   // Update various AVI 'size' fields to take account of the codec data that
00288   // we've now written to the file:
00289   unsigned maxBytesPerSecond = 0;
00290   unsigned numVideoFrames = 0;
00291   unsigned numAudioFrames = 0;
00292 
00294   MediaSubsessionIterator iter(fInputSession);
00295   MediaSubsession* subsession;
00296   while ((subsession = iter.next()) != NULL) {
00297     AVISubsessionIOState* ioState
00298       = (AVISubsessionIOState*)(subsession->miscPtr);
00299     if (ioState == NULL) continue;
00300 
00301     maxBytesPerSecond += ioState->fMaxBytesPerSecond;
00302 
00303     setWord(ioState->fSTRHFrameCountPosition, ioState->fNumFrames);
00304     if (ioState->fIsVideo) numVideoFrames = ioState->fNumFrames;
00305     else if (ioState->fIsAudio) numAudioFrames = ioState->fNumFrames;
00306   }
00307 
00309   fRIFFSizeValue += fNumBytesWritten;
00310   setWord(fRIFFSizePosition, fRIFFSizeValue);
00311 
00312   setWord(fAVIHMaxBytesPerSecondPosition, maxBytesPerSecond);
00313   setWord(fAVIHFrameCountPosition,
00314           numVideoFrames > 0 ? numVideoFrames : numAudioFrames);
00315 
00316   fMoviSizeValue += fNumBytesWritten;
00317   setWord(fMoviSizePosition, fMoviSizeValue);
00318 
00319   // We're done:
00320   fHaveCompletedOutputFile = True;
00321 }
00322 
00323 
00325 
00326 AVISubsessionIOState::AVISubsessionIOState(AVIFileSink& sink,
00327                                      MediaSubsession& subsession)
00328   : fOurSink(sink), fOurSubsession(subsession),
00329     fMaxBytesPerSecond(0), fNumFrames(0) {
00330   fBuffer = new SubsessionBuffer(fOurSink.fBufferSize);
00331   fPrevBuffer = sink.fPacketLossCompensate
00332     ? new SubsessionBuffer(fOurSink.fBufferSize) : NULL;
00333 
00334   FramedSource* subsessionSource = subsession.readSource();
00335   fOurSourceIsActive = subsessionSource != NULL;
00336 
00337   fPrevPresentationTime.tv_sec = 0;
00338   fPrevPresentationTime.tv_usec = 0;
00339 }
00340 
00341 AVISubsessionIOState::~AVISubsessionIOState() {
00342   delete fBuffer; delete fPrevBuffer;
00343 }
00344 
00345 void AVISubsessionIOState::setAVIstate(unsigned subsessionIndex) {
00346   fIsVideo = strcmp(fOurSubsession.mediumName(), "video") == 0;
00347   fIsAudio = strcmp(fOurSubsession.mediumName(), "audio") == 0;
00348 
00349   if (fIsVideo) {
00350     fAVISubsessionTag
00351       = fourChar('0'+subsessionIndex/10,'0'+subsessionIndex%10,'d','c');
00352     if (strcmp(fOurSubsession.codecName(), "JPEG") == 0) {
00353       fAVICodecHandlerType = fourChar('m','j','p','g');
00354     } else if (strcmp(fOurSubsession.codecName(), "MP4V-ES") == 0) {
00355       fAVICodecHandlerType = fourChar('D','I','V','X');
00356     } else if (strcmp(fOurSubsession.codecName(), "MPV") == 0) {
00357       fAVICodecHandlerType = fourChar('m','p','g','1'); // what about MPEG-2?
00358     } else if (strcmp(fOurSubsession.codecName(), "H263-1998") == 0 ||
00359                strcmp(fOurSubsession.codecName(), "H263-2000") == 0) {
00360       fAVICodecHandlerType = fourChar('H','2','6','3');
00361     } else if (strcmp(fOurSubsession.codecName(), "H264") == 0) {
00362       fAVICodecHandlerType = fourChar('H','2','6','4');
00363     } else {
00364       fAVICodecHandlerType = fourChar('?','?','?','?');
00365     }
00366     fAVIScale = 1; // ??? #####
00367     fAVIRate = fOurSink.fMovieFPS; // ??? #####
00368     fAVISize = fOurSink.fMovieWidth*fOurSink.fMovieHeight*3; // ??? #####
00369   } else if (fIsAudio) {
00370     fIsByteSwappedAudio = False; // by default
00371     fAVISubsessionTag
00372       = fourChar('0'+subsessionIndex/10,'0'+subsessionIndex%10,'w','b');
00373     fAVICodecHandlerType = 1; // ??? ####
00374     unsigned numChannels = fOurSubsession.numChannels();
00375     fAVISamplingFrequency = fOurSubsession.rtpTimestampFrequency(); // default
00376     if (strcmp(fOurSubsession.codecName(), "L16") == 0) {
00377       fIsByteSwappedAudio = True; // need to byte-swap data before writing it
00378       fWAVCodecTag = 0x0001;
00379       fAVIScale = fAVISize = 2*numChannels; // 2 bytes/sample
00380       fAVIRate = fAVISize*fAVISamplingFrequency;
00381     } else if (strcmp(fOurSubsession.codecName(), "L8") == 0) {
00382       fWAVCodecTag = 0x0001;
00383       fAVIScale = fAVISize = numChannels; // 1 byte/sample
00384       fAVIRate = fAVISize*fAVISamplingFrequency;
00385     } else if (strcmp(fOurSubsession.codecName(), "PCMA") == 0) {
00386       fWAVCodecTag = 0x0006;
00387       fAVIScale = fAVISize = numChannels; // 1 byte/sample
00388       fAVIRate = fAVISize*fAVISamplingFrequency;
00389     } else if (strcmp(fOurSubsession.codecName(), "PCMU") == 0) {
00390       fWAVCodecTag = 0x0007;
00391       fAVIScale = fAVISize = numChannels; // 1 byte/sample
00392       fAVIRate = fAVISize*fAVISamplingFrequency;
00393     } else if (strcmp(fOurSubsession.codecName(), "MPA") == 0) {
00394       fWAVCodecTag = 0x0050;
00395       fAVIScale = fAVISize = 1;
00396       fAVIRate = 0; // ??? #####
00397     } else {
00398       fWAVCodecTag = 0x0001; // ??? #####
00399       fAVIScale = fAVISize = 1;
00400       fAVIRate = 0; // ??? #####
00401     }
00402   } else { // unknown medium
00403     fAVISubsessionTag
00404       = fourChar('0'+subsessionIndex/10,'0'+subsessionIndex%10,'?','?');
00405     fAVICodecHandlerType = 0;
00406     fAVIScale = fAVISize = 1;
00407     fAVIRate = 0; // ??? #####
00408   }
00409 }
00410 
00411 void AVISubsessionIOState::afterGettingFrame(unsigned packetDataSize,
00412                                           struct timeval presentationTime) {
00413   // Begin by checking whether there was a gap in the RTP stream.
00414   // If so, try to compensate for this (if desired):
00415   unsigned short rtpSeqNum
00416     = fOurSubsession.rtpSource()->curPacketRTPSeqNum();
00417   if (fOurSink.fPacketLossCompensate && fPrevBuffer->bytesInUse() > 0) {
00418     short seqNumGap = rtpSeqNum - fLastPacketRTPSeqNum;
00419     for (short i = 1; i < seqNumGap; ++i) {
00420       // Insert a copy of the previous frame, to compensate for the loss:
00421       useFrame(*fPrevBuffer);
00422     }
00423   }
00424   fLastPacketRTPSeqNum = rtpSeqNum;
00425 
00426   // Now, continue working with the frame that we just got
00427   if (fBuffer->bytesInUse() == 0) {
00428     fBuffer->setPresentationTime(presentationTime);
00429   }
00430   fBuffer->addBytes(packetDataSize);
00431 
00432   useFrame(*fBuffer);
00433   if (fOurSink.fPacketLossCompensate) {
00434     // Save this frame, in case we need it for recovery:
00435     SubsessionBuffer* tmp = fPrevBuffer; // assert: != NULL
00436     fPrevBuffer = fBuffer;
00437     fBuffer = tmp;
00438   }
00439   fBuffer->reset(); // for the next input
00440 
00441   // Now, try getting more frames:
00442   fOurSink.continuePlaying();
00443 }
00444 
00445 void AVISubsessionIOState::useFrame(SubsessionBuffer& buffer) {
00446   unsigned char* const frameSource = buffer.dataStart();
00447   unsigned const frameSize = buffer.bytesInUse();
00448   struct timeval const& presentationTime = buffer.presentationTime();
00449   if (fPrevPresentationTime.tv_usec != 0||fPrevPresentationTime.tv_sec != 0) {
00450     int uSecondsDiff
00451       = (presentationTime.tv_sec - fPrevPresentationTime.tv_sec)*1000000
00452       + (presentationTime.tv_usec - fPrevPresentationTime.tv_usec);
00453     if (uSecondsDiff > 0) {
00454       unsigned bytesPerSecond = (unsigned)((frameSize*1000000.0)/uSecondsDiff);
00455       if (bytesPerSecond > fMaxBytesPerSecond) {
00456         fMaxBytesPerSecond = bytesPerSecond;
00457       }
00458     }
00459   }
00460   fPrevPresentationTime = presentationTime;
00461 
00462   if (fIsByteSwappedAudio) {
00463     // We need to swap the 16-bit audio samples from big-endian
00464     // to little-endian order, before writing them to a file:
00465     for (unsigned i = 0; i < frameSize; i += 2) {
00466       unsigned char tmp = frameSource[i];
00467       frameSource[i] = frameSource[i+1];
00468       frameSource[i+1] = tmp;
00469     }
00470   }
00471 
00472   // Write the data into the file:
00473   fOurSink.fNumBytesWritten += fOurSink.addWord(fAVISubsessionTag);
00474   fOurSink.fNumBytesWritten += fOurSink.addWord(frameSize);
00475   fwrite(frameSource, 1, frameSize, fOurSink.fOutFid);
00476   fOurSink.fNumBytesWritten += frameSize;
00477   // Pad to an even length:
00478   if (frameSize%2 != 0) fOurSink.fNumBytesWritten += fOurSink.addByte(0);
00479 
00480   ++fNumFrames;
00481 }
00482 
00483 void AVISubsessionIOState::onSourceClosure() {
00484   fOurSourceIsActive = False;
00485   fOurSink.onSourceClosure1();
00486 }
00487 
00488 
00490 
00491 unsigned AVIFileSink::addWord(unsigned word) {
00492   // Add "word" to the file in little-endian order:
00493   addByte(word); addByte(word>>8);
00494   addByte(word>>16); addByte(word>>24);
00495 
00496   return 4;
00497 }
00498 
00499 unsigned AVIFileSink::addHalfWord(unsigned short halfWord) {
00500   // Add "halfWord" to the file in little-endian order:
00501   addByte((unsigned char)halfWord); addByte((unsigned char)(halfWord>>8));
00502 
00503   return 2;
00504 }
00505 
00506 unsigned AVIFileSink::addZeroWords(unsigned numWords) {
00507   for (unsigned i = 0; i < numWords; ++i) {
00508     addWord(0);
00509   }
00510 
00511   return numWords*4;
00512 }
00513 
00514 unsigned AVIFileSink::add4ByteString(char const* str) {
00515   addByte(str[0]); addByte(str[1]); addByte(str[2]);
00516   addByte(str[3] == '\0' ? ' ' : str[3]); // e.g., for "AVI "
00517 
00518   return 4;
00519 }
00520 
00521 void AVIFileSink::setWord(unsigned filePosn, unsigned size) {
00522   do {
00523     if (fseek(fOutFid, filePosn, SEEK_SET) < 0) break;
00524     addWord(size);
00525     if (fseek(fOutFid, 0, SEEK_END) < 0) break; // go back to where we were
00526 
00527     return;
00528   } while (0);
00529 
00530   // One of the fseek()s failed, probable because we're not a seekable file
00531   envir() << "AVIFileSink::setWord(): fseek failed (err "
00532           << envir().getErrno() << ")\n";
00533 }
00534 
00535 // Methods for writing particular file headers.  Note the following macros:
00536 
00537 #define addFileHeader(tag,name) \
00538     unsigned AVIFileSink::addFileHeader_##name() { \
00539         add4ByteString("" #tag ""); \
00540         unsigned headerSizePosn = ftell(fOutFid); addWord(0); \
00541         add4ByteString("" #name ""); \
00542         unsigned ignoredSize = 8;/*don't include size of tag or size fields*/ \
00543         unsigned size = 12
00544 
00545 #define addFileHeader1(name) \
00546     unsigned AVIFileSink::addFileHeader_##name() { \
00547         add4ByteString("" #name ""); \
00548         unsigned headerSizePosn = ftell(fOutFid); addWord(0); \
00549         unsigned ignoredSize = 8;/*don't include size of name or size fields*/ \
00550         unsigned size = 8
00551 
00552 #define addFileHeaderEnd \
00553   setWord(headerSizePosn, size-ignoredSize); \
00554   return size; \
00555 }
00556 
00557 addFileHeader(RIFF,AVI);
00558     size += addFileHeader_hdrl();
00559     size += addFileHeader_movi();
00560     fRIFFSizePosition = headerSizePosn;
00561     fRIFFSizeValue = size-ignoredSize;
00562 addFileHeaderEnd;
00563 
00564 addFileHeader(LIST,hdrl);
00565     size += addFileHeader_avih();
00566 
00567     // Then, add a "strl" header for each subsession (stream):
00568     // (Make the video subsession (if any) come before the audio subsession.)
00569     unsigned subsessionCount = 0;
00570     MediaSubsessionIterator iter(fInputSession);
00571     MediaSubsession* subsession;
00572     while ((subsession = iter.next()) != NULL) {
00573       fCurrentIOState = (AVISubsessionIOState*)(subsession->miscPtr);
00574       if (fCurrentIOState == NULL) continue;
00575       if (strcmp(subsession->mediumName(), "video") != 0) continue;
00576 
00577       fCurrentIOState->setAVIstate(subsessionCount++);
00578       size += addFileHeader_strl();
00579     }
00580     iter.reset();
00581     while ((subsession = iter.next()) != NULL) {
00582       fCurrentIOState = (AVISubsessionIOState*)(subsession->miscPtr);
00583       if (fCurrentIOState == NULL) continue;
00584       if (strcmp(subsession->mediumName(), "video") == 0) continue;
00585 
00586       fCurrentIOState->setAVIstate(subsessionCount++);
00587       size += addFileHeader_strl();
00588     }
00589 
00590     // Then add another JUNK entry
00591     ++fJunkNumber;
00592     size += addFileHeader_JUNK();
00593 addFileHeaderEnd;
00594 
00595 #define AVIF_HASINDEX           0x00000010 // Index at end of file?
00596 #define AVIF_MUSTUSEINDEX       0x00000020
00597 #define AVIF_ISINTERLEAVED      0x00000100
00598 #define AVIF_TRUSTCKTYPE        0x00000800 // Use CKType to find key frames?
00599 #define AVIF_WASCAPTUREFILE     0x00010000
00600 #define AVIF_COPYRIGHTED        0x00020000
00601 
00602 addFileHeader1(avih);
00603     unsigned usecPerFrame = fMovieFPS == 0 ? 0 : 1000000/fMovieFPS;
00604     size += addWord(usecPerFrame); // dwMicroSecPerFrame
00605     fAVIHMaxBytesPerSecondPosition = ftell(fOutFid);
00606     size += addWord(0); // dwMaxBytesPerSec (fill in later)
00607     size += addWord(0); // dwPaddingGranularity
00608     size += addWord(AVIF_TRUSTCKTYPE|AVIF_HASINDEX|AVIF_ISINTERLEAVED); // dwFlags
00609     fAVIHFrameCountPosition = ftell(fOutFid);
00610     size += addWord(0); // dwTotalFrames (fill in later)
00611     size += addWord(0); // dwInitialFrame
00612     size += addWord(fNumSubsessions); // dwStreams
00613     size += addWord(fBufferSize); // dwSuggestedBufferSize
00614     size += addWord(fMovieWidth); // dwWidth
00615     size += addWord(fMovieHeight); // dwHeight
00616     size += addZeroWords(4); // dwReserved
00617 addFileHeaderEnd;
00618 
00619 addFileHeader(LIST,strl);
00620     size += addFileHeader_strh();
00621     size += addFileHeader_strf();
00622     fJunkNumber = 0;
00623     size += addFileHeader_JUNK();
00624 addFileHeaderEnd;
00625 
00626 addFileHeader1(strh);
00627     size += add4ByteString(fCurrentIOState->fIsVideo ? "vids" :
00628                            fCurrentIOState->fIsAudio ? "auds" :
00629                            "????"); // fccType
00630     size += addWord(fCurrentIOState->fAVICodecHandlerType); // fccHandler
00631     size += addWord(0); // dwFlags
00632     size += addWord(0); // wPriority + wLanguage
00633     size += addWord(0); // dwInitialFrames
00634     size += addWord(fCurrentIOState->fAVIScale); // dwScale
00635     size += addWord(fCurrentIOState->fAVIRate); // dwRate
00636     size += addWord(0); // dwStart
00637     fCurrentIOState->fSTRHFrameCountPosition = ftell(fOutFid);
00638     size += addWord(0); // dwLength (fill in later)
00639     size += addWord(fBufferSize); // dwSuggestedBufferSize
00640     size += addWord((unsigned)-1); // dwQuality
00641     size += addWord(fCurrentIOState->fAVISize); // dwSampleSize
00642     size += addWord(0); // rcFrame (start)
00643     if (fCurrentIOState->fIsVideo) {
00644         size += addHalfWord(fMovieWidth);
00645         size += addHalfWord(fMovieHeight);
00646     } else {
00647         size += addWord(0);
00648     }
00649 addFileHeaderEnd;
00650 
00651 addFileHeader1(strf);
00652     if (fCurrentIOState->fIsVideo) {
00653       // Add a BITMAPINFO header:
00654       unsigned extraDataSize = 0;
00655       size += addWord(10*4 + extraDataSize); // size
00656       size += addWord(fMovieWidth);
00657       size += addWord(fMovieHeight);
00658       size += addHalfWord(1); // planes
00659       size += addHalfWord(24); // bits-per-sample #####
00660       size += addWord(fCurrentIOState->fAVICodecHandlerType); // compr. type
00661       size += addWord(fCurrentIOState->fAVISize);
00662       size += addZeroWords(4); // ??? #####
00663       // Later, add extra data here (if any) #####
00664     } else if (fCurrentIOState->fIsAudio) {
00665       // Add a WAVFORMATEX header:
00666       size += addHalfWord(fCurrentIOState->fWAVCodecTag);
00667       unsigned numChannels = fCurrentIOState->fOurSubsession.numChannels();
00668       size += addHalfWord(numChannels);
00669       size += addWord(fCurrentIOState->fAVISamplingFrequency);
00670       size += addWord(fCurrentIOState->fAVIRate); // bytes per second
00671       size += addHalfWord(fCurrentIOState->fAVISize); // block alignment
00672       unsigned bitsPerSample = (fCurrentIOState->fAVISize*8)/numChannels;
00673       size += addHalfWord(bitsPerSample);
00674       if (strcmp(fCurrentIOState->fOurSubsession.codecName(), "MPA") == 0) {
00675         // Assume MPEG layer II audio (not MP3): #####
00676         size += addHalfWord(22); // wav_extra_size
00677         size += addHalfWord(2); // fwHeadLayer
00678         size += addWord(8*fCurrentIOState->fAVIRate); // dwHeadBitrate #####
00679         size += addHalfWord(numChannels == 2 ? 1: 8); // fwHeadMode
00680         size += addHalfWord(0); // fwHeadModeExt
00681         size += addHalfWord(1); // wHeadEmphasis
00682         size += addHalfWord(16); // fwHeadFlags
00683         size += addWord(0); // dwPTSLow
00684         size += addWord(0); // dwPTSHigh
00685       }