/** * $Id$ * * quicktime_export.c * * Code to create QuickTime Movies with Blender * * ***** BEGIN GPL/BL DUAL LICENSE BLOCK ***** * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. The Blender * Foundation also sells licenses for use in proprietary software under * the Blender License. See http://www.blender.org/BL/ for information * about this. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * The Original Code is written by Rob Haarsma (phase) * * Contributor(s): Stefan Gartner (sgefant) * * ***** END GPL/BL DUAL LICENSE BLOCK ***** */ /* TODO: - fix saving of compressionsettings This should be fixed because: - currently only one quicktime codec is used for all scenes - this single codecsetting gets only initialised from the codec dialog - the codecsettings have to get stored in the blendfile for background rendering. blender -b crashes when it wants to popup the qt codec dialog to retrieve a valid codec. (quicktime isnt initialised yet when blender -b requests a rendering codec) Some references to store the codec settings are placed at the end of this file. DONE: * structurize file & compression data * fix 23.98, 29.97, 59.94 framerates * fix framerate button * fix mac compatibility * fix fallthrough to codecselector // buttons.c * fix playback qt movie // playanim.c * fix setting fps thru blenderbutton as well as codec dialog */ #ifdef WITH_QUICKTIME #if defined(_WIN32) || defined(__APPLE__) /************************************************************ * * * INCLUDE FILES * * * *************************************************************/ #include "BKE_global.h" #include "BKE_scene.h" #include "BLI_blenlib.h" #include "BLO_sys_types.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" #include "MEM_guardedalloc.h" #include "render.h" #include "quicktime_export.h" #ifdef _WIN32 #include #include #include #include #include #include #endif /* _WIN32 */ #ifdef __APPLE__ #undef NDEBUG #include #include #include /* open() */ #include /* close() */ #include /* file permissions */ #endif /* __APPLE__ */ /************************************************************ * * * FUNCTION PROTOTYPES * * * *************************************************************/ static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame); static void QT_DoAddVideoSamplesToMedia (int frame); static void QT_EndAddVideoSamplesToMedia (void); static void QT_CreateMyVideoTrack (void); static void QT_EndCreateMyVideoTrack (void); static void check_renderbutton_framerate(void); /************************************************************ * * * STRUCTS * * * *************************************************************/ typedef struct _QuicktimeExport { FSSpec theSpec; short resRefNum; short resId; short movieResId; Str255 qtfilename; Media theMedia; Movie theMovie; Track theTrack; GWorldPtr theGWorld; PixMapHandle thePixMap; ImageDescription **anImageDescription; ImageSequence anImageSequence; ImBuf *ibuf; //for Qtime's Gworld ImBuf *ibuf2; //copy of renderdata, to be Y-flipped } QuicktimeExport; typedef struct _QuicktimeCodecDataExt { ComponentInstance theComponent; SCTemporalSettings gTemporalSettings; SCSpatialSettings gSpatialSettings; SCDataRateSettings aDataRateSetting; TimeValue duration; long kVideoTimeScale; } QuicktimeCodecDataExt; struct _QuicktimeExport *qte; struct _QuicktimeCodecDataExt *qcdx; /************************************************************ * * * VARIABLES * * * *************************************************************/ #define kMyCreatorType FOUR_CHAR_CODE('TVOD') #define kPixelDepth 32 /* use 32-bit depth */ #define kTrackStart 0 #define kMediaStart 0 static int sframe; int have_qtcodec; char qtcdname[64]; /************************************************************ * * * CheckError(OSErr err, char *msg) * * * * prints errors in console, doesnt interrupt Blender * * * *************************************************************/ void CheckError(OSErr err, char *msg) { if(err != noErr) printf("%s: %d\n", msg, err); } /************************************************************ * * * QT_CreateMyVideoTrack() * * QT_EndCreateMyVideoTrack() * * * * Creates/finishes a video track for the QuickTime movie * * * *************************************************************/ static void QT_CreateMyVideoTrack(void) { OSErr err = noErr; Rect trackFrame; trackFrame.top = 0; trackFrame.left = 0; trackFrame.bottom = R.recty; trackFrame.right = R.rectx; qte->theTrack = NewMovieTrack (qte->theMovie, FixRatio(trackFrame.right,1), FixRatio(trackFrame.bottom,1), kNoVolume); CheckError( GetMoviesError(), "NewMovieTrack error" ); qte->theMedia = NewTrackMedia (qte->theTrack, VideoMediaType, qcdx->kVideoTimeScale, nil, 0); CheckError( GetMoviesError(), "NewTrackMedia error" ); err = BeginMediaEdits (qte->theMedia); CheckError( err, "BeginMediaEdits error" ); QT_StartAddVideoSamplesToMedia (&trackFrame); } static void QT_EndCreateMyVideoTrack(void) { OSErr err = noErr; QT_EndAddVideoSamplesToMedia (); err = EndMediaEdits (qte->theMedia); CheckError( err, "EndMediaEdits error" ); err = InsertMediaIntoTrack (qte->theTrack, kTrackStart,/* track start time */ kMediaStart,/* media start time */ GetMediaDuration (qte->theMedia), fixed1); CheckError( err, "InsertMediaIntoTrack error" ); } /************************************************************ * * * QT_StartAddVideoSamplesToMedia() * * QT_DoAddVideoSamplesToMedia() * * QT_EndAddVideoSamplesToMedia() * * * * Creates video samples for the media in a track * * * *************************************************************/ static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame) { OSErr err = noErr; qte->ibuf = IMB_allocImBuf (R.rectx, R.recty, 32, IB_rect, 0); qte->ibuf2 = IMB_allocImBuf (R.rectx, R.recty, 32, IB_rect, 0); err = NewGWorldFromPtr( &qte->theGWorld, k32ARGBPixelFormat, trackFrame, NULL, NULL, 0, (unsigned char *)qte->ibuf->rect, R.rectx * 4 ); CheckError (err, "NewGWorldFromPtr error"); qte->thePixMap = GetGWorldPixMap(qte->theGWorld); LockPixels(qte->thePixMap); SCDefaultPixMapSettings (qcdx->theComponent, qte->thePixMap, true); SCSetInfo(qcdx->theComponent, scTemporalSettingsType, &qcdx->gTemporalSettings); SCSetInfo(qcdx->theComponent, scSpatialSettingsType, &qcdx->gSpatialSettings); SCSetInfo(qcdx->theComponent, scDataRateSettingsType, &qcdx->aDataRateSetting); err = SCCompressSequenceBegin(qcdx->theComponent, qte->thePixMap, NULL, &qte->anImageDescription); CheckError (err, "SCCompressSequenceBegin error" ); } static void QT_DoAddVideoSamplesToMedia (int frame) { OSErr err = noErr; Rect imageRect; register int index; register int boxsize; register uint32_t *readPos; register uint32_t *changePos; Ptr myPtr; short syncFlag; long dataSize; Handle compressedData; // copy and flip the renderdata if(qte->ibuf2) { memcpy(qte->ibuf2->rect, R.rectot, 4*R.rectx*R.recty); IMB_flipy(qte->ibuf2); } //get pointers to parse bitmapdata myPtr = GetPixBaseAddr(qte->thePixMap); imageRect = (**qte->thePixMap).bounds; boxsize = R.rectx * R.recty; readPos = (uint32_t *) qte->ibuf2->rect; changePos = (uint32_t *) myPtr; #ifdef __APPLE__ // Swap alpha byte to the end, so ARGB become RGBA; note this is big endian-centric. for( index = 0; index < boxsize; index++, changePos++, readPos++ ) *( changePos ) = ( ( *readPos & 0xFFFFFFFF ) >> 8 ) | ( ( *readPos << 24 ) & 0xFF ); #endif #ifdef _WIN32 // poked around a little... this seems to work for windows, dunno if it's legal for( index = 0; index < boxsize; index++, changePos++, readPos++ ) *( changePos ) = ( ( *readPos & 0xFFFFFFFF ) << 8 ) | ( ( *readPos >> 24 ) & 0xFF ); // & ( ( *readPos << 8 ) & 0xFF ); #endif err = SCCompressSequenceFrame(qcdx->theComponent, qte->thePixMap, &imageRect, &compressedData, &dataSize, &syncFlag); CheckError(err, "SCCompressSequenceFrame error"); err = AddMediaSample(qte->theMedia, compressedData, 0, dataSize, qcdx->duration, (SampleDescriptionHandle)qte->anImageDescription, 1, syncFlag, NULL); CheckError(err, "AddMediaSample error"); printf ("added frame %3d (frame %3d in movie): ", frame, frame-sframe); } static void QT_EndAddVideoSamplesToMedia (void) { SCCompressSequenceEnd(qcdx->theComponent); UnlockPixels(qte->thePixMap); if (qte->theGWorld) DisposeGWorld (qte->theGWorld); if (qte->ibuf) IMB_freeImBuf(qte->ibuf); if (qte->ibuf2) { IMB_freeImBuf(qte->ibuf2); } } /************************************************************ * * * makeqtstring (char *string) * * * * Function to generate output filename * * * *************************************************************/ void makeqtstring (char *string) { char txt[64]; if (string==0) return; strcpy(string, G.scene->r.pic); BLI_convertstringcode(string, G.sce, G.scene->r.cfra); RE_make_existing_file(string); if (strcasecmp(string + strlen(string) - 4, ".mov")) { sprintf(txt, "%04d_%04d.mov", (G.scene->r.sfra) , (G.scene->r.efra) ); strcat(string, txt); } } /************************************************************ * * * start_qt(void) * * append_qt(int frame) * * end_qt(int frame) * * * * Quicktime Export functions for Blender's initrender.c * * * ************************************************************/ void start_qt(void) { OSErr err = noErr; char name[2048]; char theFullPath[255]; #ifdef __APPLE__ int myFile; FSRef myRef; #endif if(qte == NULL) qte = MEM_callocN(sizeof(QuicktimeExport), "QuicktimeExport"); if(qcdx == NULL || have_qtcodec == FALSE) get_qtcodec_settings(); if (G.afbreek != 1) { sframe = (G.scene->r.sfra); makeqtstring(name); sprintf(theFullPath, "%s", name); #ifdef __APPLE__ /* hack: create an empty file to make FSPathMakeRef() happy */ myFile = open(theFullPath, O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRUSR|S_IWUSR); if (myFile < 0) { printf("error while creating file!\n"); /* do something? */ } close(myFile); err = FSPathMakeRef(theFullPath, &myRef, 0); CheckError(err, "FsPathMakeRef error"); err = FSGetCatalogInfo(&myRef, kFSCatInfoNone, NULL, NULL, &qte->theSpec, NULL); CheckError(err, "FsGetCatalogInfoRef error"); #else CopyCStringToPascal(theFullPath, qte->qtfilename); err = FSMakeFSSpec(0, 0L, qte->qtfilename, &qte->theSpec); #endif err = CreateMovieFile (&qte->theSpec, kMyCreatorType, smCurrentScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile, &qte->resRefNum, &qte->theMovie ); CheckError(err, "CreateMovieFile error"); printf("Created QuickTime movie: %s\n", name); check_renderbutton_framerate(); QT_CreateMyVideoTrack(); } } void append_qt(int frame) { QT_DoAddVideoSamplesToMedia(frame); } void end_qt(void) { OSErr err = noErr; if(qte->theMovie) { QT_EndCreateMyVideoTrack (); qte->resId = movieInDataForkResID; err = AddMovieResource (qte->theMovie, qte->resRefNum, &qte->resId, qte->qtfilename); CheckError(err, "AddMovieResource error"); if (qte->resRefNum) CloseMovieFile (qte->resRefNum); DisposeMovie (qte->theMovie); } if(qte) { MEM_freeN(qte); qte = NULL; } }; /************************************************************ * * * free_qtcodecdataExt(void) * * * * Function to release codec memory, since it remains * * resident after allocation. * * * *************************************************************/ void free_qtcodecdataExt(void) { if(qcdx) { MEM_freeN(qcdx); qcdx = NULL; } } /************************************************************ * * * check_renderbutton_framerate ( void ) * * * * To keep float framerates consistent between the codec * * dialog and frs/sec button. * * * *************************************************************/ static void check_renderbutton_framerate(void) { OSErr err; err = SCGetInfo(qcdx->theComponent, scTemporalSettingsType, &qcdx->gTemporalSettings); CheckError(err, "SCGetInfo error"); if( (G.scene->r.frs_sec == 24 || G.scene->r.frs_sec == 30 || G.scene->r.frs_sec == 60) && (qcdx->gTemporalSettings.frameRate == 1571553 || qcdx->gTemporalSettings.frameRate == 1964113 || qcdx->gTemporalSettings.frameRate == 3928227)) {;} else qcdx->gTemporalSettings.frameRate = G.scene->r.frs_sec << 16; err = SCSetInfo(qcdx->theComponent, scTemporalSettingsType, &qcdx->gTemporalSettings); CheckError( err, "SCSetInfo error" ); if(qcdx->gTemporalSettings.frameRate == 1571553) { // 23.98 fps qcdx->kVideoTimeScale = 2398; qcdx->duration = 100; } else if (qcdx->gTemporalSettings.frameRate == 1964113) { // 29.97 fps qcdx->kVideoTimeScale = 2997; qcdx->duration = 100; } else if (qcdx->gTemporalSettings.frameRate == 3928227) { // 59.94 fps qcdx->kVideoTimeScale = 5994; qcdx->duration = 100; } else { qcdx->kVideoTimeScale = (qcdx->gTemporalSettings.frameRate >> 16) * 100; qcdx->duration = 100; } } /******************************************************************** * * * get_qtcodec_settings() * * * * Displays Codec Dialog and retrieves Quicktime Codec settings. * * * ********************************************************************/ int get_qtcodec_settings(void) { OSErr err = noErr; // Component c = 0; // ComponentDescription cd; CodecInfo ci; char str[255]; // cd.componentType = StandardCompressionType; // cd.componentSubType = StandardCompressionSubType; // cd.componentManufacturer = 0; // cd.componentFlags = 0; // cd.componentFlagsMask = 0; if(qcdx == NULL) { qcdx = MEM_callocN(sizeof(QuicktimeCodecDataExt), "QuicktimeCodecDataExt"); have_qtcodec = FALSE; } // configure the standard image compression dialog box if (qcdx->theComponent == NULL) { qcdx->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType); // c = FindNextComponent(c, &cd); // qcdx->theComponent = OpenComponent(c); // qcdx->gSpatialSettings.codecType = nil; qcdx->gSpatialSettings.codec = anyCodec; // qcdx->gSpatialSettings.depth; qcdx->gSpatialSettings.spatialQuality = codecMaxQuality; qcdx->gTemporalSettings.temporalQuality = codecMaxQuality; // qcdx->gTemporalSettings.frameRate; qcdx->gTemporalSettings.keyFrameRate = 25; qcdx->aDataRateSetting.dataRate = 90 * 1024; // qcdx->aDataRateSetting.frameDuration; // qcdx->aDataRateSetting.minSpatialQuality; // qcdx->aDataRateSetting.minTemporalQuality; err = SCSetInfo(qcdx->theComponent, scTemporalSettingsType, &qcdx->gTemporalSettings); CheckError(err, "SCSetInfo1 error"); err = SCSetInfo(qcdx->theComponent, scSpatialSettingsType, &qcdx->gSpatialSettings); CheckError(err, "SCSetInfo2 error"); err = SCSetInfo(qcdx->theComponent, scDataRateSettingsType, &qcdx->aDataRateSetting); CheckError(err, "SCSetInfo3 error"); } check_renderbutton_framerate(); // put up the dialog box err = SCRequestSequenceSettings(qcdx->theComponent); if (err == scUserCancelled) { G.afbreek = 1; return 0; } have_qtcodec = TRUE; // get user selected data SCGetInfo(qcdx->theComponent, scTemporalSettingsType, &qcdx->gTemporalSettings); SCGetInfo(qcdx->theComponent, scSpatialSettingsType, &qcdx->gSpatialSettings); SCGetInfo(qcdx->theComponent, scDataRateSettingsType, &qcdx->aDataRateSetting); GetCodecInfo (&ci, qcdx->gSpatialSettings.codecType, 0); CopyPascalStringToC(ci.typeName, str); sprintf(qtcdname,"Codec: %s", str); // framerate jugglin' if(qcdx->gTemporalSettings.frameRate == 1571553) { // 23.98 fps qcdx->kVideoTimeScale = 2398; qcdx->duration = 100; G.scene->r.frs_sec = 24; } else if (qcdx->gTemporalSettings.frameRate == 1964113) { // 29.97 fps qcdx->kVideoTimeScale = 2997; qcdx->duration = 100; G.scene->r.frs_sec = 30; } else if (qcdx->gTemporalSettings.frameRate == 3928227) { // 59.94 fps qcdx->kVideoTimeScale = 5994; qcdx->duration = 100; G.scene->r.frs_sec = 60; } else { qcdx->kVideoTimeScale = 600; qcdx->duration = qcdx->kVideoTimeScale / (qcdx->gTemporalSettings.frameRate / 65536); G.scene->r.frs_sec = (qcdx->gTemporalSettings.frameRate / 65536); } return 1; } #endif /* _WIN32 || __APPLE__ */ #endif /* WITH_QUICKTIME */ #if 0 /************************************************************ * * * References for future codec handling * * * *************************************************************/ these are from mplayer sourcecode: struct ComponentRecord { long data[1]; }; typedef struct ComponentRecord ComponentRecord; typedef ComponentRecord * Component; typedef long OSErr; typedef int OSType; typedef long ComponentResult; typedef long Fixed; these are from quicktime: typedef Component CodecComponent; typedef OSType CodecType; typedef unsigned short CodecFlags; typedef unsigned long CodecQ; typedef struct { CodecType codecType; /* compressor type */ CodecComponent codec; /* compressor */ short depth; /* pixel depth */ CodecQ spatialQuality; /* desired quality */ } SCSpatialSettings; /* temporal options structure with the temporal settings request */ typedef struct { CodecQ temporalQuality; /* desired quality */ Fixed frameRate; /* frame rate */ long keyFrameRate; /* key frame rate */ } SCTemporalSettings; /* data rate options with the data rate settings request */ typedef struct { long dataRate; /* desired data rate */ long frameDuration; /* frame duration */ CodecQ minSpatialQuality; /* minimum value */ CodecQ minTemporalQuality; /* minimum value */ } SCDataRateSettings; would look like this ??? typedef struct { int codecType; /* compressor type */ //here is the prob long *codec; /* compressor */ short depth; /* pixel depth */ unsigned long spatialQuality; /* desired quality */ } SCSpatialSettings; /* temporal options structure with the temporal settings request */ typedef struct { unsigned long temporalQuality; /* desired quality */ long frameRate; /* frame rate */ long keyFrameRate; /* key frame rate */ } SCTemporalSettings; /* data rate options with the data rate settings request */ typedef struct { long dataRate; /* desired data rate */ long frameDuration; /* frame duration */ unsigned long minSpatialQuality; /* minimum value */ unsigned long minTemporalQuality; /* minimum value */ } SCDataRateSettings; stuff to use quicktime Atoms (doesnt work) heh long size; Ptr thePtr; QTAtomContainer myContainer = NULL; QTAtom myAtom; QTAtomContainer SettingsAtom; atom -> component SCSetSettingsFromAtomContainer(qcdx->theComponent, SettingsAtom); component -> atom SCGetSettingsAsAtomContainer(qcdx->theComponent, SettingsAtom); QTCopyAtomDataToPtr(container, atom, 0, size, &targetPtr, &actualsize); QTGetAtomDataPtr(container, atom, &size, &ptr); kParentAtomIsContainer #endif /* 0 */