|
MythTV
0.26-pre
|
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;
1.7.6.1