Archive

Archive for June, 2009

Analyse Audio with RemoteIO

June 18th, 2009


I have already explained how to record audio using AudioQueue's, however that method is really only practical for recording audio into a specific file, it doesn't let us drill down to sound samples and sound analysis. However RemoteIO is a lot more fragile than AudioQueue, and a lot less refined. Apple also provides limited documentation on its usage, it is however a good idea to look up Apples aurioTouch example code. This is however a bit involved, especially if you just want to master the basics first, although I will recommend it's fourier transform library. Anyway onto business, first thing we will need to do is define our variables in our interface:

@interface AudioRIO : NSObject
{
	AURenderCallbackStruct inputProc;
	AudioUnit rioUnit;
	AudioStreamBasicDescription audioFormat;
	AudioBufferList* bufferList;
	BOOL startedCallback;
}

OK now I'll explain what we've done here. The inputProc variable holds the information about the callback function we give to the RemoteIO system. The rioUnit is simply an identifier for the recording unit we have started. The audioFormat is much like the audioFormat in the AudioQueue tutorial, it defines information like the sample rate (but we'll come to that in a minute). The bufferList is a bufferList we shall allocate at the start to preventing allocation from happening in the audio callback, the reason for this is because RemoteIO does not take kindly to people using allocation methods during its callback (I have no idea why, but it slows down the entire program to a crawl for 9 seconds if you). startedCallback simply logs if our Audio Unit has started, and once it has we can begin audio analysis. Next we will define our callback method:

static OSStatus AudioRemoteIOInput(void* inRefCon,AudioUnitRenderActionFlags* ioActionFlags,const AudioTimeStamp* inTimeStamp,UInt32 inBusNumber,UInt32 inNumberFrames,AudioBufferList* ioData)
{
	AudioRIO* audioRIO = (AudioRIO*) inRefCon;
	ioData = audioRIO.bufferList;
	OSStatus result = AudioUnitRender(audioRIO.rioUnit,ioActionFlags,inTimeStamp,inBusNumber,inNumberFrames,ioData);
	if(audioRIO.startedCallback)
	{
		switch(result)
		{
			case noErr:
			{
				doSomethingWithAudioBuffer((SInt16*)audioRIO.bufferList->mBuffers[0].mData,inNumberFrames);
				break;
			}
			case kAudioUnitErr_InvalidProperty: NSLog(@"AudioUnitRender Failed: Invalid Property"); break;
			case -50: NSLog(@"AudioUnitRender Failed: Invalid Parameter(s)"); break;
			default: NSLog(@"AudioUnitRender Failed: Unknown (%d)",result); break;
		}
	}
	return noErr;
}

This method is our callback method, it will be called every time RemoteIO has filled its buffer, and will then pass the buffer along to us for us to do what we want with it. You have to be very careful with the AudioUnitRender, and if your having any problems in your code make sure you check the logs to see if it has returned noErr (no error). Now we have to add our interruption listeners and our property listeners, if we don't at least give them somewhere to point to the RemoteIO becomes very unstable:

void rioInterruptionListener(void* inClientData, UInt32 inInterruption)
{
	NSLog(@"rioInterruptionListener");
}
 
void propListener(void* inClientData,AudioSessionPropertyID inID,UInt32 inDataSize,const void* inData)
{
	NSLog(@"propListener");
}

As you can see they do nothing (though I have lately been thinking to reroute audio through them, like the aurioTouch example does), but my applications have worked fine so far using this method. So now we have to initialise our audio units:

-(id) init
{
	[super init];
	startedCallback = NO;
	inputProc.inputProc = AudioRemoteIOInput;
	inputProc.inputProcRefCon = self;
	AudioComponentDescription desc;
	desc.componentType = kAudioUnitType_Output;
	desc.componentSubType = kAudioUnitSubType_RemoteIO;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	AudioSessionInitialize(NULL,NULL,rioInterruptionListener,self);
	AudioSessionSetActive(true);
	UInt32 audioCategory = kAudioSessionCategory_RecordAudio;
	AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,sizeof(audioCategory),&audioCategory);
	AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,propListener,self);
	AudioComponent inputComponent = AudioComponentFindNext(NULL,&desc);
	if(AudioComponentInstanceNew(inputComponent,&rioUnit) == noErr)
	{
		UInt32 flag = 1;
		if(AudioUnitSetProperty(rioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Input,kInputBus,&flag,sizeof(flag)) == noErr)
		{
			flag = 0;
			AudioUnitSetProperty(rioUnit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Output,kOutputBus,&flag,sizeof(flag));
			audioFormat.mSampleRate = 44100.00;
			audioFormat.mFormatID = kAudioFormatLinearPCM;
			audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
			audioFormat.mFramesPerPacket = 1;
			audioFormat.mChannelsPerFrame = 1;
			audioFormat.mBitsPerChannel	= 16;
			audioFormat.mBytesPerPacket	= 2;
			audioFormat.mBytesPerFrame = 2;
			if(AudioUnitSetProperty(rioUnit,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output,kInputBus,&audioFormat,sizeof(audioFormat)) == noErr)
			{
				if(AudioUnitSetProperty(rioUnit,kAudioOutputUnitProperty_SetInputCallback,kAudioUnitScope_Global,kInputBus,&inputProc,sizeof(inputProc)) == noErr)
				{
					UInt32 allocFlag = 1;
					if(AudioUnitSetProperty(rioUnit,kAudioUnitProperty_ShouldAllocateBuffer,kAudioUnitScope_Input,1,&allocFlag,sizeof(allocFlag)) == noErr)
					{
						if(AudioUnitInitialize(rioUnit) == noErr)
						{
							bufferList = (AudioBufferList*) malloc(sizeof(AudioBufferList));
							bufferList->mNumberBuffers = audioFormat.mChannelsPerFrame;
							for(UInt32 i=0;i<bufferList->mNumberBuffers;i++)
							{
								bufferList->mBuffers[i].mNumberChannels = 1;
								bufferList->mBuffers[i].mDataByteSize = (1024*2) * audioFormat.mBytesPerFrame;
								bufferList->mBuffers[i].mData = malloc(bufferList->mBuffers[i].mDataByteSize);
							}
							if(AudioOutputUnitStart(rioUnit) == noErr) // Takes a long time
							{
								NSLog(@"AudioRemoteIO Started");
								startedCallback = YES;
							}
							else NSLog(@"Failed AudioOutputUnitStart");
						}
						else NSLog(@"Failed AudioUnitInitialize");
					}
					else NSLog(@"Failed AudioUnitSetProperty ShouldAllocateBuffer");
				}
				else NSLog(@"Failed AudioUnitSetProperty inputProc");
			}
			else NSLog(@"Failed AudioUnitSetProperty");
		}
		else NSLog(@"Failed AudioUnitSetProperty");
	}
	else NSLog(@"Failed AudioComponentInstanceNew");
	return self;
}

So here is where we do all the allocating and initialising for our RemoteIO process. Once startedCallback is equal to YES then we have a functioning RemoteIO that is calling our callback method regularly with buffers full of sound samples. So only 1 more thing to do now, and that's learn how to shut down the RemoteIO without causing any unstability in the program. I generally add a terminate method to my class, but it doesn't matter what you name it as long as you simply call these 2 functions in it:

AudioOutputUnitStop(rioUnit);
AudioUnitUninitialize(rioUnit);

Now we have a nice stable RemoteIO unit giving us updates on recorded samples whenever the buffers fill up. This enables us to do processing on the samples, which can give us a range of rich applications.

Author: admin Categories: Tutorials Tags:

Playing Audio Files using AVAudioFoundation

June 12th, 2009


There are many instances in which you will need to play audio files which are longer than 30 seconds, and may or may not WAV files. To do this you will need AVAudioFoundation, it provides a nice high level interface which you can use to play compressed media files, and get information about them. First you will need to import the AVAudioFoundation (and add it as a framework to):

#import <AVFoundation/AVFoundation.h>

If you want to make use of the delegation functionality you will have to comply with the protocol AVAudioPlayerDelegate, so add that to your class. Now you will want to keep the AVAudioPlayer variable around so you can repeat it, or track its progression through the audio file, so simply declare it as an instance variable in your class:

AVAudioPlayer* player;

Now we must invoke the methods to play our audio file, to do that we must first locate the media file we want to play, get the path into an NSString then use this code to make a new AVAudioPlayer object:

NSError* playerError = nil;
player = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:mediaFile] error:&playerError];

So it's all over now right? Wrong. We still have a couple more methods and variables to invoke and attributes to modify. First we will make sure the player delegate corresponds to our class, to do this we just make the delegate attribute equal the self value in our class:

player.delegate = self;

After this it is always a good idea to call prepareToPlay on the player variable, this prematurely reads the media file and doesn't make the play call suspend while it is prematurely reading the media file.

[player prepareToPlay];

Now to actually play the file, all we have to do is call the play method on the player variable:

[player play];

This method actually returns a bool, that tells us whether the play succeeded or failed. It is always a good idea to check this, because API changes a lot between different iPhone OS's, I myself have had problems with this. Another method which may be handy to you is the pause method, this simply pauses the player at a certain point in time:

[player pause];

Now the player also has interesting variables that we could use, these variables are duration and currentTime. The duration is the number of seconds the audio file is, and the currentTime is the current second the audio file is playing on. Lastly we will need to implement the method in compliance with the AVAudioPlayerDelegate:

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
}

This gets called when the audio file has come to an end, and it even tells you if it was successful or not.

Author: admin Categories: Tutorials Tags:

Transitioning Views

June 11th, 2009


If you have an app with multiple views, you may want to provide a smooth transition when you switch the views, rather than just do a straight switch by adding or removing subviews. To do this we need to use animation, which can be a bit of a tricky subject, but luckily Apple have made a function in their ViewTransitions demonstration project, which allows us to easily switch between our views with grace.

// Method to replace a given subview with another using a specified transition type, direction, and duration
- (void)replaceSubview:(UIView *)oldView withSubview:(UIView *)newView transition:(NSString *)transition direction:(NSString *)direction duration:(NSTimeInterval)duration
{
	if(oldView == newView)
		return;
	if(transitioning)
		return;
	NSArray *subViews = [self subviews];
	NSUInteger index;
	if([oldView superview] == self)
	{
		for(index = 0; [subViews objectAtIndex:index] != oldView; ++index) {}
		[oldView removeFromSuperview];
	}
	if (newView && ([newView superview] == nil))
		[self insertSubview:newView atIndex:index];
	CATransition *animation = [CATransition animation];
	[animation setDelegate:self];
	if (transition == kCATransitionFade)
	{
		[animation setType:kCATransitionFade];
	}
	else
	{
		[animation setType:transition];
		[animation setSubtype:direction];
	}
	[animation setDuration:duration];
	[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
	[[self layer] addAnimation:animation forKey:kAnimationKey];
	currentView = newView;
}

This code basically does all the work for us, we just need to call it and give it some instance variables to play with, first we'll focus on the instance variables:

BOOL transitioning;
UIView* currentView

This variable transitioning basically checks whether we are currently transitioning our views, and will wait till we stop the animation before proceeding to change the view again. The currentView method keeps track of what view we are currently residing on, so we can replace it properly. Now we simply have to call the replaceSubview method, and we can do that like so:

[self replaceSubview:currentView withSubview:homeView transition:kCATransitionMoveIn direction:kCATransitionFromRight duration:0.75];

This code will replace whatever view I currently have with my homeView, using the move in animation from the right taking 0.75 seconds to complete. Now to be sure we don't get bugs in our code, we must properly initialise the currentView variable, I usually do this in the awakeFromNib method:

currentView = homeView;

Now we can choose from more than 1 animation style, and more than 1 direction, the different animations we have at our display are:
kCATransitionPush
kCATransitionReveal
kCATransitionFade
While the different directions are:
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
Now you should have enough to customise your view transitions. I highly recommend everyone perform nice view transitions, as counter productive as they are in a programming sense, they give your application a sense of professionalism and a good look to boot, which is what app developers should be prioritising in the superficial world of the app store.

Author: admin Categories: Tutorials Tags:

Non-Sectioned Table View

June 11th, 2009


To create a non-sectioned table view you must first insert a table view into your view. Once you have done this, set its delegate to a class you have defined. We will now try to create a user interface like the one below:

You must first add the delegate protocol to the top of your class:

@interface LogView : UIView 

Now that this is done, we must implement our DataSource methods to display information in the table. The first method is numerOfSectionsInTableView, which tells the UITableView how many sections there are in this table. Now since we are dealing with a standard table, there is only 1 section, that being the main table.

-(NSInteger) numberOfSectionsInTableView:(UITableView*)tableView
{
	return 1;
}

The next is numberOfRowsInSection, this is where we get to define how many rows are table is going to take up, now the table we are trying to recreate above has 4 rows, so we will simply return 4 to indicate this:

-(NSInteger) tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
	return 4;
}

The last method we have to implement is cellForRowAtIndexPath, this will return the UITableViewCell at the a specific row. We get the row by querying indexPath.row, and we then return the text based on this.

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	switch(indexPath.row)
	{
		case 0: return @"iPhone";
		case 1: return @"iPod";
		case 2: return @"MacBook";
		case 3: return @"MacBook Pro";
	}
	return @"Unknown Table";
}

And that is all you need to do to make a very simple non-sectioned table view.

Author: admin Categories: Tutorials Tags:

iPhone Date Strings

June 9th, 2009


To get the current date of the iPhone, you must invoke 2 things. The first being NSDate and the second being NSDateFormatter. The NSDateFormatter lets you specify the way the date is output into an NSString, and the NSDate holds the date which the NSDateFormatter reads, to make it all come together you can use the following code:

NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm"];
NSString* dateString = [formatter stringFromDate:[NSDate date]];
Author: admin Categories: Tutorials Tags:

Cones and Rods

June 7th, 2009

appstore
Cones and Rods is an application that tests, simulates and corrects colour vision.

This application can not only determine whether you currently suffer from colour blindness, it can also determine what type of colour blindness you suffer from.

You can see the world from the eyes of the colour blind using the simulator, you can simulate colour blindness on any deficiency type using either any photo in your photo library or the iPhone's camera. The following colour blindness deficiencies are supported:
- Protanopia/Protanomaly (Red Deficient)
- Deuteranopia/Deuteranomaly (Green Deficient)
- Tritanopia/Tritanomaly (Blue Deficient)
- Achromatopsia/Achromatomaly (Monochromacy)

The correction function allows colour blind people to accurately identify colours by converting hues to there more basic levels. This allows colour blind people to accurately identify numbers in Ishihara tests and other real world situations. The following colour blindness deficiencies are supported:
- Protanomaly (Red Deficient)
- Deuteranomaly (Green Deficient)
- Tritanomaly (Blue Deficient)

Note to iPod Touch users: You will not be able to access the Camera views, and will only be able to simulate/correct colour vision using your Photo Library.

Support: admin@iwillapps.com
Status: On Sale
Version: 1.0
Screenshots:

Author: admin Categories: Apps Tags:

Getting Audio Volume/Power

June 1st, 2009


Getting the Audio Power coming in through the microphone is quite a handy feature in many audio based applications. The information relates the noise of the audio, and is often measured in Decibels. To get the Decibel measurement of sound, you will need to apply the kAudioQueueProperty_EnableLevelMetering property to your designated audio queue. This is very easy, simply set the property like so:

UInt32 enabledLevelMeter = true;
AudioQueueSetProperty(queue,kAudioQueueProperty_EnableLevelMetering,&enabledLevelMeter,sizeof(UInt32));

Now that we have enabled metering, we need to get the power values for the channel, to do this we will need to obtain the AudioQueueLevelMeterState structure from the audio queue:

AudioQueueLevelMeterState levelMeter;
UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);
AudioQueueGetProperty(queue,kAudioQueueProperty_CurrentLevelMeterDB,&levelMeter,&levelMeterSize);

Now that we have the levelMeter variable, we can find the Average Power and the Peak Power:

Float32 peakDB = levelMeter.mPeakPower;
Float32 averageDB = levelMeter.mAveragePower;

You'll note that this will give you values such as -60.0, the lower these values get the louder the audio is becoming. 60 Decibels represents the noise of someone talking normally into the iPhone, 20 Decibels would be almost no noise and 80 Decibels is someone raising their voice into the microphone. But what if we don't use AudioQueues? What if we are using a RemoteIO interface to record the audio, so we can have access to the individual samples? Then you have to do the Decibel calculation manually, as there is no way to do it using standard API. So to do the Decibel calculation manually (assuming SInt16 samples) you can do this:

Float32 totalDB = 0.0f;
for(int i=0;i<length;i++)
{
	Float32 tmpDB = -20.0*log10(w[i]);
	if(tmpDB < totalDB)
		totalDB = tmpDB;
}
return totalDB;

Where length is the number of samples and w is an array containing your samples. And that's all you'll need to know about implementing audio levels on the iPhone.

Author: admin Categories: Tutorials Tags: