| 1 | /********** | 
|---|
| 2 | This library is free software; you can redistribute it and/or modify it under | 
|---|
| 3 | the terms of the GNU Lesser General Public License as published by the | 
|---|
| 4 | Free Software Foundation; either version 3 of the License, or (at your | 
|---|
| 5 | option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) | 
|---|
| 6 |  | 
|---|
| 7 | This library is distributed in the hope that it will be useful, but WITHOUT | 
|---|
| 8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|---|
| 9 | FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for | 
|---|
| 10 | more details. | 
|---|
| 11 |  | 
|---|
| 12 | You should have received a copy of the GNU Lesser General Public License | 
|---|
| 13 | along with this library; if not, write to the Free Software Foundation, Inc., | 
|---|
| 14 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA | 
|---|
| 15 | **********/ | 
|---|
| 16 | // "liveMedia" | 
|---|
| 17 | // Copyright (c) 1996-2020 Live Networks, Inc.  All rights reserved. | 
|---|
| 18 | // A sink that generates an AVI file from a composite media session | 
|---|
| 19 | // Implementation | 
|---|
| 20 |  | 
|---|
| 21 | #include "AVIFileSink.hh" | 
|---|
| 22 | #include "InputFile.hh" | 
|---|
| 23 | #include "OutputFile.hh" | 
|---|
| 24 | #include "GroupsockHelper.hh" | 
|---|
| 25 |  | 
|---|
| 26 | #define fourChar(x,y,z,w) ( ((w)<<24)|((z)<<16)|((y)<<8)|(x) )/*little-endian*/ | 
|---|
| 27 |  | 
|---|
| 28 | #define AVIIF_LIST		0x00000001 | 
|---|
| 29 | #define AVIIF_KEYFRAME		0x00000010 | 
|---|
| 30 | #define AVIIF_NO_TIME		0x00000100 | 
|---|
| 31 | #define AVIIF_COMPRESSOR	0x0FFF0000 | 
|---|
| 32 |  | 
|---|
| 33 | ////////// AVISubsessionIOState /////////// | 
|---|
| 34 | // A structure used to represent the I/O state of each input 'subsession': | 
|---|
| 35 |  | 
|---|
| 36 | class SubsessionBuffer { | 
|---|
| 37 | public: | 
|---|
| 38 | SubsessionBuffer(unsigned bufferSize) | 
|---|
| 39 | : fBufferSize(bufferSize) { | 
|---|
| 40 | reset(); | 
|---|
| 41 | fData = new unsigned char[bufferSize]; | 
|---|
| 42 | } | 
|---|
| 43 | virtual ~SubsessionBuffer() { delete[] fData; } | 
|---|
| 44 | void reset() { fBytesInUse = 0; } | 
|---|
| 45 | void addBytes(unsigned numBytes) { fBytesInUse += numBytes; } | 
|---|
| 46 |  | 
|---|
| 47 | unsigned char* dataStart() { return &fData[0]; } | 
|---|
| 48 | unsigned char* dataEnd() { return &fData[fBytesInUse]; } | 
|---|
| 49 | unsigned bytesInUse() const { return fBytesInUse; } | 
|---|
| 50 | unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; } | 
|---|
| 51 |  | 
|---|
| 52 | void setPresentationTime(struct timeval const& presentationTime) { | 
|---|
| 53 | fPresentationTime = presentationTime; | 
|---|
| 54 | } | 
|---|
| 55 | struct timeval const& presentationTime() const {return fPresentationTime;} | 
|---|
| 56 |  | 
|---|
| 57 | private: | 
|---|
| 58 | unsigned fBufferSize; | 
|---|
| 59 | struct timeval fPresentationTime; | 
|---|
| 60 | unsigned char* fData; | 
|---|
| 61 | unsigned fBytesInUse; | 
|---|
| 62 | }; | 
|---|
| 63 |  | 
|---|
| 64 | class AVISubsessionIOState { | 
|---|
| 65 | public: | 
|---|
| 66 | AVISubsessionIOState(AVIFileSink& sink, MediaSubsession& subsession); | 
|---|
| 67 | virtual ~AVISubsessionIOState(); | 
|---|
| 68 |  | 
|---|
| 69 | void setAVIstate(unsigned subsessionIndex); | 
|---|
| 70 | void setFinalAVIstate(); | 
|---|
| 71 |  | 
|---|
| 72 | void afterGettingFrame(unsigned packetDataSize, | 
|---|
| 73 | struct timeval presentationTime); | 
|---|
| 74 | void onSourceClosure(); | 
|---|
| 75 |  | 
|---|
| 76 | UsageEnvironment& envir() const { return fOurSink.envir(); } | 
|---|
| 77 |  | 
|---|
| 78 | public: | 
|---|
| 79 | SubsessionBuffer *fBuffer, *fPrevBuffer; | 
|---|
| 80 | AVIFileSink& fOurSink; | 
|---|
| 81 | MediaSubsession& fOurSubsession; | 
|---|
| 82 |  | 
|---|
| 83 | unsigned short fLastPacketRTPSeqNum; | 
|---|
| 84 | Boolean fOurSourceIsActive; | 
|---|
| 85 | struct timeval fPrevPresentationTime; | 
|---|
| 86 | unsigned fMaxBytesPerSecond; | 
|---|
| 87 | Boolean fIsVideo, fIsAudio, fIsByteSwappedAudio; | 
|---|
| 88 | unsigned fAVISubsessionTag; | 
|---|
| 89 | unsigned fAVICodecHandlerType; | 
|---|
| 90 | unsigned fAVISamplingFrequency; // for audio | 
|---|
| 91 | u_int16_t fWAVCodecTag; // for audio | 
|---|
| 92 | unsigned fAVIScale; | 
|---|
| 93 | unsigned fAVIRate; | 
|---|
| 94 | unsigned fAVISize; | 
|---|
| 95 | unsigned fNumFrames; | 
|---|
| 96 | unsigned fSTRHFrameCountPosition; | 
|---|
| 97 |  | 
|---|
| 98 | private: | 
|---|
| 99 | void useFrame(SubsessionBuffer& buffer); | 
|---|
| 100 | }; | 
|---|
| 101 |  | 
|---|
| 102 |  | 
|---|
| 103 | ///////// AVIIndexRecord definition & implementation ////////// | 
|---|
| 104 |  | 
|---|
| 105 | class AVIIndexRecord { | 
|---|
| 106 | public: | 
|---|
| 107 | AVIIndexRecord(unsigned chunkId, unsigned flags, unsigned offset, unsigned size) | 
|---|
| 108 | : fNext(NULL), fChunkId(chunkId), fFlags(flags), fOffset(offset), fSize(size) { | 
|---|
| 109 | } | 
|---|
| 110 |  | 
|---|
| 111 | AVIIndexRecord*& next() { return fNext; } | 
|---|
| 112 | unsigned chunkId() const { return fChunkId; } | 
|---|
| 113 | unsigned flags() const { return fFlags; } | 
|---|
| 114 | unsigned offset() const { return fOffset; } | 
|---|
| 115 | unsigned size() const { return fSize; } | 
|---|
| 116 |  | 
|---|
| 117 | private: | 
|---|
| 118 | AVIIndexRecord* fNext; | 
|---|
| 119 | unsigned fChunkId; | 
|---|
| 120 | unsigned fFlags; | 
|---|
| 121 | unsigned fOffset; | 
|---|
| 122 | unsigned fSize; | 
|---|
| 123 | }; | 
|---|
| 124 |  | 
|---|
| 125 |  | 
|---|
| 126 | ////////// AVIFileSink implementation ////////// | 
|---|
| 127 |  | 
|---|
| 128 | AVIFileSink::AVIFileSink(UsageEnvironment& env, | 
|---|
| 129 | MediaSession& inputSession, | 
|---|
| 130 | char const* outputFileName, | 
|---|
| 131 | unsigned bufferSize, | 
|---|
| 132 | unsigned short movieWidth, unsigned short movieHeight, | 
|---|
| 133 | unsigned movieFPS, Boolean packetLossCompensate) | 
|---|
| 134 | : Medium(env), fInputSession(inputSession), | 
|---|
| 135 | fIndexRecordsHead(NULL), fIndexRecordsTail(NULL), fNumIndexRecords(0), | 
|---|
| 136 | fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate), | 
|---|
| 137 | fAreCurrentlyBeingPlayed(False), fNumSubsessions(0), fNumBytesWritten(0), | 
|---|
| 138 | fHaveCompletedOutputFile(False), | 
|---|
| 139 | fMovieWidth(movieWidth), fMovieHeight(movieHeight), fMovieFPS(movieFPS) { | 
|---|
| 140 | fOutFid = OpenOutputFile(env, outputFileName); | 
|---|
| 141 | if (fOutFid == NULL) return; | 
|---|
| 142 |  | 
|---|
| 143 | // Set up I/O state for each input subsession: | 
|---|
| 144 | MediaSubsessionIterator iter(fInputSession); | 
|---|
| 145 | MediaSubsession* subsession; | 
|---|
| 146 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 147 | // Ignore subsessions without a data source: | 
|---|
| 148 | FramedSource* subsessionSource = subsession->readSource(); | 
|---|
| 149 | if (subsessionSource == NULL) continue; | 
|---|
| 150 |  | 
|---|
| 151 | // If "subsession's" SDP description specified screen dimension | 
|---|
| 152 | // or frame rate parameters, then use these. | 
|---|
| 153 | if (subsession->videoWidth() != 0) { | 
|---|
| 154 | fMovieWidth = subsession->videoWidth(); | 
|---|
| 155 | } | 
|---|
| 156 | if (subsession->videoHeight() != 0) { | 
|---|
| 157 | fMovieHeight = subsession->videoHeight(); | 
|---|
| 158 | } | 
|---|
| 159 | if (subsession->videoFPS() != 0) { | 
|---|
| 160 | fMovieFPS = subsession->videoFPS(); | 
|---|
| 161 | } | 
|---|
| 162 |  | 
|---|
| 163 | AVISubsessionIOState* ioState | 
|---|
| 164 | = new AVISubsessionIOState(*this, *subsession); | 
|---|
| 165 | subsession->miscPtr = (void*)ioState; | 
|---|
| 166 |  | 
|---|
| 167 | // Also set a 'BYE' handler for this subsession's RTCP instance: | 
|---|
| 168 | if (subsession->rtcpInstance() != NULL) { | 
|---|
| 169 | subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState); | 
|---|
| 170 | } | 
|---|
| 171 |  | 
|---|
| 172 | ++fNumSubsessions; | 
|---|
| 173 | } | 
|---|
| 174 |  | 
|---|
| 175 | // Begin by writing an AVI header: | 
|---|
| 176 | addFileHeader_AVI(); | 
|---|
| 177 | } | 
|---|
| 178 |  | 
|---|
| 179 | AVIFileSink::~AVIFileSink() { | 
|---|
| 180 | completeOutputFile(); | 
|---|
| 181 |  | 
|---|
| 182 | // Then, stop streaming and delete each active "AVISubsessionIOState": | 
|---|
| 183 | MediaSubsessionIterator iter(fInputSession); | 
|---|
| 184 | MediaSubsession* subsession; | 
|---|
| 185 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 186 | if (subsession->readSource() != NULL) subsession->readSource()->stopGettingFrames(); | 
|---|
| 187 |  | 
|---|
| 188 | AVISubsessionIOState* ioState | 
|---|
| 189 | = (AVISubsessionIOState*)(subsession->miscPtr); | 
|---|
| 190 | if (ioState == NULL) continue; | 
|---|
| 191 |  | 
|---|
| 192 | delete ioState; | 
|---|
| 193 | } | 
|---|
| 194 |  | 
|---|
| 195 | // Then, delete the index records: | 
|---|
| 196 | AVIIndexRecord* cur = fIndexRecordsHead; | 
|---|
| 197 | while (cur != NULL) { | 
|---|
| 198 | AVIIndexRecord* next = cur->next(); | 
|---|
| 199 | delete cur; | 
|---|
| 200 | cur = next; | 
|---|
| 201 | } | 
|---|
| 202 |  | 
|---|
| 203 | // Finally, close our output file: | 
|---|
| 204 | CloseOutputFile(fOutFid); | 
|---|
| 205 | } | 
|---|
| 206 |  | 
|---|
| 207 | AVIFileSink* AVIFileSink | 
|---|
| 208 | ::createNew(UsageEnvironment& env, MediaSession& inputSession, | 
|---|
| 209 | char const* outputFileName, | 
|---|
| 210 | unsigned bufferSize, | 
|---|
| 211 | unsigned short movieWidth, unsigned short movieHeight, | 
|---|
| 212 | unsigned movieFPS, Boolean packetLossCompensate) { | 
|---|
| 213 | AVIFileSink* newSink = | 
|---|
| 214 | new AVIFileSink(env, inputSession, outputFileName, bufferSize, | 
|---|
| 215 | movieWidth, movieHeight, movieFPS, packetLossCompensate); | 
|---|
| 216 | if (newSink == NULL || newSink->fOutFid == NULL) { | 
|---|
| 217 | Medium::close(newSink); | 
|---|
| 218 | return NULL; | 
|---|
| 219 | } | 
|---|
| 220 |  | 
|---|
| 221 | return newSink; | 
|---|
| 222 | } | 
|---|
| 223 |  | 
|---|
| 224 | Boolean AVIFileSink::startPlaying(afterPlayingFunc* afterFunc, | 
|---|
| 225 | void* afterClientData) { | 
|---|
| 226 | // Make sure we're not already being played: | 
|---|
| 227 | if (fAreCurrentlyBeingPlayed) { | 
|---|
| 228 | envir().setResultMsg( "This sink has already been played"); | 
|---|
| 229 | return False; | 
|---|
| 230 | } | 
|---|
| 231 |  | 
|---|
| 232 | fAreCurrentlyBeingPlayed = True; | 
|---|
| 233 | fAfterFunc = afterFunc; | 
|---|
| 234 | fAfterClientData = afterClientData; | 
|---|
| 235 |  | 
|---|
| 236 | return continuePlaying(); | 
|---|
| 237 | } | 
|---|
| 238 |  | 
|---|
| 239 | Boolean AVIFileSink::continuePlaying() { | 
|---|
| 240 | // Run through each of our input session's 'subsessions', | 
|---|
| 241 | // asking for a frame from each one: | 
|---|
| 242 | Boolean haveActiveSubsessions = False; | 
|---|
| 243 | MediaSubsessionIterator iter(fInputSession); | 
|---|
| 244 | MediaSubsession* subsession; | 
|---|
| 245 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 246 | FramedSource* subsessionSource = subsession->readSource(); | 
|---|
| 247 | if (subsessionSource == NULL) continue; | 
|---|
| 248 |  | 
|---|
| 249 | if (subsessionSource->isCurrentlyAwaitingData()) continue; | 
|---|
| 250 |  | 
|---|
| 251 | AVISubsessionIOState* ioState | 
|---|
| 252 | = (AVISubsessionIOState*)(subsession->miscPtr); | 
|---|
| 253 | if (ioState == NULL) continue; | 
|---|
| 254 |  | 
|---|
| 255 | haveActiveSubsessions = True; | 
|---|
| 256 | unsigned char* toPtr = ioState->fBuffer->dataEnd(); | 
|---|
| 257 | unsigned toSize = ioState->fBuffer->bytesAvailable(); | 
|---|
| 258 | subsessionSource->getNextFrame(toPtr, toSize, | 
|---|
| 259 | afterGettingFrame, ioState, | 
|---|
| 260 | onSourceClosure, ioState); | 
|---|
| 261 | } | 
|---|
| 262 | if (!haveActiveSubsessions) { | 
|---|
| 263 | envir().setResultMsg( "No subsessions are currently active"); | 
|---|
| 264 | return False; | 
|---|
| 265 | } | 
|---|
| 266 |  | 
|---|
| 267 | return True; | 
|---|
| 268 | } | 
|---|
| 269 |  | 
|---|
| 270 | void AVIFileSink | 
|---|
| 271 | ::afterGettingFrame(void* clientData, unsigned packetDataSize, | 
|---|
| 272 | unsigned numTruncatedBytes, | 
|---|
| 273 | struct timeval presentationTime, | 
|---|
| 274 | unsigned /*durationInMicroseconds*/) { | 
|---|
| 275 | AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData; | 
|---|
| 276 | if (numTruncatedBytes > 0) { | 
|---|
| 277 | ioState->envir() << "AVIFileSink::afterGettingFrame(): The input frame data was too large for our buffer.  " | 
|---|
| 278 | << numTruncatedBytes | 
|---|
| 279 | << " bytes of trailing data was dropped!  Correct this by increasing the \"bufferSize\" parameter in the \"createNew()\" call.\n"; | 
|---|
| 280 | } | 
|---|
| 281 | ioState->afterGettingFrame(packetDataSize, presentationTime); | 
|---|
| 282 | } | 
|---|
| 283 |  | 
|---|
| 284 | void AVIFileSink::onSourceClosure(void* clientData) { | 
|---|
| 285 | AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData; | 
|---|
| 286 | ioState->onSourceClosure(); | 
|---|
| 287 | } | 
|---|
| 288 |  | 
|---|
| 289 | void AVIFileSink::onSourceClosure1() { | 
|---|
| 290 | // Check whether *all* of the subsession sources have closed. | 
|---|
| 291 | // If not, do nothing for now: | 
|---|
| 292 | MediaSubsessionIterator iter(fInputSession); | 
|---|
| 293 | MediaSubsession* subsession; | 
|---|
| 294 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 295 | AVISubsessionIOState* ioState | 
|---|
| 296 | = (AVISubsessionIOState*)(subsession->miscPtr); | 
|---|
| 297 | if (ioState == NULL) continue; | 
|---|
| 298 |  | 
|---|
| 299 | if (ioState->fOurSourceIsActive) return; // this source hasn't closed | 
|---|
| 300 | } | 
|---|
| 301 |  | 
|---|
| 302 | completeOutputFile(); | 
|---|
| 303 |  | 
|---|
| 304 | // Call our specified 'after' function: | 
|---|
| 305 | if (fAfterFunc != NULL) { | 
|---|
| 306 | (*fAfterFunc)(fAfterClientData); | 
|---|
| 307 | } | 
|---|
| 308 | } | 
|---|
| 309 |  | 
|---|
| 310 | void AVIFileSink::onRTCPBye(void* clientData) { | 
|---|
| 311 | AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData; | 
|---|
| 312 |  | 
|---|
| 313 | struct timeval timeNow; | 
|---|
| 314 | gettimeofday(&timeNow, NULL); | 
|---|
| 315 | unsigned secsDiff | 
|---|
| 316 | = timeNow.tv_sec - ioState->fOurSink.fStartTime.tv_sec; | 
|---|
| 317 |  | 
|---|
| 318 | MediaSubsession& subsession = ioState->fOurSubsession; | 
|---|
| 319 | ioState->envir() << "Received RTCP \"BYE\" on \"" | 
|---|
| 320 | << subsession.mediumName() | 
|---|
| 321 | << "/"<< subsession.codecName() | 
|---|
| 322 | << "\" subsession (after " | 
|---|
| 323 | << secsDiff << " seconds)\n"; | 
|---|
| 324 |  | 
|---|
| 325 | // Handle the reception of a RTCP "BYE" as if the source had closed: | 
|---|
| 326 | ioState->onSourceClosure(); | 
|---|
| 327 | } | 
|---|
| 328 |  | 
|---|
| 329 | void AVIFileSink::addIndexRecord(AVIIndexRecord* newIndexRecord) { | 
|---|
| 330 | if (fIndexRecordsHead == NULL) { | 
|---|
| 331 | fIndexRecordsHead = newIndexRecord; | 
|---|
| 332 | } else { | 
|---|
| 333 | fIndexRecordsTail->next() = newIndexRecord; | 
|---|
| 334 | } | 
|---|
| 335 | fIndexRecordsTail = newIndexRecord; | 
|---|
| 336 | ++fNumIndexRecords; | 
|---|
| 337 | } | 
|---|
| 338 |  | 
|---|
| 339 | void AVIFileSink::completeOutputFile() { | 
|---|
| 340 | if (fHaveCompletedOutputFile || fOutFid == NULL) return; | 
|---|
| 341 |  | 
|---|
| 342 | // Update various AVI 'size' fields to take account of the codec data that | 
|---|
| 343 | // we've now written to the file: | 
|---|
| 344 | unsigned maxBytesPerSecond = 0; | 
|---|
| 345 | unsigned numVideoFrames = 0; | 
|---|
| 346 | unsigned numAudioFrames = 0; | 
|---|
| 347 |  | 
|---|
| 348 | //// Subsession-specific fields: | 
|---|
| 349 | MediaSubsessionIterator iter(fInputSession); | 
|---|
| 350 | MediaSubsession* subsession; | 
|---|
| 351 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 352 | AVISubsessionIOState* ioState | 
|---|
| 353 | = (AVISubsessionIOState*)(subsession->miscPtr); | 
|---|
| 354 | if (ioState == NULL) continue; | 
|---|
| 355 |  | 
|---|
| 356 | maxBytesPerSecond += ioState->fMaxBytesPerSecond; | 
|---|
| 357 |  | 
|---|
| 358 | setWord(ioState->fSTRHFrameCountPosition, ioState->fNumFrames); | 
|---|
| 359 | if (ioState->fIsVideo) numVideoFrames = ioState->fNumFrames; | 
|---|
| 360 | else if (ioState->fIsAudio) numAudioFrames = ioState->fNumFrames; | 
|---|
| 361 | } | 
|---|
| 362 |  | 
|---|
| 363 | //// Global fields: | 
|---|
| 364 | add4ByteString( "idx1"); | 
|---|
| 365 | addWord(fNumIndexRecords*4*4); // the size of all of the index records, which come next: | 
|---|
| 366 | for (AVIIndexRecord* indexRecord = fIndexRecordsHead; indexRecord != NULL; indexRecord = indexRecord->next()) { | 
|---|
| 367 | addWord(indexRecord->chunkId()); | 
|---|
| 368 | addWord(indexRecord->flags()); | 
|---|
| 369 | addWord(indexRecord->offset()); | 
|---|
| 370 | addWord(indexRecord->size()); | 
|---|
| 371 | } | 
|---|
| 372 |  | 
|---|
| 373 | fRIFFSizeValue += fNumBytesWritten + fNumIndexRecords*4*4 - 4; | 
|---|
| 374 | setWord(fRIFFSizePosition, fRIFFSizeValue); | 
|---|
| 375 |  | 
|---|
| 376 | setWord(fAVIHMaxBytesPerSecondPosition, maxBytesPerSecond); | 
|---|
| 377 | setWord(fAVIHFrameCountPosition, | 
|---|
| 378 | numVideoFrames > 0 ? numVideoFrames : numAudioFrames); | 
|---|
| 379 |  | 
|---|
| 380 | fMoviSizeValue += fNumBytesWritten; | 
|---|
| 381 | setWord(fMoviSizePosition, fMoviSizeValue); | 
|---|
| 382 |  | 
|---|
| 383 | // We're done: | 
|---|
| 384 | fHaveCompletedOutputFile = True; | 
|---|
| 385 | } | 
|---|
| 386 |  | 
|---|
| 387 |  | 
|---|
| 388 | ////////// AVISubsessionIOState implementation /////////// | 
|---|
| 389 |  | 
|---|
| 390 | AVISubsessionIOState::AVISubsessionIOState(AVIFileSink& sink, | 
|---|
| 391 | MediaSubsession& subsession) | 
|---|
| 392 | : fOurSink(sink), fOurSubsession(subsession), | 
|---|
| 393 | fMaxBytesPerSecond(0), fIsVideo(False), fIsAudio(False), fIsByteSwappedAudio(False), fNumFrames(0) { | 
|---|
| 394 | fBuffer = new SubsessionBuffer(fOurSink.fBufferSize); | 
|---|
| 395 | fPrevBuffer = sink.fPacketLossCompensate | 
|---|
| 396 | ? new SubsessionBuffer(fOurSink.fBufferSize) : NULL; | 
|---|
| 397 |  | 
|---|
| 398 | FramedSource* subsessionSource = subsession.readSource(); | 
|---|
| 399 | fOurSourceIsActive = subsessionSource != NULL; | 
|---|
| 400 |  | 
|---|
| 401 | fPrevPresentationTime.tv_sec = 0; | 
|---|
| 402 | fPrevPresentationTime.tv_usec = 0; | 
|---|
| 403 | } | 
|---|
| 404 |  | 
|---|
| 405 | AVISubsessionIOState::~AVISubsessionIOState() { | 
|---|
| 406 | delete fBuffer; delete fPrevBuffer; | 
|---|
| 407 | } | 
|---|
| 408 |  | 
|---|
| 409 | void AVISubsessionIOState::setAVIstate(unsigned subsessionIndex) { | 
|---|
| 410 | fIsVideo = strcmp(fOurSubsession.mediumName(), "video") == 0; | 
|---|
| 411 | fIsAudio = strcmp(fOurSubsession.mediumName(), "audio") == 0; | 
|---|
| 412 |  | 
|---|
| 413 | if (fIsVideo) { | 
|---|
| 414 | fAVISubsessionTag | 
|---|
| 415 | = fourChar('0'+subsessionIndex/10,'0'+subsessionIndex%10,'d','c'); | 
|---|
| 416 | if (strcmp(fOurSubsession.codecName(), "JPEG") == 0) { | 
|---|
| 417 | fAVICodecHandlerType = fourChar('m','j','p','g'); | 
|---|
| 418 | } else if (strcmp(fOurSubsession.codecName(), "MP4V-ES") == 0) { | 
|---|
| 419 | fAVICodecHandlerType = fourChar('D','I','V','X'); | 
|---|
| 420 | } else if (strcmp(fOurSubsession.codecName(), "MPV") == 0) { | 
|---|
| 421 | fAVICodecHandlerType = fourChar('m','p','g','1'); // what about MPEG-2? | 
|---|
| 422 | } else if (strcmp(fOurSubsession.codecName(), "H263-1998") == 0 || | 
|---|
| 423 | strcmp(fOurSubsession.codecName(), "H263-2000") == 0) { | 
|---|
| 424 | fAVICodecHandlerType = fourChar('H','2','6','3'); | 
|---|
| 425 | } else if (strcmp(fOurSubsession.codecName(), "H264") == 0) { | 
|---|
| 426 | fAVICodecHandlerType = fourChar('H','2','6','4'); | 
|---|
| 427 | } else { | 
|---|
| 428 | fAVICodecHandlerType = fourChar('?','?','?','?'); | 
|---|
| 429 | } | 
|---|
| 430 | fAVIScale = 1; // ??? ##### | 
|---|
| 431 | fAVIRate = fOurSink.fMovieFPS; // ??? ##### | 
|---|
| 432 | fAVISize = fOurSink.fMovieWidth*fOurSink.fMovieHeight*3; // ??? ##### | 
|---|
| 433 | } else if (fIsAudio) { | 
|---|
| 434 | fIsByteSwappedAudio = False; // by default | 
|---|
| 435 | fAVISubsessionTag | 
|---|
| 436 | = fourChar('0'+subsessionIndex/10,'0'+subsessionIndex%10,'w','b'); | 
|---|
| 437 | fAVICodecHandlerType = 1; // ??? #### | 
|---|
| 438 | unsigned numChannels = fOurSubsession.numChannels(); | 
|---|
| 439 | fAVISamplingFrequency = fOurSubsession.rtpTimestampFrequency(); // default | 
|---|
| 440 | if (strcmp(fOurSubsession.codecName(), "L16") == 0) { | 
|---|
| 441 | fIsByteSwappedAudio = True; // need to byte-swap data before writing it | 
|---|
| 442 | fWAVCodecTag = 0x0001; | 
|---|
| 443 | fAVIScale = fAVISize = 2*numChannels; // 2 bytes/sample | 
|---|
| 444 | fAVIRate = fAVISize*fAVISamplingFrequency; | 
|---|
| 445 | } else if (strcmp(fOurSubsession.codecName(), "L8") == 0) { | 
|---|
| 446 | fWAVCodecTag = 0x0001; | 
|---|
| 447 | fAVIScale = fAVISize = numChannels; // 1 byte/sample | 
|---|
| 448 | fAVIRate = fAVISize*fAVISamplingFrequency; | 
|---|
| 449 | } else if (strcmp(fOurSubsession.codecName(), "PCMA") == 0) { | 
|---|
| 450 | fWAVCodecTag = 0x0006; | 
|---|
| 451 | fAVIScale = fAVISize = numChannels; // 1 byte/sample | 
|---|
| 452 | fAVIRate = fAVISize*fAVISamplingFrequency; | 
|---|
| 453 | } else if (strcmp(fOurSubsession.codecName(), "PCMU") == 0) { | 
|---|
| 454 | fWAVCodecTag = 0x0007; | 
|---|
| 455 | fAVIScale = fAVISize = numChannels; // 1 byte/sample | 
|---|
| 456 | fAVIRate = fAVISize*fAVISamplingFrequency; | 
|---|
| 457 | } else if (strcmp(fOurSubsession.codecName(), "MPA") == 0) { | 
|---|
| 458 | fWAVCodecTag = 0x0050; | 
|---|
| 459 | fAVIScale = fAVISize = 1; | 
|---|
| 460 | fAVIRate = 0; // ??? ##### | 
|---|
| 461 | } else { | 
|---|
| 462 | fWAVCodecTag = 0x0001; // ??? ##### | 
|---|
| 463 | fAVIScale = fAVISize = 1; | 
|---|
| 464 | fAVIRate = 0; // ??? ##### | 
|---|
| 465 | } | 
|---|
| 466 | } else { // unknown medium | 
|---|
| 467 | fAVISubsessionTag | 
|---|
| 468 | = fourChar('0'+subsessionIndex/10,'0'+subsessionIndex%10,'?','?'); | 
|---|
| 469 | fAVICodecHandlerType = 0; | 
|---|
| 470 | fAVIScale = fAVISize = 1; | 
|---|
| 471 | fAVIRate = 0; // ??? ##### | 
|---|
| 472 | } | 
|---|
| 473 | } | 
|---|
| 474 |  | 
|---|
| 475 | void AVISubsessionIOState::afterGettingFrame(unsigned packetDataSize, | 
|---|
| 476 | struct timeval presentationTime) { | 
|---|
| 477 | // Begin by checking whether there was a gap in the RTP stream. | 
|---|
| 478 | // If so, try to compensate for this (if desired): | 
|---|
| 479 | unsigned short rtpSeqNum | 
|---|
| 480 | = fOurSubsession.rtpSource()->curPacketRTPSeqNum(); | 
|---|
| 481 | if (fOurSink.fPacketLossCompensate && fPrevBuffer->bytesInUse() > 0) { | 
|---|
| 482 | short seqNumGap = rtpSeqNum - fLastPacketRTPSeqNum; | 
|---|
| 483 | for (short i = 1; i < seqNumGap; ++i) { | 
|---|
| 484 | // Insert a copy of the previous frame, to compensate for the loss: | 
|---|
| 485 | useFrame(*fPrevBuffer); | 
|---|
| 486 | } | 
|---|
| 487 | } | 
|---|
| 488 | fLastPacketRTPSeqNum = rtpSeqNum; | 
|---|
| 489 |  | 
|---|
| 490 | // Now, continue working with the frame that we just got | 
|---|
| 491 | if (fBuffer->bytesInUse() == 0) { | 
|---|
| 492 | fBuffer->setPresentationTime(presentationTime); | 
|---|
| 493 | } | 
|---|
| 494 | fBuffer->addBytes(packetDataSize); | 
|---|
| 495 |  | 
|---|
| 496 | useFrame(*fBuffer); | 
|---|
| 497 | if (fOurSink.fPacketLossCompensate) { | 
|---|
| 498 | // Save this frame, in case we need it for recovery: | 
|---|
| 499 | SubsessionBuffer* tmp = fPrevBuffer; // assert: != NULL | 
|---|
| 500 | fPrevBuffer = fBuffer; | 
|---|
| 501 | fBuffer = tmp; | 
|---|
| 502 | } | 
|---|
| 503 | fBuffer->reset(); // for the next input | 
|---|
| 504 |  | 
|---|
| 505 | // Now, try getting more frames: | 
|---|
| 506 | fOurSink.continuePlaying(); | 
|---|
| 507 | } | 
|---|
| 508 |  | 
|---|
| 509 | void AVISubsessionIOState::useFrame(SubsessionBuffer& buffer) { | 
|---|
| 510 | unsigned char* const frameSource = buffer.dataStart(); | 
|---|
| 511 | unsigned const frameSize = buffer.bytesInUse(); | 
|---|
| 512 | struct timeval const& presentationTime = buffer.presentationTime(); | 
|---|
| 513 | if (fPrevPresentationTime.tv_usec != 0||fPrevPresentationTime.tv_sec != 0) { | 
|---|
| 514 | int uSecondsDiff | 
|---|
| 515 | = (presentationTime.tv_sec - fPrevPresentationTime.tv_sec)*1000000 | 
|---|
| 516 | + (presentationTime.tv_usec - fPrevPresentationTime.tv_usec); | 
|---|
| 517 | if (uSecondsDiff > 0) { | 
|---|
| 518 | unsigned bytesPerSecond = (unsigned)((frameSize*1000000.0)/uSecondsDiff); | 
|---|
| 519 | if (bytesPerSecond > fMaxBytesPerSecond) { | 
|---|
| 520 | fMaxBytesPerSecond = bytesPerSecond; | 
|---|
| 521 | } | 
|---|
| 522 | } | 
|---|
| 523 | } | 
|---|
| 524 | fPrevPresentationTime = presentationTime; | 
|---|
| 525 |  | 
|---|
| 526 | if (fIsByteSwappedAudio) { | 
|---|
| 527 | // We need to swap the 16-bit audio samples from big-endian | 
|---|
| 528 | // to little-endian order, before writing them to a file: | 
|---|
| 529 | for (unsigned i = 0; i < frameSize; i += 2) { | 
|---|
| 530 | unsigned char tmp = frameSource[i]; | 
|---|
| 531 | frameSource[i] = frameSource[i+1]; | 
|---|
| 532 | frameSource[i+1] = tmp; | 
|---|
| 533 | } | 
|---|
| 534 | } | 
|---|
| 535 |  | 
|---|
| 536 | // Add an index record for this frame: | 
|---|
| 537 | AVIIndexRecord* newIndexRecord | 
|---|
| 538 | = new AVIIndexRecord(fAVISubsessionTag, // chunk id | 
|---|
| 539 | AVIIF_KEYFRAME, // flags | 
|---|
| 540 | 4 + fOurSink.fNumBytesWritten, // offset (note: 4 == 'movi') | 
|---|
| 541 | frameSize); // size | 
|---|
| 542 | fOurSink.addIndexRecord(newIndexRecord); | 
|---|
| 543 |  | 
|---|
| 544 | // Write the data into the file: | 
|---|
| 545 | fOurSink.fNumBytesWritten += fOurSink.addWord(fAVISubsessionTag); | 
|---|
| 546 | if (strcmp(fOurSubsession.codecName(), "H264") == 0) { | 
|---|
| 547 | // Insert a 'start code' (0x00 0x00 0x00 0x01) in front of the frame: | 
|---|
| 548 | fOurSink.fNumBytesWritten += fOurSink.addWord(4+frameSize); | 
|---|
| 549 | fOurSink.fNumBytesWritten += fOurSink.addWord(fourChar(0x00, 0x00, 0x00, 0x01));//add start code | 
|---|
| 550 | } else { | 
|---|
| 551 | fOurSink.fNumBytesWritten += fOurSink.addWord(frameSize); | 
|---|
| 552 | } | 
|---|
| 553 | fwrite(frameSource, 1, frameSize, fOurSink.fOutFid); | 
|---|
| 554 | fOurSink.fNumBytesWritten += frameSize; | 
|---|
| 555 | // Pad to an even length: | 
|---|
| 556 | if (frameSize%2 != 0) fOurSink.fNumBytesWritten += fOurSink.addByte(0); | 
|---|
| 557 |  | 
|---|
| 558 | ++fNumFrames; | 
|---|
| 559 | } | 
|---|
| 560 |  | 
|---|
| 561 | void AVISubsessionIOState::onSourceClosure() { | 
|---|
| 562 | fOurSourceIsActive = False; | 
|---|
| 563 | fOurSink.onSourceClosure1(); | 
|---|
| 564 | } | 
|---|
| 565 |  | 
|---|
| 566 |  | 
|---|
| 567 | ////////// AVI-specific implementation ////////// | 
|---|
| 568 |  | 
|---|
| 569 | unsigned AVIFileSink::addWord(unsigned word) { | 
|---|
| 570 | // Add "word" to the file in little-endian order: | 
|---|
| 571 | addByte(word); addByte(word>>8); | 
|---|
| 572 | addByte(word>>16); addByte(word>>24); | 
|---|
| 573 |  | 
|---|
| 574 | return 4; | 
|---|
| 575 | } | 
|---|
| 576 |  | 
|---|
| 577 | unsigned AVIFileSink::addHalfWord(unsigned short halfWord) { | 
|---|
| 578 | // Add "halfWord" to the file in little-endian order: | 
|---|
| 579 | addByte((unsigned char)halfWord); addByte((unsigned char)(halfWord>>8)); | 
|---|
| 580 |  | 
|---|
| 581 | return 2; | 
|---|
| 582 | } | 
|---|
| 583 |  | 
|---|
| 584 | unsigned AVIFileSink::addZeroWords(unsigned numWords) { | 
|---|
| 585 | for (unsigned i = 0; i < numWords; ++i) { | 
|---|
| 586 | addWord(0); | 
|---|
| 587 | } | 
|---|
| 588 |  | 
|---|
| 589 | return numWords*4; | 
|---|
| 590 | } | 
|---|
| 591 |  | 
|---|
| 592 | unsigned AVIFileSink::add4ByteString(char const* str) { | 
|---|
| 593 | addByte(str[0]); addByte(str[1]); addByte(str[2]); | 
|---|
| 594 | addByte(str[3] == '\0' ? ' ' : str[3]); // e.g., for "AVI " | 
|---|
| 595 |  | 
|---|
| 596 | return 4; | 
|---|
| 597 | } | 
|---|
| 598 |  | 
|---|
| 599 | void AVIFileSink::setWord(unsigned filePosn, unsigned size) { | 
|---|
| 600 | do { | 
|---|
| 601 | if (SeekFile64(fOutFid, filePosn, SEEK_SET) < 0) break; | 
|---|
| 602 | addWord(size); | 
|---|
| 603 | if (SeekFile64(fOutFid, 0, SEEK_END) < 0) break; // go back to where we were | 
|---|
| 604 |  | 
|---|
| 605 | return; | 
|---|
| 606 | } while (0); | 
|---|
| 607 |  | 
|---|
| 608 | // One of the SeekFile64()s failed, probable because we're not a seekable file | 
|---|
| 609 | envir() << "AVIFileSink::setWord(): SeekFile64 failed (err " | 
|---|
| 610 | << envir().getErrno() << ")\n"; | 
|---|
| 611 | } | 
|---|
| 612 |  | 
|---|
| 613 | // Methods for writing particular file headers.  Note the following macros: | 
|---|
| 614 |  | 
|---|
| 615 | #define (tag,name) \ | 
|---|
| 616 | unsigned AVIFileSink::addFileHeader_##name() { \ | 
|---|
| 617 | add4ByteString("" #tag ""); \ | 
|---|
| 618 | unsigned  = (unsigned)TellFile64(fOutFid); addWord(0); \ | 
|---|
| 619 | add4ByteString("" #name ""); \ | 
|---|
| 620 | unsigned ignoredSize = 8;/*don't include size of tag or size fields*/ \ | 
|---|
| 621 | unsigned size = 12 | 
|---|
| 622 |  | 
|---|
| 623 | #define (name) \ | 
|---|
| 624 | unsigned AVIFileSink::addFileHeader_##name() { \ | 
|---|
| 625 | add4ByteString("" #name ""); \ | 
|---|
| 626 | unsigned  = (unsigned)TellFile64(fOutFid); addWord(0); \ | 
|---|
| 627 | unsigned ignoredSize = 8;/*don't include size of name or size fields*/ \ | 
|---|
| 628 | unsigned size = 8 | 
|---|
| 629 |  | 
|---|
| 630 | #define  \ | 
|---|
| 631 | setWord(headerSizePosn, size-ignoredSize); \ | 
|---|
| 632 | return size; \ | 
|---|
| 633 | } | 
|---|
| 634 |  | 
|---|
| 635 | addFileHeader(RIFF,AVI); | 
|---|
| 636 | size += addFileHeader_hdrl(); | 
|---|
| 637 | size += addFileHeader_movi(); | 
|---|
| 638 | fRIFFSizePosition = headerSizePosn; | 
|---|
| 639 | fRIFFSizeValue = size-ignoredSize; | 
|---|
| 640 | addFileHeaderEnd; | 
|---|
| 641 |  | 
|---|
| 642 | addFileHeader(LIST,hdrl); | 
|---|
| 643 | size += addFileHeader_avih(); | 
|---|
| 644 |  | 
|---|
| 645 | // Then, add a "strl" header for each subsession (stream): | 
|---|
| 646 | // (Make the video subsession (if any) come before the audio subsession.) | 
|---|
| 647 | unsigned subsessionCount = 0; | 
|---|
| 648 | MediaSubsessionIterator iter(fInputSession); | 
|---|
| 649 | MediaSubsession* subsession; | 
|---|
| 650 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 651 | fCurrentIOState = (AVISubsessionIOState*)(subsession->miscPtr); | 
|---|
| 652 | if (fCurrentIOState == NULL) continue; | 
|---|
| 653 | if (strcmp(subsession->mediumName(), "video") != 0) continue; | 
|---|
| 654 |  | 
|---|
| 655 | fCurrentIOState->setAVIstate(subsessionCount++); | 
|---|
| 656 | size += addFileHeader_strl(); | 
|---|
| 657 | } | 
|---|
| 658 | iter.reset(); | 
|---|
| 659 | while ((subsession = iter.next()) != NULL) { | 
|---|
| 660 | fCurrentIOState = (AVISubsessionIOState*)(subsession->miscPtr); | 
|---|
| 661 | if (fCurrentIOState == NULL) continue; | 
|---|
| 662 | if (strcmp(subsession->mediumName(), "video") == 0) continue; | 
|---|
| 663 |  | 
|---|
| 664 | fCurrentIOState->setAVIstate(subsessionCount++); | 
|---|
| 665 | size += addFileHeader_strl(); | 
|---|
| 666 | } | 
|---|
| 667 |  | 
|---|
| 668 | // Then add another JUNK entry | 
|---|
| 669 | ++fJunkNumber; | 
|---|
| 670 | size += addFileHeader_JUNK(); | 
|---|
| 671 | addFileHeaderEnd; | 
|---|
| 672 |  | 
|---|
| 673 | #define AVIF_HASINDEX           0x00000010 // Index at end of file? | 
|---|
| 674 | #define AVIF_MUSTUSEINDEX       0x00000020 | 
|---|
| 675 | #define AVIF_ISINTERLEAVED      0x00000100 | 
|---|
| 676 | #define AVIF_TRUSTCKTYPE        0x00000800 // Use CKType to find key frames? | 
|---|
| 677 | #define AVIF_WASCAPTUREFILE     0x00010000 | 
|---|
| 678 | #define AVIF_COPYRIGHTED        0x00020000 | 
|---|
| 679 |  | 
|---|
| 680 | addFileHeader1(avih); | 
|---|
| 681 | unsigned usecPerFrame = fMovieFPS == 0 ? 0 : 1000000/fMovieFPS; | 
|---|
| 682 | size += addWord(usecPerFrame); // dwMicroSecPerFrame | 
|---|
| 683 | fAVIHMaxBytesPerSecondPosition = (unsigned)TellFile64(fOutFid); | 
|---|
| 684 | size += addWord(0); // dwMaxBytesPerSec (fill in later) | 
|---|
| 685 | size += addWord(0); // dwPaddingGranularity | 
|---|
| 686 | size += addWord(AVIF_TRUSTCKTYPE|AVIF_HASINDEX|AVIF_ISINTERLEAVED); // dwFlags | 
|---|
| 687 | fAVIHFrameCountPosition = (unsigned)TellFile64(fOutFid); | 
|---|
| 688 | size += addWord(0); // dwTotalFrames (fill in later) | 
|---|
| 689 | size += addWord(0); // dwInitialFrame | 
|---|
| 690 | size += addWord(fNumSubsessions); // dwStreams | 
|---|
| 691 | size += addWord(fBufferSize); // dwSuggestedBufferSize | 
|---|
| 692 | size += addWord(fMovieWidth); // dwWidth | 
|---|
| 693 | size += addWord(fMovieHeight); // dwHeight | 
|---|
| 694 | size += addZeroWords(4); // dwReserved | 
|---|
| 695 | addFileHeaderEnd; | 
|---|
| 696 |  | 
|---|
| 697 | addFileHeader(LIST,strl); | 
|---|
| 698 | size += addFileHeader_strh(); | 
|---|
| 699 | size += addFileHeader_strf(); | 
|---|
| 700 | fJunkNumber = 0; | 
|---|
| 701 | size += addFileHeader_JUNK(); | 
|---|
| 702 | addFileHeaderEnd; | 
|---|
| 703 |  | 
|---|
| 704 | addFileHeader1(strh); | 
|---|
| 705 | size += add4ByteString(fCurrentIOState->fIsVideo ? "vids": | 
|---|
| 706 | fCurrentIOState->fIsAudio ? "auds": | 
|---|
| 707 | "????"); // fccType | 
|---|
| 708 | size += addWord(fCurrentIOState->fAVICodecHandlerType); // fccHandler | 
|---|
| 709 | size += addWord(0); // dwFlags | 
|---|
| 710 | size += addWord(0); // wPriority + wLanguage | 
|---|
| 711 | size += addWord(0); // dwInitialFrames | 
|---|
| 712 | size += addWord(fCurrentIOState->fAVIScale); // dwScale | 
|---|
| 713 | size += addWord(fCurrentIOState->fAVIRate); // dwRate | 
|---|
| 714 | size += addWord(0); // dwStart | 
|---|
| 715 | fCurrentIOState->fSTRHFrameCountPosition = (unsigned)TellFile64(fOutFid); | 
|---|
| 716 | size += addWord(0); // dwLength (fill in later) | 
|---|
| 717 | size += addWord(fBufferSize); // dwSuggestedBufferSize | 
|---|
| 718 | size += addWord((unsigned)-1); // dwQuality | 
|---|
| 719 | size += addWord(fCurrentIOState->fAVISize); // dwSampleSize | 
|---|
| 720 | size += addWord(0); // rcFrame (start) | 
|---|
| 721 | if (fCurrentIOState->fIsVideo) { | 
|---|
| 722 | size += addHalfWord(fMovieWidth); | 
|---|
| 723 | size += addHalfWord(fMovieHeight); | 
|---|
| 724 | } else { | 
|---|
| 725 | size += addWord(0); | 
|---|
| 726 | } | 
|---|
| 727 | addFileHeaderEnd; | 
|---|
| 728 |  | 
|---|
| 729 | addFileHeader1(strf); | 
|---|
| 730 | if (fCurrentIOState->fIsVideo) { | 
|---|
| 731 | // Add a BITMAPINFO header: | 
|---|
| 732 | unsigned  = 0; | 
|---|
| 733 | size += addWord(10*4 + extraDataSize); // size | 
|---|
| 734 | size += addWord(fMovieWidth); | 
|---|
| 735 | size += addWord(fMovieHeight); | 
|---|
| 736 | size += addHalfWord(1); // planes | 
|---|
| 737 | size += addHalfWord(24); // bits-per-sample ##### | 
|---|
| 738 | size += addWord(fCurrentIOState->fAVICodecHandlerType); // compr. type | 
|---|
| 739 | size += addWord(fCurrentIOState->fAVISize); | 
|---|
| 740 | size += addZeroWords(4); // ??? ##### | 
|---|
| 741 | // Later, add extra data here (if any) ##### | 
|---|
| 742 | } else if (fCurrentIOState->fIsAudio) { | 
|---|
| 743 | // Add a WAVFORMATEX header: | 
|---|
| 744 | size += addHalfWord(fCurrentIOState->fWAVCodecTag); | 
|---|
| 745 | unsigned numChannels = fCurrentIOState->fOurSubsession.numChannels(); | 
|---|
| 746 | size += addHalfWord(numChannels); | 
|---|
| 747 | size += addWord(fCurrentIOState->fAVISamplingFrequency); | 
|---|
| 748 | size += addWord(fCurrentIOState->fAVIRate); // bytes per second | 
|---|
| 749 | size += addHalfWord(fCurrentIOState->fAVISize); // block alignment | 
|---|
| 750 | unsigned bitsPerSample = (fCurrentIOState->fAVISize*8)/numChannels; | 
|---|
| 751 | size += addHalfWord(bitsPerSample); | 
|---|
| 752 | if (strcmp(fCurrentIOState->fOurSubsession.codecName(), "MPA") == 0) { | 
|---|
| 753 | // Assume MPEG layer II audio (not MP3): ##### | 
|---|
| 754 | size += addHalfWord(22); // wav_extra_size | 
|---|
| 755 | size += addHalfWord(2); // fwHeadLayer | 
|---|
| 756 | size += addWord(8*fCurrentIOState->fAVIRate); // dwHeadBitrate ##### | 
|---|
| 757 | size += addHalfWord(numChannels == 2 ? 1: 8); // fwHeadMode | 
|---|
| 758 | size += addHalfWord(0); // fwHeadModeExt | 
|---|
| 759 | size += addHalfWord(1); // wHeadEmphasis | 
|---|
| 760 | size += addHalfWord(16); // fwHeadFlags | 
|---|
| 761 | size += addWord(0); // dwPTSLow | 
|---|
| 762 | size += addWord(0); // dwPTSHigh | 
|---|
| 763 | } | 
|---|
| 764 | } | 
|---|
| 765 | addFileHeaderEnd; | 
|---|
| 766 |  | 
|---|
| 767 | #define AVI_MASTER_INDEX_SIZE   256 | 
|---|
| 768 |  | 
|---|
| 769 | addFileHeader1(JUNK); | 
|---|
| 770 | if (fJunkNumber == 0) { | 
|---|
| 771 | size += addHalfWord(4); // wLongsPerEntry | 
|---|
| 772 | size += addHalfWord(0); // bIndexSubType + bIndexType | 
|---|
| 773 | size += addWord(0); // nEntriesInUse ##### | 
|---|
| 774 | size += addWord(fCurrentIOState->fAVISubsessionTag); // dwChunkId | 
|---|
| 775 | size += addZeroWords(2); // dwReserved | 
|---|
| 776 | size += addZeroWords(AVI_MASTER_INDEX_SIZE*4); | 
|---|
| 777 | } else { | 
|---|
| 778 | size += add4ByteString( "odml"); | 
|---|
| 779 | size += add4ByteString( "dmlh"); | 
|---|
| 780 | unsigned wtfCount = 248; | 
|---|
| 781 | size += addWord(wtfCount); // ??? ##### | 
|---|
| 782 | size += addZeroWords(wtfCount/4); | 
|---|
| 783 | } | 
|---|
| 784 | addFileHeaderEnd; | 
|---|
| 785 |  | 
|---|
| 786 | addFileHeader(LIST,movi); | 
|---|
| 787 | fMoviSizePosition = headerSizePosn; | 
|---|
| 788 | fMoviSizeValue = size-ignoredSize; | 
|---|
| 789 | addFileHeaderEnd; | 
|---|
| 790 |  | 
|---|