From c939331a6ccacc571c893646e209e99680a81aa5 Mon Sep 17 00:00:00 2001 From: Damien Plisson Date: Sat, 10 Apr 2010 09:31:41 +0000 Subject: QTKit (OSX 64bit): Add audio export to Quicktime Supports default OSX codecs : Linear PCM, Apple Lossless and AAC Note that AAC codec doesn't support sample rates above 48kHz. If a python/rna guru knows how to easily enforce this limit, he is welcome! Enjoy making Quicktime movies now with audio! --- release/scripts/ui/properties_render.py | 28 +- source/blender/makesdna/DNA_scene_types.h | 9 + source/blender/makesrna/SConscript | 2 +- source/blender/makesrna/intern/CMakeLists.txt | 2 +- source/blender/makesrna/intern/Makefile | 1 + source/blender/makesrna/intern/SConscript | 1 + source/blender/makesrna/intern/rna_scene.c | 116 ++++- source/blender/quicktime/CMakeLists.txt | 1 + source/blender/quicktime/SConscript | 3 +- source/blender/quicktime/apple/Makefile | 1 + source/blender/quicktime/apple/qtkit_export.m | 519 ++++++++++++++++++++-- source/blender/quicktime/apple/quicktime_export.c | 30 +- source/blender/quicktime/quicktime_export.h | 21 +- 13 files changed, 681 insertions(+), 53 deletions(-) diff --git a/release/scripts/ui/properties_render.py b/release/scripts/ui/properties_render.py index 2d20a948278..57cd23e64ef 100644 --- a/release/scripts/ui/properties_render.py +++ b/release/scripts/ui/properties_render.py @@ -362,13 +362,37 @@ class RENDER_PT_output(RenderButtonsPanel): elif rd.file_format == 'QUICKTIME_CARBON': split = layout.split() - split.operator("scene.render_set_quicktime_codec") + split.operator("scene.render_data_set_quicktime_codec") elif rd.file_format == 'QUICKTIME_QTKIT': split = layout.split() col = split.column() - col.prop(rd, "quicktime_codec_type") + col.prop(rd, "quicktime_codec_type", text="Video Codec") col.prop(rd, "quicktime_codec_spatial_quality", text="Quality") + + #Audio + col.prop(rd,"quicktime_audiocodec_type", text="Audio Codec") + if rd.quicktime_audiocodec_type != 'No audio': + split = layout.split() + col = split.column() + if rd.quicktime_audiocodec_type == 'LPCM': + col.prop(rd,"quicktime_audio_bitdepth", text="") + if wide_ui: + col = split.column() + col.prop(rd,"quicktime_audio_samplerate", text="") + + split = layout.split() + col = split.column() + if rd.quicktime_audiocodec_type == 'AAC': + col.prop(rd,"quicktime_audio_bitrate") + if wide_ui: + subsplit = split.split() + col = subsplit.column() + if rd.quicktime_audiocodec_type == 'AAC': + col.prop(rd,"quicktime_audio_codec_isvbr") + if wide_ui: + col = subsplit.column() + col.prop(rd,"quicktime_audio_resampling_hq") class RENDER_PT_encoding(RenderButtonsPanel): diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index f9deebb0026..7d7af0ddf82 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -104,6 +104,15 @@ typedef struct QuicktimeCodecSettings { int minTemporalQuality; /* in 0-100 scale, to be translated in 0-1024 for qt use */ int keyFrameRate; int bitRate; /* bitrate in bps */ + + /* Audio Codec settings */ + int audiocodecType; + int audioSampleRate; + short audioBitDepth; + short audioChannels; + int audioCodecFlags; + int audioBitRate; + int pad1; } QuicktimeCodecSettings; typedef struct FFMpegCodecData { diff --git a/source/blender/makesrna/SConscript b/source/blender/makesrna/SConscript index cf31fb8e0e5..71cfc3c7da4 100644 --- a/source/blender/makesrna/SConscript +++ b/source/blender/makesrna/SConscript @@ -6,7 +6,7 @@ objs = [] o = SConscript('intern/SConscript') objs += o -incs = '#/intern/guardedalloc ../blenkernel ../blenlib ../makesdna intern .' +incs = '#/intern/guardedalloc #/intern/audaspace/intern ../blenkernel ../blenlib ../makesdna intern .' incs += ' ../windowmanager ../editors/include ../gpu ../imbuf ../ikplugin' incs += ' ../render/extern/include' diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 58931165638..0e25160cdff 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -39,7 +39,7 @@ SET(SRC ../../../../intern/guardedalloc/intern/mallocn.c ../../../../intern/guardedalloc/intern/mmap_win.c) -INCLUDE_DIRECTORIES(../../../../intern/guardedalloc .. ../../makesdna ../../blenkernel ../../blenlib ../../ikplugin ../../windowmanager ../../editors/include ../../gpu ../../imbuf ../../render/extern/include .) +INCLUDE_DIRECTORIES(../../../../intern/audaspace/intern ../../../../intern/guardedalloc .. ../../makesdna ../../blenkernel ../../blenlib ../../ikplugin ../../windowmanager ../../editors/include ../../gpu ../../imbuf ../../render/extern/include .) FILE(GLOB INC_FILES ../*.h ../../makesdna/*.h) IF(NOT WITH_PYTHON) diff --git a/source/blender/makesrna/intern/Makefile b/source/blender/makesrna/intern/Makefile index 5afe55bb319..c26593100f8 100644 --- a/source/blender/makesrna/intern/Makefile +++ b/source/blender/makesrna/intern/Makefile @@ -46,6 +46,7 @@ endif CFLAGS += $(LEVEL_1_C_WARNINGS) CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include +CPPFLAGS += -I../../../../intern/audaspace/intern CPPFLAGS += -I../../blenlib CPPFLAGS += -I../../blenkernel CPPFLAGS += -I../../imbuf diff --git a/source/blender/makesrna/intern/SConscript b/source/blender/makesrna/intern/SConscript index b63a816edfb..d22a654db02 100644 --- a/source/blender/makesrna/intern/SConscript +++ b/source/blender/makesrna/intern/SConscript @@ -33,6 +33,7 @@ incs = '#/intern/guardedalloc ../../blenlib ../../blenkernel' incs += ' ../../imbuf ../../makesdna ../../makesrna ../../ikplugin' incs += ' ../../windowmanager ../../editors/include' incs += ' ../../render/extern/include' +incs += ' #/intern/audaspace/intern' if env['WITH_BF_OPENEXR']: defs.append('WITH_OPENEXR') diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index dd06d2df8e0..40027b16950 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -39,6 +39,7 @@ #ifdef WITH_QUICKTIME #include "quicktime_export.h" +#include "AUD_C-API.h" #endif #ifdef WITH_FFMPEG @@ -523,14 +524,14 @@ static int rna_RenderSettings_qtcodecsettings_codecType_get(PointerRNA *ptr) { RenderData *rd= (RenderData*)ptr->data; - return quicktime_rnatmpvalue_from_codectype(rd->qtcodecsettings.codecType); + return quicktime_rnatmpvalue_from_videocodectype(rd->qtcodecsettings.codecType); } static void rna_RenderSettings_qtcodecsettings_codecType_set(PointerRNA *ptr, int value) { RenderData *rd= (RenderData*)ptr->data; - rd->qtcodecsettings.codecType = quicktime_codecType_from_rnatmpvalue(value); + rd->qtcodecsettings.codecType = quicktime_videocodecType_from_rnatmpvalue(value); } static EnumPropertyItem *rna_RenderSettings_qtcodecsettings_codecType_itemf(bContext *C, PointerRNA *ptr, int *free) @@ -541,8 +542,8 @@ static EnumPropertyItem *rna_RenderSettings_qtcodecsettings_codecType_itemf(bCon int i=1, totitem= 0; char id[5]; - for(i=0;irnatmpvalue; @@ -556,9 +557,48 @@ static EnumPropertyItem *rna_RenderSettings_qtcodecsettings_codecType_itemf(bCon RNA_enum_item_end(&item, &totitem); *free= 1; + return item; +} + +#ifdef USE_QTKIT +static int rna_RenderSettings_qtcodecsettings_audiocodecType_get(PointerRNA *ptr) +{ + RenderData *rd= (RenderData*)ptr->data; + + return quicktime_rnatmpvalue_from_audiocodectype(rd->qtcodecsettings.audiocodecType); +} + +static void rna_RenderSettings_qtcodecsettings_audiocodecType_set(PointerRNA *ptr, int value) +{ + RenderData *rd= (RenderData*)ptr->data; + + rd->qtcodecsettings.audiocodecType = quicktime_audiocodecType_from_rnatmpvalue(value); +} + +static EnumPropertyItem *rna_RenderSettings_qtcodecsettings_audiocodecType_itemf(bContext *C, PointerRNA *ptr, int *free) +{ + EnumPropertyItem *item= NULL; + EnumPropertyItem tmp = {0, "", 0, "", ""}; + QuicktimeCodecTypeDesc *codecTypeDesc; + int i=1, totitem= 0; + + for(i=0;irnatmpvalue; + tmp.identifier= codecTypeDesc->codecName; + tmp.name= codecTypeDesc->codecName; + RNA_enum_item_add(&item, &totitem, &tmp); + } + + RNA_enum_item_end(&item, &totitem); + *free= 1; + return item; } #endif +#endif static int rna_RenderSettings_active_layer_index_get(PointerRNA *ptr) { @@ -1824,6 +1864,35 @@ static void rna_def_scene_render_data(BlenderRNA *brna) static EnumPropertyItem quicktime_codec_type_items[] = { {0, "codec", 0, "codec", ""}, {0, NULL, 0, NULL, NULL}}; + +#ifdef USE_QTKIT + static EnumPropertyItem quicktime_audio_samplerate_items[] = { + {22050, "22050", 0, "22kHz", ""}, + {44100, "44100", 0, "44.1kHz", ""}, + {48000, "48000", 0, "48kHz", ""}, + {88200, "88200", 0, "88.2kHz", ""}, + {96000, "96000", 0, "96kHz", ""}, + {192000, "192000", 0, "192kHz", ""}, + {0, NULL, 0, NULL, NULL}}; + + static EnumPropertyItem quicktime_audio_bitdepth_items[] = { + {AUD_FORMAT_U8, "8BIT", 0, "8bit", ""}, + {AUD_FORMAT_S16, "16BIT", 0, "16bit", ""}, + {AUD_FORMAT_S24, "24BIT", 0, "24bit", ""}, + {AUD_FORMAT_S32, "32BIT", 0, "32bit", ""}, + {AUD_FORMAT_FLOAT32, "FLOAT32", 0, "float32", ""}, + {AUD_FORMAT_FLOAT64, "FLOAT64", 0, "float64", ""}, + {0, NULL, 0, NULL, NULL}}; + + static EnumPropertyItem quicktime_audio_bitrate_items[] = { + {64000, "64000", 0, "64kbps", ""}, + {112000, "112000", 0, "112kpbs", ""}, + {128000, "128000", 0, "128kbps", ""}, + {192000, "192000", 0, "192kbps", ""}, + {256000, "256000", 0, "256kbps", ""}, + {320000, "320000", 0, "320kbps", ""}, + {0, NULL, 0, NULL, NULL}}; +#endif #endif #ifdef WITH_FFMPEG @@ -2031,8 +2100,47 @@ static void rna_def_scene_render_data(BlenderRNA *brna) RNA_def_property_int_sdna(prop, NULL, "qtcodecsettings.codecSpatialQuality"); RNA_def_property_range(prop, 0, 100); RNA_def_property_ui_text(prop, "Spatial quality", "Intra-frame spatial quality level"); + RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); + +#ifdef USE_QTKIT + prop= RNA_def_property(srna, "quicktime_audiocodec_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "qtcodecsettings.audiocodecType"); + RNA_def_property_enum_items(prop, quicktime_codec_type_items); + RNA_def_property_enum_funcs(prop, "rna_RenderSettings_qtcodecsettings_audiocodecType_get", + "rna_RenderSettings_qtcodecsettings_audiocodecType_set", + "rna_RenderSettings_qtcodecsettings_audiocodecType_itemf"); + RNA_def_property_ui_text(prop, "Audio Codec", "QuickTime audio codec type"); + RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); + + prop= RNA_def_property(srna, "quicktime_audio_samplerate", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "qtcodecsettings.audioSampleRate"); + RNA_def_property_enum_items(prop, quicktime_audio_samplerate_items); + RNA_def_property_ui_text(prop, "Smp Rate", "Sample Rate"); + RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); + + prop= RNA_def_property(srna, "quicktime_audio_bitdepth", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "qtcodecsettings.audioBitDepth"); + RNA_def_property_enum_items(prop, quicktime_audio_bitdepth_items); + RNA_def_property_ui_text(prop, "Bit Depth", "Bit Depth"); + RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); + + prop= RNA_def_property(srna, "quicktime_audio_resampling_hq", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "qtcodecsettings.audioCodecFlags", QTAUDIO_FLAG_RESAMPLE_NOHQ); + RNA_def_property_ui_text(prop, "HQ", "Use High Quality resampling algorithm"); + RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); + + prop= RNA_def_property(srna, "quicktime_audio_codec_isvbr", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "qtcodecsettings.audioCodecFlags", QTAUDIO_FLAG_CODEC_ISCBR); + RNA_def_property_ui_text(prop, "VBR", "Use Variable Bit Rate compression (improves quality at same bitrate)"); + RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); + + prop= RNA_def_property(srna, "quicktime_audio_bitrate", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "qtcodecsettings.audioBitRate"); + RNA_def_property_enum_items(prop, quicktime_audio_bitrate_items); + RNA_def_property_ui_text(prop, "Bitrate", "Compressed audio bitrate"); RNA_def_property_update(prop, NC_SCENE|ND_RENDER_OPTIONS, NULL); #endif +#endif #ifdef WITH_FFMPEG /* FFMPEG Video*/ diff --git a/source/blender/quicktime/CMakeLists.txt b/source/blender/quicktime/CMakeLists.txt index f1b2a121b50..2b5c8a53e9b 100644 --- a/source/blender/quicktime/CMakeLists.txt +++ b/source/blender/quicktime/CMakeLists.txt @@ -45,6 +45,7 @@ SET(INC ../render/extern/include ../include ../windowmanager + ../../../intern/audaspace/intern ) SET(INC ${INC} ${QUICKTIME_INC}) diff --git a/source/blender/quicktime/SConscript b/source/blender/quicktime/SConscript index c8cd795decb..c8aeb70aa57 100644 --- a/source/blender/quicktime/SConscript +++ b/source/blender/quicktime/SConscript @@ -23,7 +23,8 @@ incs = ['.', '../imbuf/intern', '../blenloader', '../render/extern/include', - '../editors/include'] + '../editors/include', + '#/intern/audaspace/intern'] incs.append(env['BF_QUICKTIME_INC']) diff --git a/source/blender/quicktime/apple/Makefile b/source/blender/quicktime/apple/Makefile index 70f3f05c5f0..70757f02055 100644 --- a/source/blender/quicktime/apple/Makefile +++ b/source/blender/quicktime/apple/Makefile @@ -59,4 +59,5 @@ CPPFLAGS += -I.. CPPFLAGS += -I../../blenloader -I../../imbuf/intern -I../../imbuf CPPFLAGS += -I../../blenlib -I../../makesdna -I../../editors/include -I../../avi CPPFLAGS += -I../../blenkernel -I../../render/extern/include -I../../windowmanager -I../../makesrna +CPPFLAGS += -I../../../intern/audaspace/intern diff --git a/source/blender/quicktime/apple/qtkit_export.m b/source/blender/quicktime/apple/qtkit_export.m index 06082832b1e..cfe86017e3a 100644 --- a/source/blender/quicktime/apple/qtkit_export.m +++ b/source/blender/quicktime/apple/qtkit_export.m @@ -36,6 +36,9 @@ #include #include "DNA_scene_types.h" +#include "DNA_userdef_types.h" + +#include "AUD_C-API.h" #include "BKE_global.h" #include "BKE_scene.h" @@ -57,6 +60,7 @@ #endif #import #import +#include #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4) || !__LP64__ #error 64 bit build & OSX 10.5 minimum are needed for QTKit @@ -74,14 +78,34 @@ typedef struct QuicktimeExport { QTTime frameDuration; NSDictionary *frameAttributes; + + NSString *videoTempFileName; + /* Audio section */ + AUD_Device *audioInputDevice; + AudioFileID audioFile; + NSString *audioFileName; + AudioConverterRef audioConverter; + AudioBufferList audioBufferList; + AudioStreamBasicDescription audioInputFormat, audioOutputFormat; + AudioStreamPacketDescription *audioOutputPktDesc; + SInt64 audioFilePos; + char* audioInputBuffer; + char* audioOutputBuffer; + UInt32 audioCodecMaxOutputPacketSize; + UInt64 audioTotalExportedFrames, audioTotalSavedFrames; + UInt64 audioLastFrame; + SInt64 audioOutputPktPos; + } QuicktimeExport; static struct QuicktimeExport *qtexport; -#pragma mark rna helper functions +#define AUDIOOUTPUTBUFFERSIZE 65536 +#pragma mark rna helper functions -static QuicktimeCodecTypeDesc qtCodecList[] = { +/* Video codec */ +static QuicktimeCodecTypeDesc qtVideoCodecList[] = { {kRawCodecType, 1, "Uncompressed"}, {kJPEGCodecType, 2, "JPEG"}, {kMotionJPEGACodecType, 3, "M-JPEG A"}, @@ -96,34 +120,75 @@ static QuicktimeCodecTypeDesc qtCodecList[] = { {kH264CodecType, 12, "H.264"}, {0,0,NULL}}; -static int qtCodecCount = 12; +static int qtVideoCodecCount = 12; -int quicktime_get_num_codecs() { - return qtCodecCount; +int quicktime_get_num_videocodecs() { + return qtVideoCodecCount; } -QuicktimeCodecTypeDesc* quicktime_get_codecType_desc(int indexValue) { - if ((indexValue>=0) && (indexValue < qtCodecCount)) - return &qtCodecList[indexValue]; +QuicktimeCodecTypeDesc* quicktime_get_videocodecType_desc(int indexValue) { + if ((indexValue>=0) && (indexValue < qtVideoCodecCount)) + return &qtVideoCodecList[indexValue]; else return NULL; } -int quicktime_rnatmpvalue_from_codectype(int codecType) { +int quicktime_rnatmpvalue_from_videocodectype(int codecType) { int i; - for (i=0;i=0) && (indexValue < qtAudioCodecCount)) + return &qtAudioCodecList[indexValue]; + else + return NULL; +} + +int quicktime_rnatmpvalue_from_audiocodectype(int codecType) { + int i; + for (i=0;iaudioTotalExportedFrames >= qtexport->audioLastFrame) { /* EOF */ + *ioNumberDataPackets = 0; + return noErr; + } + + if (qtexport->audioInputFormat.mBytesPerPacket * *ioNumberDataPackets > AUDIOOUTPUTBUFFERSIZE) + *ioNumberDataPackets = AUDIOOUTPUTBUFFERSIZE / qtexport->audioInputFormat.mBytesPerPacket; + + if ((qtexport->audioTotalExportedFrames + *ioNumberDataPackets) > qtexport->audioLastFrame) + *ioNumberDataPackets += qtexport->audioLastFrame - qtexport->audioTotalExportedFrames; + + qtexport->audioTotalExportedFrames += *ioNumberDataPackets; + + AUD_readDevice(qtexport->audioInputDevice, (UInt8*)qtexport->audioInputBuffer, + qtexport->audioInputFormat.mFramesPerPacket * *ioNumberDataPackets); + + ioData->mBuffers[0].mDataByteSize = qtexport->audioInputFormat.mBytesPerPacket * *ioNumberDataPackets; + ioData->mBuffers[0].mData = qtexport->audioInputBuffer; + ioData->mBuffers[0].mNumberChannels = qtexport->audioInputFormat.mChannelsPerFrame; + + return noErr; +} + + #pragma mark export functions int start_qt(struct Scene *scene, struct RenderData *rd, int rectx, int recty, ReportList *reports) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSError *error; - char name[2048]; + char name[1024]; int success= 1; + OSStatus err=noErr; if(qtexport == NULL) qtexport = MEM_callocN(sizeof(QuicktimeExport), "QuicktimeExport"); @@ -192,14 +311,223 @@ int start_qt(struct Scene *scene, struct RenderData *rd, int rectx, int recty, R } else { makeqtstring(rd, name); - qtexport->filename = [NSString stringWithCString:name + qtexport->filename = [[NSString alloc] initWithCString:name encoding:[NSString defaultCStringEncoding]]; - qtexport->movie = [[QTMovie alloc] initToWritableFile:qtexport->filename error:&error]; + qtexport->movie = nil; + qtexport->audioFile = NULL; + + if (rd->qtcodecsettings.audiocodecType) { + // generate a name for our video & audio files + /* Init audio file */ + CFURLRef outputFileURL; + char extension[32]; + AudioFileTypeID audioFileType; + + switch (rd->qtcodecsettings.audiocodecType) { + case kAudioFormatLinearPCM: + audioFileType = kAudioFileWAVEType; + strcpy(extension,".wav"); + break; + case kAudioFormatMPEG4AAC: + case kAudioFormatAppleLossless: + audioFileType = kAudioFileM4AType; + strcpy(extension, ".m4a"); + break; + default: + audioFileType = kAudioFileAIFFType; + strcpy(extension,".aiff"); + break; + } + + tmpnam(name); + strcat(name, extension); + outputFileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,(UInt8*) name, strlen(name), false); + + if (outputFileURL) { + + qtexport->audioFileName = [[NSString alloc] initWithCString:name + encoding:[NSString defaultCStringEncoding]]; + + qtexport->audioInputFormat.mSampleRate = U.audiorate; + qtexport->audioInputFormat.mFormatID = kAudioFormatLinearPCM; + qtexport->audioInputFormat.mChannelsPerFrame = U.audiochannels; + switch (U.audioformat) { + case AUD_FORMAT_U8: + qtexport->audioInputFormat.mBitsPerChannel = 8; + qtexport->audioInputFormat.mFormatFlags = 0; + break; + case AUD_FORMAT_S24: + qtexport->audioInputFormat.mBitsPerChannel = 24; + qtexport->audioInputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + break; + case AUD_FORMAT_S32: + qtexport->audioInputFormat.mBitsPerChannel = 32; + qtexport->audioInputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + break; + case AUD_FORMAT_FLOAT32: + qtexport->audioInputFormat.mBitsPerChannel = 32; + qtexport->audioInputFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + break; + case AUD_FORMAT_FLOAT64: + qtexport->audioInputFormat.mBitsPerChannel = 64; + qtexport->audioInputFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + break; + case AUD_FORMAT_S16: + default: + qtexport->audioInputFormat.mBitsPerChannel = 16; + qtexport->audioInputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + break; + } + qtexport->audioInputFormat.mBytesPerFrame = qtexport->audioInputFormat.mChannelsPerFrame * qtexport->audioInputFormat.mBitsPerChannel / 8; + qtexport->audioInputFormat.mFramesPerPacket = 1; + qtexport->audioInputFormat.mBytesPerPacket = qtexport->audioInputFormat.mBytesPerFrame; + qtexport->audioInputFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked; + + + /*Ouput format*/ + qtexport->audioOutputFormat.mFormatID = rd->qtcodecsettings.audiocodecType; + //TODO: set audio channels + qtexport->audioOutputFormat.mChannelsPerFrame = 2; + qtexport->audioOutputFormat.mSampleRate = rd->qtcodecsettings.audioSampleRate; + + /* Default value for compressed formats, overriden after if not the case */ + qtexport->audioOutputFormat.mFramesPerPacket = 0; + qtexport->audioOutputFormat.mBytesPerFrame = 0; + qtexport->audioOutputFormat.mBytesPerPacket = 0; + qtexport->audioOutputFormat.mBitsPerChannel = 0; + + switch (rd->qtcodecsettings.audiocodecType) { + case kAudioFormatMPEG4AAC: + qtexport->audioOutputFormat.mFormatFlags = kMPEG4Object_AAC_Main; + case kAudioFormatAppleLossless: + switch (U.audioformat) { + case AUD_FORMAT_S16: + qtexport->audioOutputFormat.mFormatFlags = kAppleLosslessFormatFlag_16BitSourceData; + break; + case AUD_FORMAT_S24: + qtexport->audioOutputFormat.mFormatFlags = kAppleLosslessFormatFlag_24BitSourceData; + break; + case AUD_FORMAT_S32: + qtexport->audioOutputFormat.mFormatFlags = kAppleLosslessFormatFlag_32BitSourceData; + break; + case AUD_FORMAT_U8: + case AUD_FORMAT_FLOAT32: + case AUD_FORMAT_FLOAT64: + default: + break; + } + break; + case kAudioFormatLinearPCM: + default: + switch (rd->qtcodecsettings.audioBitDepth) { + case AUD_FORMAT_U8: + qtexport->audioOutputFormat.mBitsPerChannel = 8; + qtexport->audioOutputFormat.mFormatFlags = 0; + break; + case AUD_FORMAT_S24: + qtexport->audioOutputFormat.mBitsPerChannel = 24; + qtexport->audioOutputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + break; + case AUD_FORMAT_S32: + qtexport->audioOutputFormat.mBitsPerChannel = 32; + qtexport->audioOutputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + break; + case AUD_FORMAT_FLOAT32: + qtexport->audioOutputFormat.mBitsPerChannel = 32; + qtexport->audioOutputFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + break; + case AUD_FORMAT_FLOAT64: + qtexport->audioOutputFormat.mBitsPerChannel = 64; + qtexport->audioOutputFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat; + break; + case AUD_FORMAT_S16: + default: + qtexport->audioOutputFormat.mBitsPerChannel = 16; + qtexport->audioOutputFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + break; + } + qtexport->audioOutputFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked; + qtexport->audioOutputFormat.mBytesPerPacket = qtexport->audioOutputFormat.mChannelsPerFrame * (qtexport->audioOutputFormat.mBitsPerChannel / 8); + qtexport->audioOutputFormat.mFramesPerPacket = 1; + qtexport->audioOutputFormat.mBytesPerFrame = qtexport->audioOutputFormat.mBytesPerPacket; + break; + } + + err = AudioFileCreateWithURL(outputFileURL, audioFileType, &qtexport->audioOutputFormat, kAudioFileFlags_EraseFile, &qtexport->audioFile); + CFRelease(outputFileURL); + + if(err) + BKE_report(reports, RPT_ERROR, "\nQuicktime: unable to create temporary audio file. Format error ?"); + else { + err = AudioConverterNew(&qtexport->audioInputFormat, &qtexport->audioOutputFormat, &qtexport->audioConverter); + if (err) { + BKE_report(reports, RPT_ERROR, "\nQuicktime: unable to initialize audio codec converter. Format error ?"); + AudioFileClose(qtexport->audioFile); + qtexport->audioFile = NULL; + [qtexport->audioFileName release]; + qtexport->audioFileName = nil; + } else { + UInt32 prop,propSize; + /* Set up codec properties */ + if (rd->qtcodecsettings.audiocodecType == kAudioFormatMPEG4AAC) { /*Lossy compressed format*/ + prop = rd->qtcodecsettings.audioBitRate; + AudioConverterSetProperty(qtexport->audioConverter, kAudioConverterEncodeBitRate, + sizeof(prop), &prop); + + if (rd->qtcodecsettings.audioCodecFlags & QTAUDIO_FLAG_CODEC_ISCBR) + prop = kAudioCodecBitRateControlMode_Constant; + else + prop = kAudioCodecBitRateControlMode_LongTermAverage; + AudioConverterSetProperty(qtexport->audioConverter, kAudioCodecPropertyBitRateControlMode, + sizeof(prop), &prop); + } + /* Conversion quality : if performance impact then offer degraded option */ + if ((rd->qtcodecsettings.audioCodecFlags & QTAUDIO_FLAG_RESAMPLE_NOHQ) == 0) { + prop = kAudioConverterSampleRateConverterComplexity_Mastering; + AudioConverterSetProperty(qtexport->audioConverter, kAudioConverterSampleRateConverterComplexity, + sizeof(prop), &prop); + + prop = kAudioConverterQuality_Max; + AudioConverterSetProperty(qtexport->audioConverter, kAudioConverterSampleRateConverterQuality, + sizeof(prop), &prop); + } + + write_cookie(qtexport->audioConverter, qtexport->audioFile); + + /* Allocate output buffer */ + if (qtexport->audioOutputFormat.mBytesPerPacket ==0) /* VBR */ + AudioConverterGetProperty(qtexport->audioConverter, kAudioConverterPropertyMaximumOutputPacketSize, + &propSize, &qtexport->audioCodecMaxOutputPacketSize); + else + qtexport->audioCodecMaxOutputPacketSize = qtexport->audioOutputFormat.mBytesPerPacket; + + qtexport->audioInputBuffer = MEM_mallocN(AUDIOOUTPUTBUFFERSIZE, "qt_audio_inputPacket"); + qtexport->audioOutputBuffer = MEM_mallocN(AUDIOOUTPUTBUFFERSIZE, "qt_audio_outputPacket"); + qtexport->audioOutputPktDesc = MEM_mallocN(sizeof(AudioStreamPacketDescription)*AUDIOOUTPUTBUFFERSIZE/qtexport->audioCodecMaxOutputPacketSize, + "qt_audio_pktdesc"); + } + } + } + + if (err == noErr) { + qtexport->videoTempFileName = [[NSString alloc] initWithCString:tmpnam(nil) + encoding:[NSString defaultCStringEncoding]]; + if (qtexport->videoTempFileName) + qtexport->movie = [[QTMovie alloc] initToWritableFile:qtexport->videoTempFileName error:&error]; + + } + } else + qtexport->movie = [[QTMovie alloc] initToWritableFile:qtexport->filename error:&error]; if(qtexport->movie == nil) { BKE_report(reports, RPT_ERROR, "Unable to create quicktime movie."); success= 0; - NSLog(@"Unable to create quicktime movie : %@",[error localizedDescription]); + if (qtexport->filename) [qtexport->filename release]; + qtexport->filename = nil; + if (qtexport->audioFileName) [qtexport->audioFileName release]; + qtexport->audioFileName = nil; + if (qtexport->videoTempFileName) [qtexport->videoTempFileName release]; + qtexport->videoTempFileName = nil; [QTMovie exitQTKitOnThread]; } else { [qtexport->movie retain]; @@ -226,6 +554,23 @@ int start_qt(struct Scene *scene, struct RenderData *rd, int rectx, int recty, R nil]; } [qtexport->frameAttributes retain]; + + if (qtexport->audioFile) { + /* Init audio input stream */ + AUD_DeviceSpecs specs; + + specs.channels = U.audiochannels; + specs.format = U.audioformat; + specs.rate = U.audiorate; + qtexport->audioInputDevice = AUD_openReadDevice(specs); + AUD_playDevice(qtexport->audioInputDevice, scene->sound_scene, rd->sfra * rd->frs_sec_base / rd->frs_sec); + + qtexport->audioOutputPktPos = 0; + qtexport->audioTotalExportedFrames = 0; + qtexport->audioTotalSavedFrames = 0; + + qtexport->audioLastFrame = (rd->efra - rd->sfra) * qtexport->audioInputFormat.mSampleRate * rd->frs_sec_base / rd->frs_sec; + } } } @@ -276,6 +621,41 @@ int append_qt(struct RenderData *rd, int frame, int *pixels, int rectx, int rect [blBitmapFormatImage release]; [frameImage release]; + + + if (qtexport->audioFile) { + UInt32 audioPacketsConverted; + /* Append audio */ + while (((double)qtexport->audioTotalExportedFrames / (double) qtexport->audioInputFormat.mSampleRate) + < ((double)(frame - rd->sfra)) / (((double)rd->frs_sec) / rd->frs_sec_base)) { + + qtexport->audioBufferList.mNumberBuffers = 1; + qtexport->audioBufferList.mBuffers[0].mNumberChannels = qtexport->audioOutputFormat.mChannelsPerFrame; + qtexport->audioBufferList.mBuffers[0].mDataByteSize = AUDIOOUTPUTBUFFERSIZE; + qtexport->audioBufferList.mBuffers[0].mData = qtexport->audioOutputBuffer; + audioPacketsConverted = AUDIOOUTPUTBUFFERSIZE / qtexport->audioCodecMaxOutputPacketSize; + + AudioConverterFillComplexBuffer(qtexport->audioConverter, AudioConverterInputCallback, + NULL, &audioPacketsConverted, &qtexport->audioBufferList, qtexport->audioOutputPktDesc); + if (audioPacketsConverted) { + AudioFileWritePackets(qtexport->audioFile, false, qtexport->audioBufferList.mBuffers[0].mDataByteSize, + qtexport->audioOutputPktDesc, qtexport->audioOutputPktPos, &audioPacketsConverted, qtexport->audioOutputBuffer); + qtexport->audioOutputPktPos += audioPacketsConverted; + + if (qtexport->audioOutputFormat.mFramesPerPacket) { + // this is the common case: format has constant frames per packet + qtexport->audioTotalSavedFrames += (audioPacketsConverted * qtexport->audioOutputFormat.mFramesPerPacket); + } else { + unsigned int i; + // if there are variable frames per packet, then we have to do this for each packeet + for (i = 0; i < audioPacketsConverted; ++i) + qtexport->audioTotalSavedFrames += qtexport->audioOutputPktDesc[i].mVariableFramesInPacket; + } + + + } + } + } [pool drain]; return 1; @@ -284,17 +664,96 @@ int append_qt(struct RenderData *rd, int frame, int *pixels, int rectx, int rect void end_qt(void) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; if (qtexport->movie) { - /* Flush update of the movie file */ - [qtexport->movie updateMovieFile]; - [qtexport->movie invalidate]; + if (qtexport->audioFile) + { + NSDictionary *dict = nil; + QTMovie *audioTmpMovie = nil; + NSError *error; + NSFileManager *fileManager; + + /* Mux video and audio then save file */ + + /* Write last frames for VBR files */ + if (qtexport->audioOutputFormat.mBitsPerChannel == 0) { + OSStatus err = noErr; + AudioConverterPrimeInfo primeInfo; + UInt32 primeSize = sizeof(primeInfo); + + err = AudioConverterGetProperty(qtexport->audioConverter, kAudioConverterPrimeInfo, &primeSize, &primeInfo); + if (err == noErr) { + // there's priming to write out to the file + AudioFilePacketTableInfo pti; + pti.mPrimingFrames = primeInfo.leadingFrames; + pti.mRemainderFrames = primeInfo.trailingFrames; + pti.mNumberValidFrames = qtexport->audioTotalSavedFrames - pti.mPrimingFrames - pti.mRemainderFrames; + AudioFileSetProperty(qtexport->audioFile, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti); + } + + } + + write_cookie(qtexport->audioConverter, qtexport->audioFile); + AudioConverterDispose(qtexport->audioConverter); + AudioFileClose(qtexport->audioFile); + AUD_closeReadDevice(qtexport->audioInputDevice); + qtexport->audioFile = NULL; + qtexport->audioInputDevice = NULL; + MEM_freeN(qtexport->audioInputBuffer); + MEM_freeN(qtexport->audioOutputBuffer); + MEM_freeN(qtexport->audioOutputPktDesc); + + /* Reopen audio file and merge it */ + audioTmpMovie = [QTMovie movieWithFile:qtexport->audioFileName error:&error]; + if (audioTmpMovie) { + NSArray *audioTracks = [audioTmpMovie tracksOfMediaType:QTMediaTypeSound]; + QTTrack *audioTrack = nil; + if( [audioTracks count] > 0 ) + { + audioTrack = [audioTracks objectAtIndex:0]; + } + + if( audioTrack ) + { + QTTimeRange totalRange; + totalRange.time = QTZeroTime; + totalRange.duration = [[audioTmpMovie attributeForKey:QTMovieDurationAttribute] QTTimeValue]; + + [qtexport->movie insertSegmentOfTrack:audioTrack timeRange:totalRange atTime:QTZeroTime]; + } + } + + /* Save file */ + dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] + forKey:QTMovieFlatten]; + + if (dict) { + [qtexport->movie writeToFile:qtexport->filename withAttributes:dict]; + } + + /* Delete temp files */ + fileManager = [[NSFileManager alloc] init]; + [fileManager removeItemAtPath:qtexport->audioFileName error:&error]; + [fileManager removeItemAtPath:qtexport->videoTempFileName error:&error]; + } + else { + /* Flush update of the movie file */ + [qtexport->movie updateMovieFile]; + + [qtexport->movie invalidate]; + } /* Clean up movie structure */ - [qtexport->filename release]; + if (qtexport->filename) [qtexport->filename release]; + qtexport->filename = nil; + if (qtexport->audioFileName) [qtexport->audioFileName release]; + qtexport->audioFileName = nil; + if (qtexport->videoTempFileName) [qtexport->videoTempFileName release]; + qtexport->videoTempFileName = nil; [qtexport->frameAttributes release]; [qtexport->movie release]; - } + } [QTMovie exitQTKitOnThread]; @@ -302,6 +761,7 @@ void end_qt(void) MEM_freeN(qtexport); qtexport = NULL; } + [pool drain]; } @@ -318,6 +778,15 @@ void quicktime_verify_image_type(RenderData *rd) rd->qtcodecsettings.codecType = kJPEGCodecType; rd->qtcodecsettings.codecSpatialQuality = (codecHighQuality*100)/codecLosslessQuality; } + if ((rd->qtcodecsettings.audioSampleRate < 21000) || + (rd->qtcodecsettings.audioSampleRate > 193000)) + rd->qtcodecsettings.audioSampleRate = 48000; + + if (rd->qtcodecsettings.audioBitDepth == 0) + rd->qtcodecsettings.audioBitDepth = AUD_FORMAT_S16; + + if (rd->qtcodecsettings.audioBitRate == 0) + rd->qtcodecsettings.audioBitRate = 256000; } } diff --git a/source/blender/quicktime/apple/quicktime_export.c b/source/blender/quicktime/apple/quicktime_export.c index aaf2634bbf6..c1291fc6949 100644 --- a/source/blender/quicktime/apple/quicktime_export.c +++ b/source/blender/quicktime/apple/quicktime_export.c @@ -123,7 +123,7 @@ static int sframe; /* RNA functions */ -static QuicktimeCodecTypeDesc qtCodecList[] = { +static QuicktimeCodecTypeDesc qtVideoCodecList[] = { {kRawCodecType, 1, "Uncompressed"}, {kJPEGCodecType, 2, "JPEG"}, {kMotionJPEGACodecType, 3, "M-JPEG A"}, @@ -138,34 +138,34 @@ static QuicktimeCodecTypeDesc qtCodecList[] = { {kH264CodecType, 12, "H.264"}, {0,0,NULL}}; -static int qtCodecCount = 12; +static int qtVideoCodecCount = 12; -int quicktime_get_num_codecs() { - return qtCodecCount; +int quicktime_get_num_videocodecs() { + return qtVideoCodecCount; } -QuicktimeCodecTypeDesc* quicktime_get_codecType_desc(int indexValue) { - if ((indexValue>=0) && (indexValue < qtCodecCount)) - return &qtCodecList[indexValue]; +QuicktimeCodecTypeDesc* quicktime_get_videocodecType_desc(int indexValue) { + if ((indexValue>=0) && (indexValue < qtVideoCodecCount)) + return &qtVideoCodecList[indexValue]; else return NULL; } -int quicktime_rnatmpvalue_from_codectype(int codecType) { +int quicktime_rnatmpvalue_from_videocodectype(int codecType) { int i; - for (i=0;i