AudioConverter.m

/*
 
File: AudioConverter.m
 
Author: QuickTime DTS
 
Change History (most recent first): <1> 09/05/06 initial release
 
© Copyright 2006 Apple Computer, Inc. All rights reserved.
 
IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in
consideration of your agreement to the following terms, and your use, installation,
modification or redistribution of this Apple software constitutes acceptance of these
terms.  If you do not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
 
In consideration of your agreement to abide by the following terms, and subject to these
terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in
this original Apple software (the "Apple Software"), to use, reproduce, modify and
redistribute the Apple Software, with or without modifications, in source and/or binary
forms; provided that if you redistribute the Apple Software in its entirety and without
modifications, you must retain this notice and the following text and disclaimers in all
such redistributions of the Apple Software. Neither the name, trademarks, service marks
or logos of Apple Computer, Inc. may be used to endorse or promote products derived from
the Apple Software without specific prior written permission from Apple.  Except as
expressly stated in this notice, no other rights or licenses, express or implied, are
granted by Apple herein, including but not limited to any patent rights that may be
infringed by your derivative works or by other works in which the Apple Software may be
incorporated.
 
The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE
APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
 
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE
USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER
CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT
LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
*/
 
/*
 
QTExtractAndConvertToMovie contains two Objective-C objects that are used together to
implement audio extraction and audio conversion from a QuickTime Movie's sound track to
an movie file.
 
The first is a simple class called MovieWriter (this is a modified version of the
AIFFWriter class that was included with the ExtractMovieAudioToAIFF and
QTExtractAndConvertToAIFF samples). MovieWriter uses QuickTime's Movie Audio Extraction,
Movie Storage and Media Creation APIs. The second is another simple class called
AudioConverter which encapsulates Core Audio's Audio Converter API.
 
The sample uses an instance of the MovieWriter class to easily set up audio extraction
from a QTKit QTMovie to a new destination Movie and its associated movie file (or movie
storage as it's called in the API). MovieWriter uses an instance of the AudioConverter
class to perform the conversion to a user selected destination format which is
configured by the Standard Audio Dialog Component. The Audio Converter uses a Movie
Audio Extraction Session in the Read Input Procedure to pull audio out of the source
Movie.
 
QTExtractAndConvertToMovie demonstrates how to call AddMediaSample2 and supports VBR as
well as CBR audio encoding formats.
 
NOTE: This sample uses the default extraction channel layout which is the aggregate
channel layout of the Movie (for example, all Rights mixed together, all Left Surrounds
mixed together, etc).
 
References:
 
http://developer.apple.com/documentation/QuickTime/Conceptual/QT7UpdateGuide/Chapter02/chapter_2_section_6.html
 
http://developer.apple.com/documentation/MusicAudio/Conceptual/CoreAudioOverview/WhatsinCoreAudio/chapter_3_section_4.html
 
*/
 
#import "AudioConverter.h"
#include "Carbon/Carbon.h"
 
@implementation AudioConverter
 
#pragma mark ---- class methods ----
 
+ (id)newAudioConverterWithMovie:(Movie)inMovie status:(OSStatus *)outStatus
{
    if (NULL == inMovie) {
        NSLog(@"Can't create an AudioConverter object, the Movie is invalid!\n");
        return nil;
    }
 
    return [[self alloc] initWithMovie:inMovie status:outStatus];
}
 
#pragma mark ---- instance methods ----
#pragma mark ---- initialization/dealocation ----
 
- (id)initWithMovie:(Movie)inMovie status:(OSStatus *)outStatus
{
    ComponentInstance ci;
    OSStatus err = *outStatus = noErr;
        
    if (self = [super init]) {
        SCAudioFormatFlagsRestrictions pcmRestrictions = {kLinearPCMFormatFlagIsNonInterleaved, 0};
 
        UInt32 flags;
        SCExtendedProcs extendedProcs = {0}; // used for a custom name on the dialog
        
        //*** Get the summary channel layout and ASBD from the movie ***/
        
        // get the audio stream basic description
        err = QTGetMovieProperty(inMovie,
                                 kQTPropertyClass_Audio,
                                 kQTAudioPropertyID_SummaryASBD,
                                 sizeof(AudioStreamBasicDescription),
                                 &mInputFormat,
                                 NULL);
        if (err) goto bail;
        
        // get the size of the movie summary channel layout
        err = QTGetMoviePropertyInfo(inMovie,
                                     kQTPropertyClass_Audio,
                                     kQTAudioPropertyID_SummaryChannelLayout,
                                     NULL,
                                     &mInputChannelLayoutSize,
                                     NULL);
        if (err) goto bail;
 
        // allocate memory for the layout
        mInputChannelLayoutPtr = (AudioChannelLayout *)calloc(1, mInputChannelLayoutSize);
        if (NULL == mInputChannelLayoutPtr)  { err = memFullErr; goto bail; }
 
        // get the layout for the current extraction configuration
        err = QTGetMovieProperty(inMovie,
                                 kQTPropertyClass_Audio,
                                 kQTAudioPropertyID_SummaryChannelLayout,
                                 mInputChannelLayoutSize,
                                 mInputChannelLayoutPtr,
                                 NULL);
        if (err) goto bail;
        
        // initially set the main channel layout
        // this will change if the number of output channels changes
        mChannelLayout = mInputChannelLayoutPtr;
        mChannelLayoutSize = mInputChannelLayoutSize;
 
        //*** Bring up the StdAudio dialog and get the desired output configuration ***/
    
        // open StdAudio
        err = OpenADefaultComponent(StandardCompressionType, StandardCompressionSubTypeAudio, &ci);
        if (err) goto bail;
    
        // set the input format of the StdAudio component to that of our input asbd
        err = QTSetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                     kQTSCAudioPropertyID_InputBasicDescription,
                                     sizeof(mInputFormat), &mInputFormat);
        if (err) goto bail;
                           
        // set the input channel layout
        err =  QTSetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                      kQTSCAudioPropertyID_InputChannelLayout,
                                      mInputChannelLayoutSize, mInputChannelLayoutPtr);
        if (err) goto bail;
                               
        // set the lpcm restrictions    
        err = QTSetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                     kQTSCAudioPropertyID_ClientRestrictedLPCMFlags,
                                     sizeof(SCAudioFormatFlagsRestrictions),
                                     &pcmRestrictions);
        if (err) goto bail;
        
        // set a custom dialog name
        strcpy((char*)extendedProcs.customName + 1, "Select Output Encoding Format");
        extendedProcs.customName[0] = (unsigned char)strlen((char*)extendedProcs.customName + 1);
        err = QTSetComponentProperty(ci, kQTPropertyClass_SCAudio, 
                                     kQTSCAudioPropertyID_ExtendedProcs, 
                                     sizeof(SCExtendedProcs), &extendedProcs);
        if (err) goto bail;
                           
        // show the dialog (this call blocks until the dialog is finished)
        err = SCRequestImageSettings(ci);
        if (err) goto bail;
        
        // *** Get the information we need to create an Audio Converter to convert to the selected output format
        
        // get the desired output format
        err = QTGetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                     kQTSCAudioPropertyID_BasicDescription,
                                     sizeof(mOutputFormat), &mOutputFormat, NULL);
        if (err) goto bail;
        
        // get the output channel layout
        err = QTGetComponentPropertyInfo(ci, kQTPropertyClass_SCAudio,
                                         kQTSCAudioPropertyID_ChannelLayout,
                                         NULL, &mOutputChannelLayoutSize, NULL);
        if (err) goto bail;
                                                                  
        // allocate memory for it
        mOutputChannelLayoutPtr = (AudioChannelLayout *)calloc(1, mOutputChannelLayoutSize);
        if (NULL == mOutputChannelLayoutPtr) { err = memFullErr; goto bail; }
        
        // get the channel layout
        err = QTGetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                         kQTSCAudioPropertyID_ChannelLayout,
                                         mOutputChannelLayoutSize, mOutputChannelLayoutPtr, &mOutputChannelLayoutSize);
        if (err) goto bail;
         
        // retrive the render quality
        err = QTGetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                     kQTSCAudioPropertyID_RenderQuality,
                                     sizeof(mRenderQuality), &mRenderQuality, NULL);
        if (err) goto bail;
 
        // get the codec specific settings if available
        // used to configure the audio converter
        if (noErr == QTGetComponentPropertyInfo(ci, kQTPropertyClass_SCAudio,
                                                kQTSCAudioPropertyID_CodecSpecificSettingsArray,
                                                NULL, NULL, &flags) && (flags & kComponentPropertyFlagCanGetNow)) {                                
                                                                                                  
            err = QTGetComponentProperty(ci, kQTPropertyClass_SCAudio, 
                                         kQTSCAudioPropertyID_CodecSpecificSettingsArray,
                                         sizeof(CFArrayRef), &mCodecSpecificSettings, NULL);
            if (err) goto bail;
                                         
        }
        
        // get the magic cookie if available
        // maybe used to configure the audio converter if CodecSpecificSettingsArray is not available
        // must be written to the file if it exists
        if (noErr == QTGetComponentPropertyInfo(ci, kQTPropertyClass_SCAudio,
                                                    kQTSCAudioPropertyID_MagicCookie,
                                                    NULL, &mMagicCookieSize, NULL) && mMagicCookieSize) {
            
            mMagicCookie = calloc(1, mMagicCookieSize);
            if (NULL == mMagicCookie) { err = memFullErr; goto bail; }
            
            err = QTGetComponentProperty(ci, kQTPropertyClass_SCAudio,
                                         kQTSCAudioPropertyID_MagicCookie,
                                         mMagicCookieSize, mMagicCookie, &mMagicCookieSize);
            if (err) goto bail;
        }
        
        // see if the output format is externally framed and requires packet descriptions
        err = QTGetComponentProperty(ci, kQTPropertyClass_SCAudio, 
                                     kQTSCAudioPropertyID_OutputFormatIsExternallyFramed,
                                     sizeof(Boolean), &mFormatRequiresPacketDescriptions, NULL);
        if (err) goto bail;
        
        // close StdAudio, we don't need it anymore
        CloseComponent(ci);
        ci = NULL;
    
        // *** Create the audio converter and set it up with info retrieved from StdAudio ***/
 
        if (mInputFormat.mChannelsPerFrame != mOutputFormat.mChannelsPerFrame) {
            // an AudioConverter can't do mixing -- it requires n channels IN equalling n channels OUT
            // therefore we need to alter the input ASBD so Movie Audio Extraction will do the mixing for us
            
            // modify the input ASBD as required
            mInputFormat.mChannelsPerFrame = mOutputFormat.mChannelsPerFrame;
            
            // also need to change the channel layout appropriately
            mChannelLayout = mOutputChannelLayoutPtr;
            mChannelLayoutSize = mOutputChannelLayoutSize;
        }
    
        // create an AudioConverter
        err = AudioConverterNew(&mInputFormat, &mOutputFormat, &mAudioConverter);
        if (err) goto bail;
        
        // set the channel layout
        err = [self setProperty:kAudioConverterOutputChannelLayout :mOutputChannelLayoutSize :mOutputChannelLayoutPtr];
        if (err) goto bail;
        
        // set the render quality for the audio converter
        // if there's no sample rate conversion in the chain a kAudioConverterErr_PropertyNotSupported
        // error would be returned here, this is not a problem so ignore the return value
        // NOTE: remember to also set the same quality value to the Movie Audio Extraction session
        // so that any scaled edits or codec decompressions are done at the same render quality
        // specified in the dialog - we do this ourselves later
        [self setProperty:kAudioConverterSampleRateConverterQuality:sizeof(UInt32):&mRenderQuality];
    
        // A codec that has CodecSpecificSettings might have a MagicCookie as well,
        // but prefer the CodecSpecificSettingsArray for the audio converter if you have both
        if (NULL != mCodecSpecificSettings) {
        
            // set the codec specific settings if we got em or...
            err = [self setProperty:kAudioConverterPropertySettings:sizeof(CFArrayRef):&mCodecSpecificSettings];
            if (err) goto bail;
            
        } else if (NULL != mMagicCookie) {
        
            // ...set the magic cookie if we got it
            err = [self setProperty:kAudioConverterCompressionMagicCookie:mMagicCookieSize:mMagicCookie];
            if (err) goto bail;
        }
        
        return self;
    }
    
bail:
    
    if (NULL != ci) CloseComponent(ci);
    [self release];
    
    *outStatus = err;
    
    return nil;
}
 
- (void)dealloc
{   
    [self reset];
    
    [super dealloc];
}
 
#pragma mark ---- reset ----
 
- (void)reset
{   
    mInputFormat = (AudioStreamBasicDescription){0};
    mOutputFormat = (AudioStreamBasicDescription){0};
        
    mChannelLayout = NULL;
    mChannelLayoutSize = 0;
    mFormatRequiresPacketDescriptions = false;
    mRenderQuality = kRenderQuality_Min;
    mReadInputDataProcPtr = NULL;
    mOutputBufferList = NULL;
    
    if (NULL != mInputChannelLayoutPtr)  { free(mInputChannelLayoutPtr); mInputChannelLayoutPtr = NULL; mInputChannelLayoutSize = 0; }
    if (NULL != mOutputChannelLayoutPtr) { free(mOutputChannelLayoutPtr); mOutputChannelLayoutPtr = NULL; mOutputChannelLayoutSize = 0; }
    
    if (NULL != mCodecSpecificSettings) { CFRelease(mCodecSpecificSettings); mCodecSpecificSettings = NULL; }
    if (NULL != mMagicCookie) { free(mMagicCookie); mMagicCookie = NULL; mMagicCookieSize = 0; }
    
    if (NULL != mAudioConverter) {
        AudioConverterDispose(mAudioConverter);
        mAudioConverter = NULL;
    }
}
 
#pragma mark ---- convert ----
 
- (OSStatus)convert:(UInt32 *)ioOutputDataPacketSize :(AudioStreamPacketDescription *)outPacketDescription :(void *)inRefCon
{
    OSStatus err = paramErr;
        
    if (NULL == mAudioConverter || NULL == mReadInputDataProcPtr || NULL == mOutputBufferList) {
        NSLog(@"The AudioConverter Object is Incorrectly Configured!\n");
        return err;
    }
        
    return AudioConverterFillComplexBuffer(mAudioConverter, mReadInputDataProcPtr, inRefCon, ioOutputDataPacketSize, mOutputBufferList, outPacketDescription);
}
 
 
#pragma mark ---- properties ----
 
- (OSStatus)getPropertyInfo:(AudioConverterPropertyID)propertyID :(UInt32 *)propertyDataSize :(Boolean *)writable
{
    return AudioConverterGetPropertyInfo(mAudioConverter, propertyID, propertyDataSize, writable);
}
 
- (OSStatus)getProperty:(AudioConverterPropertyID) propertyID: (UInt32 *)propertyDataSize: (void *)propertyData;
{
    return AudioConverterGetProperty(mAudioConverter, propertyID, propertyDataSize, propertyData);
}
 
- (OSStatus)setProperty:(AudioConverterPropertyID) propertyID: (UInt32)propertyDataSize: (const void *)propertyData;
{
    return AudioConverterSetProperty(mAudioConverter, propertyID, propertyDataSize, propertyData);
}
 
#pragma mark ---- getters ----
 
- (AudioConverterRef)audioConverter
{
    return mAudioConverter;
}
 
- (AudioStreamBasicDescription *)inputFormat
{
    return &mInputFormat;
}
 
- (AudioStreamBasicDescription *)outputFormat
{
    return &mOutputFormat;
}
 
- (AudioChannelLayout *)channelLayout
{
    return mChannelLayout;
}
 
- (UInt32)channelLayoutSize
{
    return mChannelLayoutSize;
}
 
- (UInt32 *)renderQuality
{
    return &mRenderQuality;
}
 
- (void *)magicCookie
{
    return mMagicCookie;
}
 
- (UInt32)magicCookieSize
{
    return mMagicCookieSize;
}
 
- (Boolean)formatRequiresPacketDescriptions
{
    return mFormatRequiresPacketDescriptions;
}
 
#pragma mark ---- setters ----
 
-(void)setInputDataProc:(AudioConverterComplexInputDataProc)inInputDataProcPtr
{
    mReadInputDataProcPtr = inInputDataProcPtr;
}
 
-(void)setOutputAudioBufferList:(AudioBufferList *)inOutputAudioBufferList
{
    mOutputBufferList = inOutputAudioBufferList;
}
    
@end