1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 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
36class SubsessionBuffer {
37public:
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
57private:
58 unsigned fBufferSize;
59 struct timeval fPresentationTime;
60 unsigned char* fData;
61 unsigned fBytesInUse;
62};
63
64class AVISubsessionIOState {
65public:
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
78public:
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
98private:
99 void useFrame(SubsessionBuffer& buffer);
100};
101
102
103///////// AVIIndexRecord definition & implementation //////////
104
105class AVIIndexRecord {
106public:
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
117private:
118 AVIIndexRecord* fNext;
119 unsigned fChunkId;
120 unsigned fFlags;
121 unsigned fOffset;
122 unsigned fSize;
123};
124
125
126////////// AVIFileSink implementation //////////
127
128AVIFileSink::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
179AVIFileSink::~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
207AVIFileSink* 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
224Boolean 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
239Boolean 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
270void 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
284void AVIFileSink::onSourceClosure(void* clientData) {
285 AVISubsessionIOState* ioState = (AVISubsessionIOState*)clientData;
286 ioState->onSourceClosure();
287}
288
289void 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
310void 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
329void 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
339void 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
390AVISubsessionIOState::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
405AVISubsessionIOState::~AVISubsessionIOState() {
406 delete fBuffer; delete fPrevBuffer;
407}
408
409void 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
475void 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
509void 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
561void AVISubsessionIOState::onSourceClosure() {
562 fOurSourceIsActive = False;
563 fOurSink.onSourceClosure1();
564}
565
566
567////////// AVI-specific implementation //////////
568
569unsigned 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
577unsigned 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
584unsigned AVIFileSink::addZeroWords(unsigned numWords) {
585 for (unsigned i = 0; i < numWords; ++i) {
586 addWord(0);
587 }
588
589 return numWords*4;
590}
591
592unsigned 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
599void 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 addFileHeader(tag,name) \
616 unsigned AVIFileSink::addFileHeader_##name() { \
617 add4ByteString("" #tag ""); \
618 unsigned headerSizePosn = (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 addFileHeader1(name) \
624 unsigned AVIFileSink::addFileHeader_##name() { \
625 add4ByteString("" #name ""); \
626 unsigned headerSizePosn = (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 addFileHeaderEnd \
631 setWord(headerSizePosn, size-ignoredSize); \
632 return size; \
633}
634
635addFileHeader(RIFF,AVI);
636 size += addFileHeader_hdrl();
637 size += addFileHeader_movi();
638 fRIFFSizePosition = headerSizePosn;
639 fRIFFSizeValue = size-ignoredSize;
640addFileHeaderEnd;
641
642addFileHeader(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();
671addFileHeaderEnd;
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
680addFileHeader1(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
695addFileHeaderEnd;
696
697addFileHeader(LIST,strl);
698 size += addFileHeader_strh();
699 size += addFileHeader_strf();
700 fJunkNumber = 0;
701 size += addFileHeader_JUNK();
702addFileHeaderEnd;
703
704addFileHeader1(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 }
727addFileHeaderEnd;
728
729addFileHeader1(strf);
730 if (fCurrentIOState->fIsVideo) {
731 // Add a BITMAPINFO header:
732 unsigned extraDataSize = 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 }
765addFileHeaderEnd;
766
767#define AVI_MASTER_INDEX_SIZE 256
768
769addFileHeader1(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 }
784addFileHeaderEnd;
785
786addFileHeader(LIST,movi);
787 fMoviSizePosition = headerSizePosn;
788 fMoviSizeValue = size-ignoredSize;
789addFileHeaderEnd;
790