00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "QuickTimeFileSink.hh"
00022 #include "QuickTimeGenericRTPSource.hh"
00023 #include "GroupsockHelper.hh"
00024 #include "OutputFile.hh"
00025 #include "H263plusVideoRTPSource.hh"
00026 #include "MPEG4GenericRTPSource.hh"
00027 #include "MPEG4LATMAudioRTPSource.hh"
00028 #include "Base64.hh"
00029
00030 #include <ctype.h>
00031
00032 #define fourChar(x,y,z,w) ( ((x)<<24)|((y)<<16)|((z)<<8)|(w) )
00033
00035
00036
00037 class ChunkDescriptor {
00038 public:
00039 ChunkDescriptor(unsigned offsetInFile, unsigned size,
00040 unsigned frameSize, unsigned frameDuration,
00041 struct timeval presentationTime);
00042 virtual ~ChunkDescriptor();
00043
00044 ChunkDescriptor* extendChunk(unsigned newOffsetInFile, unsigned newSize,
00045 unsigned newFrameSize,
00046 unsigned newFrameDuration,
00047 struct timeval newPresentationTime);
00048
00049 public:
00050 ChunkDescriptor* fNextChunk;
00051 unsigned fOffsetInFile;
00052 unsigned fNumFrames;
00053 unsigned fFrameSize;
00054 unsigned fFrameDuration;
00055 struct timeval fPresentationTime;
00056 };
00057
00058 class SubsessionBuffer {
00059 public:
00060 SubsessionBuffer(unsigned bufferSize)
00061 : fBufferSize(bufferSize) {
00062 reset();
00063 fData = new unsigned char[bufferSize];
00064 }
00065 virtual ~SubsessionBuffer() { delete fData; }
00066 void reset() { fBytesInUse = 0; }
00067 void addBytes(unsigned numBytes) { fBytesInUse += numBytes; }
00068
00069 unsigned char* dataStart() { return &fData[0]; }
00070 unsigned char* dataEnd() { return &fData[fBytesInUse]; }
00071 unsigned bytesInUse() const { return fBytesInUse; }
00072 unsigned bytesAvailable() const { return fBufferSize - fBytesInUse; }
00073
00074 void setPresentationTime(struct timeval const& presentationTime) {
00075 fPresentationTime = presentationTime;
00076 }
00077 struct timeval const& presentationTime() const {return fPresentationTime;}
00078
00079 private:
00080 unsigned fBufferSize;
00081 struct timeval fPresentationTime;
00082 unsigned char* fData;
00083 unsigned fBytesInUse;
00084 };
00085
00086
00087
00088 class Count64 {
00089 public:
00090 Count64() { hi = lo = 0; }
00091
00092 void operator+=(unsigned arg);
00093
00094 unsigned hi, lo;
00095 };
00096
00097 class SubsessionIOState {
00098 public:
00099 SubsessionIOState(QuickTimeFileSink& sink, MediaSubsession& subsession);
00100 virtual ~SubsessionIOState();
00101
00102 Boolean setQTstate();
00103 void setFinalQTstate();
00104
00105 void afterGettingFrame(unsigned packetDataSize,
00106 struct timeval presentationTime);
00107 void onSourceClosure();
00108
00109 Boolean syncOK(struct timeval presentationTime);
00110
00111
00112 static void setHintTrack(SubsessionIOState* hintedTrack,
00113 SubsessionIOState* hintTrack);
00114 Boolean isHintTrack() const { return fTrackHintedByUs != NULL; }
00115 Boolean hasHintTrack() const { return fHintTrackForUs != NULL; }
00116
00117 UsageEnvironment& envir() const { return fOurSink.envir(); }
00118
00119 public:
00120 static unsigned fCurrentTrackNumber;
00121 unsigned fTrackID;
00122 SubsessionIOState* fHintTrackForUs; SubsessionIOState* fTrackHintedByUs;
00123
00124 SubsessionBuffer *fBuffer, *fPrevBuffer;
00125 QuickTimeFileSink& fOurSink;
00126 MediaSubsession& fOurSubsession;
00127
00128 unsigned short fLastPacketRTPSeqNum;
00129 Boolean fOurSourceIsActive;
00130
00131 Boolean fHaveBeenSynced;
00132 struct timeval fSyncTime;
00133
00134 Boolean fQTEnableTrack;
00135 unsigned fQTcomponentSubtype;
00136 char const* fQTcomponentName;
00137 typedef unsigned (QuickTimeFileSink::*atomCreationFunc)();
00138 atomCreationFunc fQTMediaInformationAtomCreator;
00139 atomCreationFunc fQTMediaDataAtomCreator;
00140 char const* fQTAudioDataType;
00141 unsigned short fQTSoundSampleVersion;
00142 unsigned fQTTimeScale;
00143 unsigned fQTTimeUnitsPerSample;
00144 unsigned fQTBytesPerFrame;
00145 unsigned fQTSamplesPerFrame;
00146
00147
00148 unsigned fQTTotNumSamples;
00149 unsigned fQTDurationM;
00150 unsigned fQTDurationT;
00151 unsigned fTKHD_durationPosn;
00152
00153 unsigned fQTInitialOffsetDuration;
00154
00155
00156 ChunkDescriptor *fHeadChunk, *fTailChunk;
00157 unsigned fNumChunks;
00158
00159
00160 struct hinf {
00161 Count64 trpy;
00162 Count64 nump;
00163 Count64 tpyl;
00164
00165 Count64 dmed;
00166 Count64 dimm;
00167
00168
00169 unsigned pmax;
00170 unsigned dmax;
00171 } fHINF;
00172
00173 private:
00174 void useFrame(SubsessionBuffer& buffer);
00175 void useFrameForHinting(unsigned frameSize,
00176 struct timeval presentationTime,
00177 unsigned startSampleNumber);
00178
00179
00180 unsigned useFrame1(unsigned sourceDataSize,
00181 struct timeval presentationTime,
00182 unsigned frameDuration, unsigned destFileOffset);
00183
00184
00185 private:
00186
00187 struct {
00188 unsigned frameSize;
00189 struct timeval presentationTime;
00190 unsigned destFileOffset;
00191
00192
00193 unsigned startSampleNumber;
00194 unsigned short seqNum;
00195 unsigned rtpHeader;
00196 unsigned char numSpecialHeaders;
00197 unsigned specialHeaderBytesLength;
00198 unsigned char specialHeaderBytes[SPECIAL_HEADER_BUFFER_SIZE];
00199 unsigned packetSizes[256];
00200 } fPrevFrameState;
00201 };
00202
00203
00205
00206 QuickTimeFileSink::QuickTimeFileSink(UsageEnvironment& env,
00207 MediaSession& inputSession,
00208 char const* outputFileName,
00209 unsigned bufferSize,
00210 unsigned short movieWidth,
00211 unsigned short movieHeight,
00212 unsigned movieFPS,
00213 Boolean packetLossCompensate,
00214 Boolean syncStreams,
00215 Boolean generateHintTracks,
00216 Boolean generateMP4Format)
00217 : Medium(env), fInputSession(inputSession),
00218 fBufferSize(bufferSize), fPacketLossCompensate(packetLossCompensate),
00219 fSyncStreams(syncStreams), fGenerateMP4Format(generateMP4Format),
00220 fAreCurrentlyBeingPlayed(False),
00221 fLargestRTPtimestampFrequency(0),
00222 fNumSubsessions(0), fNumSyncedSubsessions(0),
00223 fHaveCompletedOutputFile(False),
00224 fMovieWidth(movieWidth), fMovieHeight(movieHeight),
00225 fMovieFPS(movieFPS), fMaxTrackDurationM(0) {
00226 fOutFid = OpenOutputFile(env, outputFileName);
00227 if (fOutFid == NULL) return;
00228
00229 fNewestSyncTime.tv_sec = fNewestSyncTime.tv_usec = 0;
00230 fFirstDataTime.tv_sec = fFirstDataTime.tv_usec = (unsigned)(~0);
00231
00232
00233 MediaSubsessionIterator iter(fInputSession);
00234 MediaSubsession* subsession;
00235 while ((subsession = iter.next()) != NULL) {
00236
00237 FramedSource* subsessionSource = subsession->readSource();
00238 if (subsessionSource == NULL) continue;
00239
00240
00241
00242
00243 if (subsession->videoWidth() != 0) {
00244 fMovieWidth = subsession->videoWidth();
00245 }
00246 if (subsession->videoHeight() != 0) {
00247 fMovieHeight = subsession->videoHeight();
00248 }
00249 if (subsession->videoFPS() != 0) {
00250 fMovieFPS = subsession->videoFPS();
00251 }
00252
00253 SubsessionIOState* ioState
00254 = new SubsessionIOState(*this, *subsession);
00255 if (ioState == NULL || !ioState->setQTstate()) {
00256
00257 delete ioState; ioState = NULL;
00258 continue;
00259 }
00260 subsession->miscPtr = (void*)ioState;
00261
00262 if (generateHintTracks) {
00263
00264 SubsessionIOState* hintTrack
00265 = new SubsessionIOState(*this, *subsession);
00266 SubsessionIOState::setHintTrack(ioState, hintTrack);
00267 if (!hintTrack->setQTstate()) {
00268 delete hintTrack;
00269 SubsessionIOState::setHintTrack(ioState, NULL);
00270 }
00271 }
00272
00273
00274 if (subsession->rtcpInstance() != NULL) {
00275 subsession->rtcpInstance()->setByeHandler(onRTCPBye, ioState);
00276 }
00277
00278 unsigned rtpTimestampFrequency = subsession->rtpTimestampFrequency();
00279 if (rtpTimestampFrequency > fLargestRTPtimestampFrequency) {
00280 fLargestRTPtimestampFrequency = rtpTimestampFrequency;
00281 }
00282
00283 ++fNumSubsessions;
00284 }
00285
00286
00287
00288
00289 gettimeofday(&fStartTime, NULL);
00290 fAppleCreationTime = fStartTime.tv_sec - 0x83dac000;
00291
00292
00293
00294
00295 fMDATposition = ftell(fOutFid);
00296 addAtomHeader("mdat");
00297 }
00298
00299 QuickTimeFileSink::~QuickTimeFileSink() {
00300 completeOutputFile();
00301
00302
00303 MediaSubsessionIterator iter(fInputSession);
00304 MediaSubsession* subsession;
00305 while ((subsession = iter.next()) != NULL) {
00306 SubsessionIOState* ioState
00307 = (SubsessionIOState*)(subsession->miscPtr);
00308 if (ioState == NULL) continue;
00309
00310 delete ioState->fHintTrackForUs;
00311 delete ioState;
00312 }
00313
00314
00315 CloseOutputFile(fOutFid);
00316 }
00317
00318 QuickTimeFileSink*
00319 QuickTimeFileSink::createNew(UsageEnvironment& env,
00320 MediaSession& inputSession,
00321 char const* outputFileName,
00322 unsigned bufferSize,
00323 unsigned short movieWidth,
00324 unsigned short movieHeight,
00325 unsigned movieFPS,
00326 Boolean packetLossCompensate,
00327 Boolean syncStreams,
00328 Boolean generateHintTracks,
00329 Boolean generateMP4Format) {
00330 QuickTimeFileSink* newSink =
00331 new QuickTimeFileSink(env, inputSession, outputFileName, bufferSize, movieWidth, movieHeight, movieFPS,
00332 packetLossCompensate, syncStreams, generateHintTracks, generateMP4Format);
00333 if (newSink == NULL || newSink->fOutFid == NULL) {
00334 Medium::close(newSink);
00335 return NULL;
00336 }
00337
00338 return newSink;
00339 }
00340
00341 Boolean QuickTimeFileSink::startPlaying(afterPlayingFunc* afterFunc,
00342 void* afterClientData) {
00343
00344 if (fAreCurrentlyBeingPlayed) {
00345 envir().setResultMsg("This sink has already been played");
00346 return False;
00347 }
00348
00349 fAreCurrentlyBeingPlayed = True;
00350 fAfterFunc = afterFunc;
00351 fAfterClientData = afterClientData;
00352
00353 return continuePlaying();
00354 }
00355
00356 Boolean QuickTimeFileSink::continuePlaying() {
00357
00358
00359 Boolean haveActiveSubsessions = False;
00360 MediaSubsessionIterator iter(fInputSession);
00361 MediaSubsession* subsession;
00362 while ((subsession = iter.next()) != NULL) {
00363 FramedSource* subsessionSource = subsession->readSource();
00364 if (subsessionSource == NULL) continue;
00365
00366 if (subsessionSource->isCurrentlyAwaitingData()) continue;
00367
00368 SubsessionIOState* ioState
00369 = (SubsessionIOState*)(subsession->miscPtr);
00370 if (ioState == NULL) continue;
00371
00372 haveActiveSubsessions = True;
00373 unsigned char* toPtr = ioState->fBuffer->dataEnd();
00374 unsigned toSize = ioState->fBuffer->bytesAvailable();
00375 subsessionSource->getNextFrame(toPtr, toSize,
00376 afterGettingFrame, ioState,
00377 onSourceClosure, ioState);
00378 }
00379 if (!haveActiveSubsessions) {
00380 envir().setResultMsg("No subsessions are currently active");
00381 return False;
00382 }
00383
00384 return True;
00385 }
00386
00387 void QuickTimeFileSink
00388 ::afterGettingFrame(void* clientData, unsigned packetDataSize,
00389 unsigned ,
00390 struct timeval presentationTime,
00391 unsigned ) {
00392 SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00393 if (!ioState->syncOK(presentationTime)) {
00394
00395 ioState->fOurSink.continuePlaying();
00396 return;
00397 }
00398 ioState->afterGettingFrame(packetDataSize, presentationTime);
00399 }
00400
00401 void QuickTimeFileSink::onSourceClosure(void* clientData) {
00402 SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00403 ioState->onSourceClosure();
00404 }
00405
00406 void QuickTimeFileSink::onSourceClosure1() {
00407
00408
00409 MediaSubsessionIterator iter(fInputSession);
00410 MediaSubsession* subsession;
00411 while ((subsession = iter.next()) != NULL) {
00412 SubsessionIOState* ioState
00413 = (SubsessionIOState*)(subsession->miscPtr);
00414 if (ioState == NULL) continue;
00415
00416 if (ioState->fOurSourceIsActive) return;
00417 }
00418
00419 completeOutputFile();
00420
00421
00422 if (fAfterFunc != NULL) {
00423 (*fAfterFunc)(fAfterClientData);
00424 }
00425 }
00426
00427 void QuickTimeFileSink::onRTCPBye(void* clientData) {
00428 SubsessionIOState* ioState = (SubsessionIOState*)clientData;
00429
00430 struct timeval timeNow;
00431 gettimeofday(&timeNow, NULL);
00432 unsigned secsDiff
00433 = timeNow.tv_sec - ioState->fOurSink.fStartTime.tv_sec;
00434
00435 MediaSubsession& subsession = ioState->fOurSubsession;
00436 ioState->envir() << "Received RTCP \"BYE\" on \""
00437 << subsession.mediumName()
00438 << "/" << subsession.codecName()
00439 << "\" subsession (after "
00440 << secsDiff << " seconds)\n";
00441
00442
00443 ioState->onSourceClosure();
00444 }
00445
00446 static Boolean timevalGE(struct timeval const& tv1,
00447 struct timeval const& tv2) {
00448 return (unsigned)tv1.tv_sec > (unsigned)tv2.tv_sec
00449 || (tv1.tv_sec == tv2.tv_sec
00450 && (unsigned)tv1.tv_usec >= (unsigned)tv2.tv_usec);
00451 }
00452
00453 void QuickTimeFileSink::completeOutputFile() {
00454 if (fHaveCompletedOutputFile || fOutFid == NULL) return;
00455
00456
00457
00458 unsigned curFileSize = ftell(fOutFid);
00459 setWord(fMDATposition, curFileSize);
00460
00461
00462 MediaSubsessionIterator iter(fInputSession);
00463 MediaSubsession* subsession;
00464 while ((subsession = iter.next()) != NULL) {
00465 SubsessionIOState* ioState
00466 = (SubsessionIOState*)(subsession->miscPtr);
00467 if (ioState == NULL) continue;
00468
00469 ChunkDescriptor* const headChunk = ioState->fHeadChunk;
00470 if (headChunk != NULL
00471 && timevalGE(fFirstDataTime, headChunk->fPresentationTime)) {
00472 fFirstDataTime = headChunk->fPresentationTime;
00473 }
00474 }
00475
00476
00477 iter.reset();
00478 while ((subsession = iter.next()) != NULL) {
00479 SubsessionIOState* ioState
00480 = (SubsessionIOState*)(subsession->miscPtr);
00481 if (ioState == NULL) continue;
00482
00483 ioState->setFinalQTstate();
00484
00485 if (ioState->hasHintTrack()) {
00486 ioState->fHintTrackForUs->setFinalQTstate();
00487 }
00488 }
00489
00490 if (fGenerateMP4Format) {
00491
00492 addAtom_ftyp();
00493 }
00494
00495
00496 addAtom_moov();
00497
00498
00499 fHaveCompletedOutputFile = True;
00500 }
00501
00502
00504
00505 unsigned SubsessionIOState::fCurrentTrackNumber = 0;
00506
00507 SubsessionIOState::SubsessionIOState(QuickTimeFileSink& sink,
00508 MediaSubsession& subsession)
00509 : fHintTrackForUs(NULL), fTrackHintedByUs(NULL),
00510 fOurSink(sink), fOurSubsession(subsession),
00511 fLastPacketRTPSeqNum(0), fHaveBeenSynced(False), fQTTotNumSamples(0),
00512 fHeadChunk(NULL), fTailChunk(NULL), fNumChunks(0) {
00513 fTrackID = ++fCurrentTrackNumber;
00514
00515 fBuffer = new SubsessionBuffer(fOurSink.fBufferSize);
00516 fPrevBuffer = sink.fPacketLossCompensate
00517 ? new SubsessionBuffer(fOurSink.fBufferSize) : NULL;
00518
00519 FramedSource* subsessionSource = subsession.readSource();
00520 fOurSourceIsActive = subsessionSource != NULL;
00521
00522 fPrevFrameState.presentationTime.tv_sec = 0;
00523 fPrevFrameState.presentationTime.tv_usec = 0;
00524 fPrevFrameState.seqNum = 0;
00525 }
00526
00527 SubsessionIOState::~SubsessionIOState() {
00528 delete fBuffer; delete fPrevBuffer;
00529 delete fHeadChunk;
00530 }
00531
00532 Boolean SubsessionIOState::setQTstate() {
00533 char const* noCodecWarning1 = "Warning: We don't implement a QuickTime ";
00534 char const* noCodecWarning2 = " Media Data Type for the \"";
00535 char const* noCodecWarning3 = "\" track, so we'll insert a dummy \"????\" Media Data Atom instead. A separate, codec-specific editing pass will be needed before this track can be played.\n";
00536
00537 do {
00538 fQTEnableTrack = True;
00539 fQTTimeScale = fOurSubsession.rtpTimestampFrequency();
00540 fQTTimeUnitsPerSample = 1;
00541 fQTBytesPerFrame = 0;
00542
00543 fQTSamplesPerFrame = 1;
00544
00545
00546
00547 if (isHintTrack()) {
00548
00549 fQTEnableTrack = False;
00550 fQTcomponentSubtype = fourChar('h','i','n','t');
00551 fQTcomponentName = "hint media handler";
00552 fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_gmhd;
00553 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_rtp;
00554 } else if (strcmp(fOurSubsession.mediumName(), "audio") == 0) {
00555 fQTcomponentSubtype = fourChar('s','o','u','n');
00556 fQTcomponentName = "Apple Sound Media Handler";
00557 fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_smhd;
00558 fQTMediaDataAtomCreator
00559 = &QuickTimeFileSink::addAtom_soundMediaGeneral;
00560 fQTSoundSampleVersion = 0;
00561
00562
00563 if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00564 strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00565 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00566 } else if (strcmp(fOurSubsession.codecName(), "PCMU") == 0) {
00567 fQTAudioDataType = "ulaw";
00568 fQTBytesPerFrame = 1;
00569 } else if (strcmp(fOurSubsession.codecName(), "GSM") == 0) {
00570 fQTAudioDataType = "agsm";
00571 fQTBytesPerFrame = 33;
00572 fQTSamplesPerFrame = 160;
00573 } else if (strcmp(fOurSubsession.codecName(), "PCMA") == 0) {
00574 fQTAudioDataType = "alaw";
00575 fQTBytesPerFrame = 1;
00576 } else if (strcmp(fOurSubsession.codecName(), "QCELP") == 0) {
00577 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_Qclp;
00578 fQTSamplesPerFrame = 160;
00579 } else if (strcmp(fOurSubsession.codecName(), "MPEG4-GENERIC") == 0 ||
00580 strcmp(fOurSubsession.codecName(), "MP4A-LATM") == 0) {
00581 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4a;
00582 fQTTimeUnitsPerSample = 1024;
00583
00584
00585 unsigned frequencyFromConfig
00586 = samplingFrequencyFromAudioSpecificConfig(fOurSubsession.fmtp_config());
00587 if (frequencyFromConfig != 0) fQTTimeScale = frequencyFromConfig;
00588 } else {
00589 envir() << noCodecWarning1 << "Audio" << noCodecWarning2
00590 << fOurSubsession.codecName() << noCodecWarning3;
00591 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00592 fQTEnableTrack = False;
00593 }
00594 } else if (strcmp(fOurSubsession.mediumName(), "video") == 0) {
00595 fQTcomponentSubtype = fourChar('v','i','d','e');
00596 fQTcomponentName = "Apple Video Media Handler";
00597 fQTMediaInformationAtomCreator = &QuickTimeFileSink::addAtom_vmhd;
00598
00599
00600 if (strcmp(fOurSubsession.codecName(), "X-QT") == 0 ||
00601 strcmp(fOurSubsession.codecName(), "X-QUICKTIME") == 0) {
00602 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_genericMedia;
00603 } else if (strcmp(fOurSubsession.codecName(), "H263-1998") == 0 ||
00604 strcmp(fOurSubsession.codecName(), "H263-2000") == 0) {
00605 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_h263;
00606 fQTTimeScale = 600;
00607 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00608 } else if (strcmp(fOurSubsession.codecName(), "H264") == 0) {
00609 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_avc1;
00610 fQTTimeScale = 600;
00611 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00612 } else if (strcmp(fOurSubsession.codecName(), "MP4V-ES") == 0) {
00613 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_mp4v;
00614 fQTTimeScale = 600;
00615 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00616 } else {
00617 envir() << noCodecWarning1 << "Video" << noCodecWarning2
00618 << fOurSubsession.codecName() << noCodecWarning3;
00619 fQTMediaDataAtomCreator = &QuickTimeFileSink::addAtom_dummy;
00620 fQTEnableTrack = False;
00621 }
00622 } else {
00623 envir() << "Warning: We don't implement a QuickTime Media Handler for media type \""
00624 << fOurSubsession.mediumName() << "\"";
00625 break;
00626 }
00627
00628 #ifdef QT_SUPPORT_PARTIALLY_ONLY
00629 envir() << "Warning: We don't have sufficient codec-specific information (e.g., sample sizes) to fully generate the \""
00630 << fOurSubsession.mediumName() << "/" << fOurSubsession.codecName()
00631 << "\" track, so we'll disable this track in the movie. A separate, codec-specific editing pass will be needed before this track can be played\n";
00632 fQTEnableTrack = False;
00633 #endif
00634
00635 return True;
00636 } while (0);
00637
00638 envir() << ", so a track for the \"" << fOurSubsession.mediumName()
00639 << "/" << fOurSubsession.codecName()
00640 << "\" subsession will not be included in the output QuickTime file\n";
00641 return False;
00642 }
00643
00644 void SubsessionIOState::setFinalQTstate() {
00645
00646 fQTDurationT = 0;
00647
00648 ChunkDescriptor* chunk = fHeadChunk;
00649 while (chunk != NULL) {
00650 unsigned const numFrames = chunk->fNumFrames;
00651 unsigned const dur = numFrames*chunk->fFrameDuration;
00652 fQTDurationT += dur;
00653
00654 chunk = chunk->fNextChunk;
00655 }
00656
00657
00658 double scaleFactor = fOurSink.movieTimeScale()/(double)fQTTimeScale;
00659 fQTDurationM = (unsigned)(fQTDurationT*scaleFactor);
00660
00661 if (fQTDurationM > fOurSink.fMaxTrackDurationM) {
00662 fOurSink.fMaxTrackDurationM = fQTDurationM;
00663 }
00664 }
00665
00666 void SubsessionIOState::afterGettingFrame(unsigned packetDataSize,
00667 struct timeval presentationTime) {
00668
00669
00670 unsigned short rtpSeqNum
00671 = fOurSubsession.rtpSource()->curPacketRTPSeqNum();
00672 if (fOurSink.fPacketLossCompensate && fPrevBuffer->bytesInUse() > 0) {
00673 short seqNumGap = rtpSeqNum - fLastPacketRTPSeqNum;
00674 for (short i = 1; i < seqNumGap; ++i) {
00675
00676 useFrame(*fPrevBuffer);
00677 }
00678 }
00679 fLastPacketRTPSeqNum = rtpSeqNum;
00680
00681
00682 if (fBuffer->bytesInUse() == 0) {
00683 fBuffer->setPresentationTime(presentationTime);
00684 }
00685 fBuffer->addBytes(packetDataSize);
00686
00687
00688
00689 if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_genericMedia){
00690 QuickTimeGenericRTPSource* rtpSource
00691 = (QuickTimeGenericRTPSource*)fOurSubsession.rtpSource();
00692 QuickTimeGenericRTPSource::QTState& qtState = rtpSource->qtState;
00693 fQTTimeScale = qtState.timescale;
00694 if (qtState.width != 0) {
00695 fOurSink.fMovieWidth = qtState.width;
00696 }
00697 if (qtState.height != 0) {
00698 fOurSink.fMovieHeight = qtState.height;
00699 }
00700
00701
00702
00703 if (qtState.sdAtomSize >= 8) {
00704 char const* atom = qtState.sdAtom;
00705 unsigned mediaType = fourChar(atom[4],atom[5],atom[6],atom[7]);
00706 switch (mediaType) {
00707 case fourChar('a','g','s','m'): {
00708 fQTBytesPerFrame = 33;
00709 fQTSamplesPerFrame = 160;
00710 break;
00711 }
00712 case fourChar('Q','c','l','p'): {
00713 fQTBytesPerFrame = 35;
00714 fQTSamplesPerFrame = 160;
00715 break;
00716 }
00717 case fourChar('H','c','l','p'): {
00718 fQTBytesPerFrame = 17;
00719 fQTSamplesPerFrame = 160;
00720 break;
00721 }
00722 case fourChar('h','2','6','3'): {
00723 fQTTimeUnitsPerSample = fQTTimeScale/fOurSink.fMovieFPS;
00724 break;
00725 }
00726 }
00727 }
00728 } else if (fQTMediaDataAtomCreator == &QuickTimeFileSink::addAtom_Qclp) {
00729
00730
00731
00732
00733 fQTBytesPerFrame = packetDataSize;
00734 }
00735
00736 useFrame(*fBuffer);
00737 if (fOurSink.fPacketLossCompensate) {
00738
00739 SubsessionBuffer* tmp = fPrevBuffer;
00740 fPrevBuffer = fBuffer;
00741 fBuffer = tmp;
00742 }
00743 fBuffer->