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