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

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