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!
This commit is contained in:
Damien Plisson 2010-04-10 09:31:41 +00:00
parent c3ab6bc509
commit c939331a6c
13 changed files with 681 additions and 53 deletions

@ -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):

@ -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 {

@ -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'

@ -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)

@ -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

@ -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')

@ -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;i<quicktime_get_num_codecs();i++) {
codecTypeDesc = quicktime_get_codecType_desc(i);
for(i=0;i<quicktime_get_num_videocodecs();i++) {
codecTypeDesc = quicktime_get_videocodecType_desc(i);
if (!codecTypeDesc) break;
tmp.value= codecTypeDesc->rnatmpvalue;
@ -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;i<quicktime_get_num_audiocodecs();i++) {
codecTypeDesc = quicktime_get_audiocodecType_desc(i);
if (!codecTypeDesc) break;
tmp.value= codecTypeDesc->rnatmpvalue;
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*/

@ -45,6 +45,7 @@ SET(INC
../render/extern/include
../include
../windowmanager
../../../intern/audaspace/intern
)
SET(INC ${INC} ${QUICKTIME_INC})

@ -23,7 +23,8 @@ incs = ['.',
'../imbuf/intern',
'../blenloader',
'../render/extern/include',
'../editors/include']
'../editors/include',
'#/intern/audaspace/intern']
incs.append(env['BF_QUICKTIME_INC'])

@ -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

@ -36,6 +36,9 @@
#include <string.h>
#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 <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>
#include <AudioToolbox/AudioToolbox.h>
#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;
#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<qtCodecCount;i++) {
if (qtCodecList[i].codecType == codecType)
return qtCodecList[i].rnatmpvalue;
for (i=0;i<qtVideoCodecCount;i++) {
if (qtVideoCodecList[i].codecType == codecType)
return qtVideoCodecList[i].rnatmpvalue;
}
return 0;
}
int quicktime_codecType_from_rnatmpvalue(int rnatmpvalue) {
int quicktime_videocodecType_from_rnatmpvalue(int rnatmpvalue) {
int i;
for (i=0;i<qtCodecCount;i++) {
if (qtCodecList[i].rnatmpvalue == rnatmpvalue)
return qtCodecList[i].codecType;
for (i=0;i<qtVideoCodecCount;i++) {
if (qtVideoCodecList[i].rnatmpvalue == rnatmpvalue)
return qtVideoCodecList[i].codecType;
}
return 0;
}
/* Audio codec */
static QuicktimeCodecTypeDesc qtAudioCodecList[] = {
{0, 0, "No audio"},
{kAudioFormatLinearPCM, 1, "LPCM"},
{kAudioFormatAppleLossless, 2, "Apple Lossless"},
{kAudioFormatMPEG4AAC, 3, "AAC"},
{0,0,NULL}};
static int qtAudioCodecCount = 4;
int quicktime_get_num_audiocodecs() {
return qtAudioCodecCount;
}
QuicktimeCodecTypeDesc* quicktime_get_audiocodecType_desc(int indexValue) {
if ((indexValue>=0) && (indexValue < qtAudioCodecCount))
return &qtAudioCodecList[indexValue];
else
return NULL;
}
int quicktime_rnatmpvalue_from_audiocodectype(int codecType) {
int i;
for (i=0;i<qtAudioCodecCount;i++) {
if (qtAudioCodecList[i].codecType == codecType)
return qtAudioCodecList[i].rnatmpvalue;
}
return 0;
}
int quicktime_audiocodecType_from_rnatmpvalue(int rnatmpvalue) {
int i;
for (i=0;i<qtAudioCodecCount;i++) {
if (qtAudioCodecList[i].rnatmpvalue == rnatmpvalue)
return qtAudioCodecList[i].codecType;
}
return 0;
@ -172,14 +237,68 @@ void filepath_qt(char *string, RenderData *rd) {
}
#pragma mark audio export functions
static OSStatus write_cookie(AudioConverterRef converter, AudioFileID outfile)
{
// grab the cookie from the converter and write it to the file
UInt32 cookieSize = 0;
OSStatus err = AudioConverterGetPropertyInfo(converter, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
// if there is an error here, then the format doesn't have a cookie, so on we go
if (!err && cookieSize) {
char* cookie = malloc(cookieSize);
err = AudioConverterGetProperty(converter, kAudioConverterCompressionMagicCookie, &cookieSize, cookie);
if (!err)
err = AudioFileSetProperty (outfile, kAudioFilePropertyMagicCookieData, cookieSize, cookie);
// even though some formats have cookies, some files don't take them
free(cookie);
}
return err;
}
/* AudioConverter input stream callback */
static OSStatus AudioConverterInputCallback(AudioConverterRef inAudioConverter,
UInt32* ioNumberDataPackets,
AudioBufferList* ioData,
AudioStreamPacketDescription** outDataPacketDescription,
void* inUserData)
{
if (qtexport->audioTotalExportedFrames >= 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;
}
}

@ -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<qtCodecCount;i++) {
if (qtCodecList[i].codecType == codecType)
return qtCodecList[i].rnatmpvalue;
for (i=0;i<qtVideoCodecCount;i++) {
if (qtVideoCodecList[i].codecType == codecType)
return qtVideoCodecList[i].rnatmpvalue;
}
return 0;
}
int quicktime_codecType_from_rnatmpvalue(int rnatmpvalue) {
int quicktime_videocodecType_from_rnatmpvalue(int rnatmpvalue) {
int i;
for (i=0;i<qtCodecCount;i++) {
if (qtCodecList[i].rnatmpvalue == rnatmpvalue)
return qtCodecList[i].codecType;
for (i=0;i<qtVideoCodecCount;i++) {
if (qtVideoCodecList[i].rnatmpvalue == rnatmpvalue)
return qtVideoCodecList[i].codecType;
}
return 0;

@ -34,6 +34,10 @@
#define __AIFF__
#define QTAUDIO_FLAG_RESAMPLE_NOHQ 1
#define QTAUDIO_FLAG_CODEC_ISCBR 2
/*Codec list*/
typedef struct QuicktimeCodecTypeDesc {
int codecType;
@ -54,10 +58,19 @@ void filepath_qt(char *string, struct RenderData *rd);
/*RNA helper functions */
void quicktime_verify_image_type(struct RenderData *rd); //used by RNA for defaults values init, if needed
int quicktime_get_num_codecs();
QuicktimeCodecTypeDesc* quicktime_get_codecType_desc(int indexValue);
int quicktime_rnatmpvalue_from_codectype(int codecType);
int quicktime_codecType_from_rnatmpvalue(int rnatmpvalue);
/*Video codec type*/
int quicktime_get_num_videocodecs();
QuicktimeCodecTypeDesc* quicktime_get_videocodecType_desc(int indexValue);
int quicktime_rnatmpvalue_from_videocodectype(int codecType);
int quicktime_videocodecType_from_rnatmpvalue(int rnatmpvalue);
#ifdef USE_QTKIT
/*Audio codec type*/
int quicktime_get_num_audiocodecs();
QuicktimeCodecTypeDesc* quicktime_get_audiocodecType_desc(int indexValue);
int quicktime_rnatmpvalue_from_audiocodectype(int codecType);
int quicktime_audiocodecType_from_rnatmpvalue(int rnatmpvalue);
#endif
#ifndef USE_QTKIT
void SCENE_OT_render_data_set_quicktime_codec(struct wmOperatorType *ot); //Operator to raise quicktime standard dialog to request codec settings