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

Generated on Thu May 10 20:25:11 2012 for live by  doxygen 1.5.2