Audio Unit:音楽ファイルを再生する

前回のサイン波の再生に続き、今回は音楽ファイルの再生にチャレンジします。
音楽ファイルのアクセスにはAudio Toolboxの中のExtended Audio File Serviceを利用します。このサービスのAPIを使えば様々な音楽ファイルの形式(WAV, mp3, AAC等)を意識することなくプログラムで扱いやすいリニアPCM形式としてアクセスできます。(形式の変換はAPI側でやってくれます)

作成したプログラムの画面は以下の通りです。

●音楽ファイルのオープン

Extended Audio File Serviceを利用して音楽ファイルをオープンします。
オープンした後は読み込み時の形式をExtAudioFileSetPropertyのkExtAudioFileProperty_ClientDataFormatで32bit浮動小数点のリニアPCM形式に設定します。

#define	CHANNEL_NUM		2
//
// Audio Fileのオープン
//
- (BOOL)openExtAudioFile:(NSString *)strPath
{
	NSURL *oURL = [NSURL fileURLWithPath:strPath];
	OSStatus err = ExtAudioFileOpenURL( (CFURLRef)oURL, &mAudioFileRef );
	if ( err ) {
		NSLog( @"ExtAudioFileOpenURL failed.(err=%d)\n", err );
		return NO;
	}

	UInt32 nSize = sizeof(SInt64);
	err = ExtAudioFileGetProperty( mAudioFileRef,
								  kExtAudioFileProperty_FileLengthFrames,
								  &nSize,
								  &mFrameCount );
	if ( err ) {
		NSLog( @"ExtAudioFileGetProperty failed.(err=%d)\n", err );
		return NO;
	}
	NSLog( @"Frame Count = %d\n", mFrameCount );

	AudioStreamBasicDescription tFormat;
	
	tFormat.mChannelsPerFrame = CHANNEL_NUM;
	
	tFormat.mSampleRate = mSamplingRate;
	tFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
	tFormat.mFormatID = kAudioFormatLinearPCM;
	tFormat.mBytesPerPacket = 4 * CHANNEL_NUM;
	tFormat.mFramesPerPacket = 1;
	tFormat.mBytesPerFrame = 4 * CHANNEL_NUM;
	tFormat.mBitsPerChannel = 32;
	
	nSize = sizeof(AudioStreamBasicDescription);
	err = ExtAudioFileSetProperty(mAudioFileRef,
								  kExtAudioFileProperty_ClientDataFormat, 
								  nSize,
								  &tFormat);
	if(err){
		NSLog( @"kExtAudioFileProperty_ClientDataFormat failed.(err=%d)\n", err );
		return NO;
	}

	return YES;
}

●音楽ファイルの読み込み

コールバック関数の中でオープンしたファイルから波形データを読み込みます。読み込んだデータはLRLRというように左チャンネルのデータと右チャンネルのデータが交互に続きます。

//
// コールバック関数
//
OSStatus	RenderCallback(void	 *inRefCon,
						   AudioUnitRenderActionFlags *ioActionFlags,
						   const AudioTimeStamp *inTimeStamp,
						   UInt32				inBusNumber,
						   UInt32				inNumberFrames,
						   AudioBufferList		*ioData)
{
	//NSLog(@"inNumberFrames = %d",inNumberFrames);	
	
	UCExtAudioFilePlay *oFilePlay = inRefCon;
		
	UInt32	nFrames = inNumberFrames;
	AudioBufferList audioBuffer;
	
    audioBuffer.mNumberBuffers = 1;
    audioBuffer.mBuffers[0].mNumberChannels = CHANNEL_NUM;
    audioBuffer.mBuffers[0].mData = malloc( nFrames * CHANNEL_NUM * sizeof(float) );
    audioBuffer.mBuffers[0].mDataByteSize = nFrames * CHANNEL_NUM * sizeof(float);

	OSStatus err = ExtAudioFileRead( [oFilePlay AudioFileRef], &nFrames, &audioBuffer );
	if ( err != noErr ) {
		NSLog( @"ExtAudioFileRead() failed. err=%d\n", err );
		free( audioBuffer.mBuffers[0].mData );
		return err;
	}

	if ( nFrames == 0 ) {
		// EOF
		free( audioBuffer.mBuffers[0].mData );
		if ( [[oFilePlay delegate] respondsToSelector:@selector(MusicDidEnd)] ) {
			[[oFilePlay delegate] MusicDidEnd];
		}
		return noErr;
	}
	
	float *outL = ioData->mBuffers[0].mData;
	float *outR = ioData->mBuffers[1].mData;
	float *wave = audioBuffer.mBuffers[0].mData;
	
	int i;
	for (i=0; i< nFrames; i++){
		*outL++ = *wave++;
		*outR++ = *wave++;
    }
	
	free( audioBuffer.mBuffers[0].mData );

	[oFilePlay incrementFrames:nFrames];
	if ( [[oFilePlay delegate] respondsToSelector:@selector(Increment:)] ) {
		[[oFilePlay delegate] Increment:[oFilePlay curFrame]];
	}
				
	return noErr;
}

●音楽ファイル再生クラス(UCExtAudioFilePlay)

音楽ファイル再生クラスのコードを以下に示します。

UCExtAudioFilePlay.h:音楽ファイル再生クラスインターフェースファイル
#import <Cocoa/Cocoa.h>
#import <AudioUnit/AudioUnit.h>
#import <AudioToolbox/ExtendedAudioFile.h>

@interface UCExtAudioFilePlay : NSObject {
	NSString		*mAudioPath;	// ファイルパス
	AudioUnit		mOutUnit;		// Audio Unit(Default Output)
	Float64			mSamplingRate;	// サンプリングレート
	SInt64			mFrameCount;	// フレーム数
	SInt64			mCurFrame;		// カレントフレーム
	Float32			mVolume;		// 音量
	ExtAudioFileRef	mAudioFileRef;	// extended audio file object.
	BOOL			mPlaying;		// 再生中フラグ
	id				mDelegate;		// delegate
}

- (id)initWithContentsOfFile:(NSString *)strPath volume:(float)fVol delegate:(id)delegate;
- (void)dealloc;
- (void)setVolume:(float)fVol;
- (void)play;
- (void)stop;
- (void)seek:(SInt64)nPos;
- (ExtAudioFileRef)AudioFileRef;
- (void)incrementFrames:(UInt32)nFrames;
- (SInt64)frameCount;
- (SInt64)curFrame;
- (id)delegate;

// 内部メソッド
- (BOOL)initAU;
- (BOOL)openExtAudioFile:(NSString *)strPath;

@end

UCExtAudioFilePlay.m:音楽ファイル再生クラスインプリメントファイル
#import "UCExtAudioFilePlay.h"

#define	CHANNEL_NUM		2

@implementation UCExtAudioFilePlay
//
// 初期化メソッド
//
- (id)initWithContentsOfFile:(NSString *)strPath volume:(float)fVol delegate:(id)delegate
{
	self = [super init];
	if ( self == nil ) {
		return nil;
	}
	
	// Audio Unitの初期化
	BOOL bRet = [self initAU];
	if ( bRet != YES ) {
		[self release];
		return nil;
	}

	// Audio Fileのオープン
	bRet = [self openExtAudioFile:strPath];
	if ( bRet != YES ) {
		[self release];
		return nil;
	}

	// メンバー変数の初期化
	mAudioPath = [strPath retain];
	[self setVolume:fVol];
	mCurFrame = 0;
	mPlaying = NO;
	mDelegate = delegate;
	
	return self;
}

//
// 後処理
//
- (void)dealloc
{
	AudioUnitUninitialize(mOutUnit);
	CloseComponent(mOutUnit);
	
	ExtAudioFileDispose( mAudioFileRef );

	[mAudioPath release];
	
	[super dealloc];
}

//
// 音量を設定する
//
- (void)setVolume:(float)fVol
{
	mVolume = fVol;
	OSStatus err = AudioUnitSetParameter( mOutUnit,
										 kHALOutputParam_Volume,
										 kAudioUnitScope_Global,
										 0,
										 mVolume,
										 0 );
	if ( err != noErr ) {
		NSLog( @"AudioUnitSetParameter(kHALOutputParam_Volume) failed. err=%d\n", err );
	}
}

//
// 再生
//
- (void)play
{
	AudioOutputUnitStart(mOutUnit);
	mPlaying = YES;
}

//
// 停止
//
- (void)stop
{
	AudioOutputUnitStop(mOutUnit);
	mPlaying = NO;
}

//
// seek
//
- (void)seek:(SInt64)nPos
{
	OSStatus err = ExtAudioFileSeek( mAudioFileRef, nPos );
	if ( err != noErr ) {
		NSLog( @"ExtAudioFileSeek() failed. err=%d\n", err );
	}
	mCurFrame = nPos;
}

//
// ファイルハンドルの取得
//
- (ExtAudioFileRef)AudioFileRef
{
	return mAudioFileRef;
}

//
// カレントフレームの更新
//
- (void)incrementFrames:(UInt32)nFrames
{
	mCurFrame += (SInt64)nFrames;
	//NSLog( @"mFrameCount=%ld, mCurFrame=%ld\n", mFrameCount, mCurFrame );
}

//
// 全フレーム数
//
- (SInt64)frameCount
{
	return mFrameCount;
}

//
// 現在のフレーム数
//
- (SInt64)curFrame
{
	return mCurFrame;
}

//
// delegateオブジェクトの取得
//
- (id)delegate
{
	return mDelegate;
}

//
// Audio Unitの初期化
//
- (BOOL)initAU
{
	// デフォルト出力のAudio Unitの取得
	ComponentDescription cd;
	cd.componentType = kAudioUnitType_Output;
	cd.componentSubType = kAudioUnitSubType_DefaultOutput;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	
	Component comp = FindNextComponent(NULL, &cd);
	OpenAComponent(comp, &mOutUnit);
	
	// サンプリングレートの取得
	AudioStreamBasicDescription tASBD;
	UInt32 nSize = sizeof(tASBD);
	OSStatus err = AudioUnitGetProperty( mOutUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &tASBD, &nSize );
	if ( err != noErr ) {
		NSLog( @"AudioUnitGetProperty(kAudioUnitProperty_StreamFormat) failed. err=%d\n", err );
		return NO;
	}
	mSamplingRate = tASBD.mSampleRate;
	
	NSLog( @"mSampleRate=%f\n", tASBD.mSampleRate );
	NSLog( @"mFormatID=0x%x\n", tASBD.mFormatID );
	NSLog( @"mSampleRate=%d\n", tASBD.mFormatFlags );
	NSLog( @"mBytesPerPacket=%d\n", tASBD.mBytesPerPacket );
	NSLog( @"mFramesPerPacket=%d\n", tASBD.mFramesPerPacket );
	NSLog( @"mBytesPerFrame=%d\n", tASBD.mBytesPerFrame );
	NSLog( @"mChannelsPerFrame=%d\n", tASBD.mChannelsPerFrame );
	NSLog( @"mBitsPerChannel=%d\n", tASBD.mBitsPerChannel );
	
	// コールバック関数の登録
    AURenderCallbackStruct input;
	input.inputProc = RenderCallback;
	input.inputProcRefCon = self;
	
	AudioUnitSetProperty (mOutUnit,
						  kAudioUnitProperty_SetRenderCallback,
						  kAudioUnitScope_Input,
						  0,
						  &input,
						  sizeof(input));
	
	// Audio Unitの初期化
	AudioUnitInitialize(mOutUnit);
	
	return YES;
}

//
// Audio Fileのオープン
//
- (BOOL)openExtAudioFile:(NSString *)strPath
{
	NSURL *oURL = [NSURL fileURLWithPath:strPath];
	OSStatus err = ExtAudioFileOpenURL( (CFURLRef)oURL, &mAudioFileRef );
	if ( err ) {
		NSLog( @"ExtAudioFileOpenURL failed.(err=%d)\n", err );
		return NO;
	}

	UInt32 nSize = sizeof(SInt64);
	err = ExtAudioFileGetProperty( mAudioFileRef,
								  kExtAudioFileProperty_FileLengthFrames,
								  &nSize,
								  &mFrameCount );
	if ( err ) {
		NSLog( @"ExtAudioFileGetProperty failed.(err=%d)\n", err );
		return NO;
	}
	NSLog( @"Frame Count = %d\n", mFrameCount );

	AudioStreamBasicDescription tFormat;
	
	tFormat.mChannelsPerFrame = CHANNEL_NUM;
	
	tFormat.mSampleRate = mSamplingRate;
	tFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
	tFormat.mFormatID = kAudioFormatLinearPCM;
	tFormat.mBytesPerPacket = 4 * CHANNEL_NUM;
	tFormat.mFramesPerPacket = 1;
	tFormat.mBytesPerFrame = 4 * CHANNEL_NUM;
	tFormat.mBitsPerChannel = 32;
	
	nSize = sizeof(AudioStreamBasicDescription);
	err = ExtAudioFileSetProperty(mAudioFileRef,
								  kExtAudioFileProperty_ClientDataFormat, 
								  nSize,
								  &tFormat);
	if(err){
		NSLog( @"kExtAudioFileProperty_ClientDataFormat failed.(err=%d)\n", err );
		return NO;
	}

	return YES;
}

@end

●Application delegateクラス

アプリケーションの動きを制御するdelegateクラスのコードを以下に示します。

ExtAudioFilePlayAppDelegate.h:App. Delegateクラスインターフェースファイル
#import <Cocoa/Cocoa.h>
#import "UCExtAudioFilePlay.h"

@interface ExtAudioFilePlayAppDelegate : NSObject <NSApplicationDelegate> {
    NSWindow *window;
	NSTextField *outTxtPath;
	NSButton *outBtnPlayStop;
	NSSlider *outSldVol;
	NSSlider *outSldPos;

@private
	UCExtAudioFilePlay *mExtAudioFilePlay;
	BOOL	mPlaying;
	SInt64	mFrameCount;
}

@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet NSTextField *outTxtPath;
@property (assign) IBOutlet NSButton *outBtnPlayStop;
@property (assign) IBOutlet NSSlider *outSldVol;
@property (assign) IBOutlet NSSlider *outSldPos;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *) theApplication;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *) sender;

- (IBAction)onBtnChoose:(id)sender;
- (IBAction)onBtnPlay:(id)sender;
- (IBAction)onVolSliderDidChange:(id)sender;
- (IBAction)onPosSliderDidChange:(id)sender;

// delegate method
- (void)Increment:(SInt64)nCurFrame;
- (void)MusicDidEnd;

@end

ExtAudioFilePlayAppDelegate.m:App. Delegateクラスインプリメントファイル
#import "ExtAudioFilePlayAppDelegate.h"

@implementation ExtAudioFilePlayAppDelegate

@synthesize window;
@synthesize outTxtPath;
@synthesize outBtnPlayStop;
@synthesize outSldVol;
@synthesize outSldPos;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	// Insert code here to initialize your application 
	mExtAudioFilePlay = nil;
	mPlaying = NO;
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *) theApplication
{
	return YES;
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *) sender
{
	if ( mExtAudioFilePlay != nil ) {
		[mExtAudioFilePlay release];
	}

	return NSTerminateNow;
}

- (IBAction)onBtnChoose:(id)sender
{
	NSOpenPanel *oOpnPnl = [NSOpenPanel openPanel];
	int nResult = [oOpnPnl runModalForTypes:nil];
	if ( nResult == NSFileHandlingPanelOKButton ) {
		[outTxtPath setStringValue:[[oOpnPnl filenames] objectAtIndex:0]];
		NSFileManager *oFM = [NSFileManager defaultManager];
		if ( [oFM fileExistsAtPath:[outTxtPath stringValue]] != YES ) {
			NSBeep();
			return;
		}
		if ( mExtAudioFilePlay != nil ) {
			[mExtAudioFilePlay release];
		}
		mExtAudioFilePlay = [[UCExtAudioFilePlay alloc] initWithContentsOfFile:[outTxtPath stringValue] volume:[outSldVol floatValue] delegate:self];
		mFrameCount = [mExtAudioFilePlay frameCount];
		[outSldPos setMaxValue:mFrameCount];
	}
}

- (IBAction)onBtnPlay:(id)sender
{
	if ( !mPlaying ) {
		if ( [[outTxtPath stringValue] length] == 0 ) {
			NSBeep();
			return;
		}
		[mExtAudioFilePlay play];
		mPlaying = YES;
		[outBtnPlayStop	setTitle:@"Stop"];
	} else {
		[mExtAudioFilePlay stop];
		mPlaying = NO;
		[outBtnPlayStop	setTitle:@"Play"];
	}
}

- (IBAction)onVolSliderDidChange:(id)sender
{
	double dVal = [outSldVol doubleValue];
	[mExtAudioFilePlay setVolume:dVal];
}

- (IBAction)onPosSliderDidChange:(id)sender
{
	double dVal = [outSldPos doubleValue];
	[mExtAudioFilePlay seek:dVal];
}

- (void)Increment:(SInt64)nCurFrame
{
	[outSldPos setIntegerValue:nCurFrame];
}

- (void)MusicDidEnd
{
	[outSldPos setIntegerValue:mFrameCount];
	[self onBtnPlay:nil];
}

@end

このプログラムはNorihisa Naganoさんの「Getting Started With Audio Unit」を参考にさせていただきました。貴重な情報ありがとうございました。

サンプルプログラムのダウンロード(37KB)

【作成・確認環境】
MacOS X v10.6.4
Xcode v3.2.3

一覧に戻る