/* * MkvExportPrivate.cpp * * MkvExportPrivate.cpp - C++ code for interfacing with libmatroska to export. * * * Copyright (c) 2006 David Conrad * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include "DataHandlerCallback.h" #include #include #include #include #include #include #include #include #include "MatroskaExport.h" #include "MkvExportPrivate.h" #include "MatroskaCodecIDs.h" using namespace std; using namespace libmatroska; ComponentResult WriteHeader(MatroskaExportGlobals glob) { EbmlHead fileHead; EDocType & myDocType = GetChild(fileHead); *static_cast(&myDocType) = "matroska"; EDocTypeVersion & myDocTypeVer = GetChild(fileHead); *(static_cast(&myDocTypeVer)) = MATROSKA_VERSION; EDocTypeReadVersion & myDocTypeReadVer = GetChild(fileHead); *(static_cast(&myDocTypeReadVer)) = 1; fileHead.Render(*glob->ioHandler, false); glob->segment = new KaxSegment(); // size is unknown and will always be, we can render it right away glob->segmentSize = glob->segment->WriteHead(*glob->ioHandler, 5, false); // reserve 300 octets for the meta seek for the important elements glob->metaSeekPlaceholder = new EbmlVoid(); glob->metaSeekPlaceholder->SetSize(300); glob->metaSeekPlaceholder->Render(*glob->ioHandler, false); glob->metaSeek = new KaxSeekHead(); glob->cues = new KaxCues(); KaxInfo & myInfos = GetChild(*glob->segment); KaxTimecodeScale & timeScale = GetChild(myInfos); if (glob->timecodeScale) *(static_cast(&timeScale)) = glob->timecodeScale; else glob->timecodeScale = 1000000; // default scale: 1 milisecond KaxDuration & segDuration = GetChild(myInfos); *(static_cast(&segDuration)) = Float64(WideToSInt64(glob->duration.value)) * 1e9 / (glob->duration.scale * UInt64(timeScale)); UTFstring muxingApp, writingApp; muxingApp.SetUTF8(string("libebml ") + EbmlCodeVersion + " & libmatroska " + KaxCodeVersion); writingApp.SetUTF8(string("Perian 0.5")); *((EbmlUnicodeString *)&GetChild(myInfos)) = muxingApp; *((EbmlUnicodeString *)&GetChild(myInfos)) = writingApp; GetChild(myInfos).SetEpochDate(time(NULL)); // segment UID is 128 random bits uint8 segUID[128 / 8]; srandom(time(NULL)); for (int i = 0; i < 128 / 8; i++) segUID[i] = random(); KaxSegmentUID &kaxSegUID = GetChild(myInfos); kaxSegUID.CopyBuffer(segUID, 128 / 8); glob->actSegmentSize += myInfos.Render(*glob->ioHandler); glob->metaSeek->IndexThis(myInfos, *glob->segment); return noErr; } ComponentResult GetTrackInfoCopy(MatroskaExportGlobals glob, KaxTracks &trackInfo, KaxTrackEntry &track, OutputTrack &outputTrack) { ComponentResult err = noErr; Media theMedia = GetTrackMedia(outputTrack.theTrack); SampleDescriptionHandle desc = (SampleDescriptionHandle) NewHandle(0); OSType mediaType; GetMediaSampleDescription(theMedia, 1, desc); GetMediaHandlerDescription(theMedia, &mediaType, NULL, NULL); KaxTrackType & trackType = GetChild(track); KaxCodecID & codecID = GetChild(track); // native codec id is preferred; look it up for (int j = 0; j < sizeof(kMatroskaCodecIDs) / sizeof(MatroskaQT_Codec); j++) { if ((*desc)->dataFormat == kMatroskaCodecIDs[j].cType) { *static_cast(&codecID) = kMatroskaCodecIDs[j].mkvID; } } if (mediaType == VideoMediaType) { ImageDescriptionHandle imgDesc = (ImageDescriptionHandle) desc; KaxTrackVideo & trackVideo = GetChild(track); *(static_cast(&trackType)) = track_video; // no native codec id; use vfw compatibility mode if we have a strf extension // otherwise QT compatibility mode; though we should have an option to force vfw // since support for QT compatibility isn't as good if (codecID.GetSize() == 0) { long count; CountImageDescriptionExtensionType(imgDesc, 'strf', &count); if (count > 0) { Handle imgDescHandle = NewHandle(0); GetImageDescriptionExtension(imgDesc, &imgDescHandle, 'strf', 1); *static_cast(&codecID) = MKV_V_MS; KaxCodecPrivate & codecPrivate = GetChild(track); codecPrivate.CopyBuffer((binary *) *imgDescHandle, GetHandleSize(imgDescHandle)); DisposeHandle(imgDescHandle); } else { return -1; } } KaxVideoPixelWidth & pxlWidth = GetChild(trackVideo); KaxVideoPixelHeight & pxlHeight = GetChild(trackVideo); //KaxVideoDisplayWidth & dispWidth = GetChild(trackVideo); //KaxVideoDisplayHeight & dispHeight = GetChild(trackVideo); *(static_cast(&pxlWidth)) = (*imgDesc)->width; *(static_cast(&pxlHeight)) = (*imgDesc)->height; track.EnableLacing(false); } else if (mediaType == SoundMediaType) { SoundDescriptionHandle sndDesc = (SoundDescriptionHandle) desc; KaxTrackAudio & trackAudio = GetChild(track); *(static_cast(&trackType)) = track_audio; AudioStreamBasicDescription asbd = {0}; err = QTSoundDescriptionGetProperty(sndDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_AudioStreamBasicDescription, sizeof(asbd), &asbd, NULL); KaxAudioSamplingFreq & sampleFreq = GetChild(trackAudio); *(static_cast(&sampleFreq)) = asbd.mSampleRate; // only matters for uncompressed formats; it's set to 0 for compressed if (asbd.mBitsPerChannel) { KaxAudioBitDepth & bitDepth = GetChild(trackAudio); *(static_cast(&bitDepth)) = asbd.mBitsPerChannel; } KaxAudioChannels & channels = GetChild(trackAudio); *(static_cast(&channels)) = asbd.mChannelsPerFrame; track.EnableLacing(true); } else { // we couldn't handle the track; remove it trackInfo.Remove(trackInfo.ListSize() - 1); } DisposeHandle((Handle) desc); return err; } ComponentResult GetTrackInfoEncode(MatroskaExportGlobals glob, KaxTrackEntry &track, OutputTrack &outputTrack) { ComponentResult err = noErr; MovieExportGetDataParams gdp = {0}; gdp.recordSize = sizeof(MovieExportGetDataParams); gdp.trackID = outputTrack.trackID; gdp.sourceTimeScale = outputTrack.sourceTimeScale; err = InvokeMovieExportGetDataUPP(outputTrack.refCon, &gdp, outputTrack.getDataProc); if (err) return err; KaxTrackType & trackType = GetChild(track); KaxCodecID & codecID = GetChild(track); // native codec id is preferred; look it up for (int j = 0; j < sizeof(kMatroskaCodecIDs) / sizeof(MatroskaQT_Codec); j++) { if ((*gdp.desc)->dataFormat == kMatroskaCodecIDs[j].cType) { *static_cast(&codecID) = kMatroskaCodecIDs[j].mkvID; } } if (gdp.descType == VideoMediaType) { ImageDescriptionHandle imgDesc = (ImageDescriptionHandle) gdp.desc; *(static_cast(&trackType)) = track_video; if (codecID.GetSize() == 0) { long count; // no native codec id; use vfw compatibility mode if we have a strf extension CountImageDescriptionExtensionType(imgDesc, 'strf', &count); if (count > 0) { Handle imgDescHandle = NewHandle(0); GetImageDescriptionExtension(imgDesc, &imgDescHandle, 'strf', 1); *static_cast(&codecID) = MKV_V_MS; KaxCodecPrivate & codecPrivate = GetChild(track); codecPrivate.CopyBuffer((binary *) *imgDescHandle, GetHandleSize(imgDescHandle)); DisposeHandle(imgDescHandle); } else { return -1; } } KaxVideoPixelWidth & pxlWidth = GetChild(track); KaxVideoPixelHeight & pxlHeight = GetChild(track); //KaxVideoDisplayWidth & dispWidth = GetChild(track); //KaxVideoDisplayHeight & dispHeight = GetChild(track); *(static_cast(&pxlWidth)) = (*imgDesc)->width; *(static_cast(&pxlHeight)) = (*imgDesc)->height; track.EnableLacing(false); } else if (gdp.descType == SoundMediaType) { *(static_cast(&trackType)) = track_audio; } else { printf("Unhandled media type %4.4s\n", &gdp.descType); } return err; } ComponentResult WriteTrackInfo(MatroskaExportGlobals glob) { ComponentResult err = noErr; KaxTracks & trackInfo = GetChild(*glob->segment); KaxTrackEntry *track = NULL; for (int i = 0; i < glob->outputTracks->size(); i++) { if (track == NULL) track = &GetChild(trackInfo); else track = &GetNextChild(trackInfo, *track); track->SetGlobalTimecodeScale(glob->timecodeScale); KaxTrackNumber & trackNum = GetChild(*track); *(static_cast(&trackNum)) = i + 1; KaxTrackUID & trackUID = GetChild(*track); *(static_cast(&trackUID)) = random(); if (glob->outputTracks->at(i).theTrack) GetTrackInfoCopy(glob, trackInfo, *track, glob->outputTracks->at(i)); else GetTrackInfoEncode(glob, *track, glob->outputTracks->at(i)); } glob->actSegmentSize += trackInfo.Render(*glob->ioHandler, false); glob->metaSeek->IndexThis(trackInfo, *glob->segment); return err; } ComponentResult StartNewCluster(MatroskaExportGlobals glob, TimeValue64 atTime) { if (glob->currentCluster) { glob->segmentSize += glob->currentCluster->Render(*glob->ioHandler, *glob->cues, false); glob->currentCluster->ReleaseFrames(); glob->metaSeek->IndexThis(*glob->currentCluster, *glob->segment); delete glob->currentCluster; } glob->currentCluster = new KaxCluster(); glob->currentCluster->SetParent(*glob->segment); glob->currentCluster->SetPreviousTimecode(atTime * glob->timecodeScale, glob->timecodeScale); glob->currentCluster->EnableChecksum(); return noErr; } ComponentResult FinishClusters(MatroskaExportGlobals glob) { if (glob->currentCluster) { glob->segmentSize += glob->currentCluster->Render(*glob->ioHandler, *glob->cues, false); glob->currentCluster->ReleaseFrames(); glob->metaSeek->IndexThis(*glob->currentCluster, *glob->segment); delete glob->currentCluster; glob->currentCluster = NULL; } return noErr; } ComponentResult WriteFrame(MatroskaExportGlobals glob) { ComponentResult err = noErr; Media theMedia; int trackNum = 0; TimeValue64 decodeTime = glob->outputTracks->at(0).currentTime; TimeValue64 decodeDuration, displayOffset; MediaSampleFlags sampleFlags; ByteCount sampleDataSize; // find the track with the next earliest sample for (int i = 1; i < glob->outputTracks->size(); i++) { if (glob->outputTracks->at(i).currentTime < decodeTime && glob->outputTracks->at(i).currentTime >= 0) { trackNum = i; decodeTime = glob->outputTracks->at(i).currentTime; } } theMedia = GetTrackMedia(glob->outputTracks->at(trackNum).theTrack); // update the track's current time GetMediaNextInterestingDecodeTime(theMedia, nextTimeMediaSample, decodeTime, fixed1, &glob->outputTracks->at(trackNum).currentTime, NULL); // get the sample size err = GetMediaSample2(theMedia, NULL, 0, &sampleDataSize, decodeTime, NULL, NULL, NULL, NULL, NULL, 1, NULL, NULL); if (err) return err; // make sure our buffer is large enough if (sampleDataSize > GetPtrSize(glob->sampleBuffer)) SetPtrSize(glob->sampleBuffer, sampleDataSize); // and actually fetch the sample err = GetMediaSample2(theMedia, (UInt8 *) glob->sampleBuffer, GetPtrSize(glob->sampleBuffer), NULL, decodeTime, NULL, &decodeDuration, &displayOffset, NULL, NULL, 1, NULL, &sampleFlags); DataBuffer data((binary *)glob->sampleBuffer, sampleDataSize); // TODO: import with references to correct frames //glob->currentCluster->AddFrame(*glob->outputTracks->at(trackNum).kaxTrack, return err; } ComponentResult FinishFile(MatroskaExportGlobals glob) { // write the seek head where it goes glob->actSegmentSize += glob->metaSeekPlaceholder->ReplaceWith(*glob->metaSeek, *glob->ioHandler, false); if (glob->segment->ForceSize(glob->segmentSize - glob->segment->HeadSize() + glob->actSegmentSize)) glob->segment->OverwriteHead(*glob->ioHandler); return noErr; }