I’m trying to make a small iPhone application with some buttons to play WAV sounds.
My buttons works, but I have a small latency (~ 0,5 sec).
This is my .m file :
#import "buttonSoundViewController.h"
#implementation buttonSoundViewController
//#synthesize player;
-(IBAction) playSoundA:(id)sender{
NSString *path = [[NSBundle mainBundle] pathForResource:#"a" ofType:#"wav"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
-(IBAction) playSoundB:(id)sender{
NSString *path = [[NSBundle mainBundle] pathForResource:#"b" ofType:#"wav"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
[player release];
}
-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
}
-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {
}
-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player {
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
}
- (void)dealloc {
[audioPlayer release];
[super dealloc];
}
#end
How can I avoid this latency between playing different sounds?
Using AudioServices is indeed much quicker for small sounds (less than 30 seconds). The required code isn't very long either (but it requires some good old C).
#import <AudioToolbox/AudioServices.h>
SystemSoundID soundID = 0;
NSString* str = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
CFURLRef soundFileURL = (CFURLRef)[NSURL URLWithString:str ];
OSStatus errorCode = AudioServicesCreateSystemSoundID(soundFileURL, &soundID);
if (errorCode != 0) {
// Handle failure here
}
else
AudioServicesPlaySystemSound(soundID);
Also you can optimize your sounds (reduce their size) with the following terminal command:
afconvert mysound.caf mysoundcompressed.caf -d ima4 -f caff
You could simplify things a lot by using system sounds. Look up: AudioServicesCreateSystemSoundID in the documentation. There is also a "System Sound Services Reference Document" that talks about that and other related functions. This is a simple and efficient way to play short sounds. Not sure if it will solve your latency issues but its a good start. You may also try using some different sound file types. Perhaps there is an issue with how it was or was not compressed.
Maybe you could look at AppSoundEngine. It addresses latency and greatly simplifies using of System Sound Services, because it is objective-c wrapper for SystemSoundID and associated C functions.
You can not get acceptable latency (<10 ms) from AVAudioPlayer. System Sound Services is the way to go.
From the Apple Docs grab the SoundEffect class in project BubbleLevel
SoundEffect is a simple Objective-C wrapper around Audio Services
functions that allow the loading and playing of sound files.
Classes/SoundEffect.m
this will make playing files as easy as
SoundEffect *soundEffect = [SoundEffect soundEffectWithContentsOfFile:#""]
[soundEffect play];
it will also handle memory deallocation. AudioServicesDisposeSystemSoundID(soundID);
One simple fix would just be to do the AVAudioPlayer alloc init for the 2 sounds in your buttonSoundViewController's init method. Then those 2 audio players will already be ready to play in your button delegates.
The fastest way to play sounds is to use the RemoteIO Audio Unit, but that's a far more advanced and complicated looking API.
Related
I've searched and I believe my problem is quite unique. I'm aware of the Simulator 5.1 bug when using AVAudioPlayer which isn't my problem. I'm running on a iOS 5.1 device.
Here's my header file:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVAudioPlayer.h>
-(IBAction)pushBell;
#end
and my implementation file:
#import "BellViewController.h"
#interface BellViewController ()
#end
#implementation BellViewController
-(IBAction)pushBell {
NSString *soundPath =[[NSBundle mainBundle] pathForResource:#"bell1" ofType:#"caf"];
NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
NSError *error;
AVAudioPlayer *theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
[theAudio setDelegate:self];
[theAudio setNumberOfLoops:0];
[theAudio play];
}
#end
And I have a button in my .xib that plays the file.
To make sure I referenced my audio file correctly, I deliberately type a wrong file name and indeed I got an exception. To make sure the file is playable, I mailed it to myself and played it on the device.
When I tap the button I hear nothing. There are no errors. I don't know what is wrong.
More information
iPhone 4 with iOS 5.1
Xcode 4.3.2
Audio is about 700kbps converted to .caf format using afconvert from a big .wav file.
SOLVED.
1.- Check if your file has been added to "Copy Bundle Resources" in Build Phases.
2.- ViewController.m
#import <AVFoundation/AVAudioPlayer.h>
#interface ViewController ()
#property (nonatomic, strong) AVAudioPlayer *theAudio;
- (IBAction)pushBell;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *soundPath =[[NSBundle mainBundle] pathForResource:#"beep-beep" ofType:#"caf"];
NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
NSError *error = nil;
self.theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
}
- (IBAction)pushBell {
[self.theAudio play];
}
3.- Connect the IBAction to your button in Interface Builder.
Done.
This was my issue, so I'm just posting it here in case it helps anyone!
Believe it or not, there appears to be a bug (in iOS 5.1 on iPad 2 at least) where if you had the Side Switch set to Mute and it was Muted, then set the Side Switch to Lock Rotation, AVAudioPlayer sounds WON'T PLAY except through headphones!
So I had to go to the Settings app, change the switch back to Mute, then unmute the iPad, switch it back to Lock Rotation and my app started to work properly.
Your theAudio AVAudioPlayer is deallocated before it can even start playing (since it's allocated as a local variable inside pushBell method.
Change your code to:
header file:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVAudioPlayer.h>
#interface BellViewController:UIViewController
#property (nonatomic, strong) AVAudioPlayer *theAudio;
-(IBAction)pushBell;
#end
implementation file:
#import "BellViewController.h"
#implementation BellViewController
#synthesize theAudio = _theAudio;
- (void)viewDidLoad {
if (!theAudio) {
NSString *soundPath =[[NSBundle mainBundle] pathForResource:#"bell1" ofType:#"caf"];
NSURL *soundURL = [NSURL fileURLWithPath:soundPath];
NSError *error;
_theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
[self.theAudio setDelegate:self];
[self.theAudio setNumberOfLoops:0];
[self.theAudio prepareToPlay];
}
}
-(IBAction)pushBell {
[self.theAudio play];
}
#end
I'm assuming that BellController is a UIViewController, if it's a different kind of class just modify the code accordingly.
I think that it is a memory issue, you should instantiate the AVAudioPlayer *theAudio as a global class object and initialise it in the init method. Then just run the [theAudio play]; method in the -(IBAction)pushBell method.
I responded do a similar issue related to playing a system sound (which in fact is what you are trying to do), check it out my response is at the bottom:
Couldn't play system sound after switching to iOS 5
By the way consider playing the bell as a system sound, AVAudioPlayer is more appropriate to play sound tracks, not bells and effects.
I hope that it helps.
I agree with hooleyhoop. Try
NSLog(#"%#", error.userInfo)
to see what's coming out.
Another alternative would be to set up the player object in either viewDidLoad or initWithNib and storing it as a property on your object. This way you can use something like
[self.theAudio prepareToPlay];
To avoid loading up the audio each time you press the button, which could save you some time if it's high quality audio. Then just call [self.theAudio play]; in you IBAction method.
Sorry, if this answer is a little sloppy - typing on an iPad so can't double check properly.
You should give it some time to prepare the pronunciation since it's probably buffering from a URL.
The code:
[theAudio prepareToPlay];
[self performSelector:#selector(playMusic) withObject:nil afterDelay:3.0];
Hi I want to play a mp3 file via my server e.g. http://test.com/hi.mp3
At the moment the code plays the file if its in the directory of the code.
The code also enables only 1 sound at a time.
- (IBAction)oneSound:(id)sender; {
NSString *path = [[NSBundle mainBundle] pathForResource:#"1" ofType:#"mp3"];
if (theAudio) [theAudio release];
NSError *error = nil;
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
if (error)
NSLog(#"%#",[error localizedDescription]);
theAudio.delegate = self;
[theAudio play];
}
However this code here enables me to play the sound through the http server, but I can play multiple sounds at once, I need the sound in sessions so only 1 sound can play at once. I have 10 sounds.
- (IBAction)oneSound:(id)sender; {
AVPlayer *player = [[AVPlayer playerWithURL:[NSURL URLWithString:#"http://www.mysite.com/hi.mp3"]] retain];
[player play];
}
My suggestion would be to move the pointer for your player from being declared in that method so that it is declared at a module level (in the .h file) - either just defined in the interface, or defined as a #property. Then you can access this player in another method later.
Then when you wish to switch to a new sound in another method you could try:
[player pause]; // stop the player from playing
[player release]; // free the reference count
// start a new plaer
player = [[AVPlayer playerWithURL:
[NSURL URLWithString:#"http://www.mysite.com/nextsound.mp3"]] retain];
You should be careful with the 'retain' call here. The playerWithURL will be passing back an autoreleased object, so depending on what you are doing elsewhere with autorelease pools, and depending on whether you are using a property including (retain) in its definition, you may not need to call retain here.
I have an app I am working on and It uses some of the hardware sensors to provide data on screen, there is a Label that updates with the number. I want a sound to play whenever the number is above 100 or something. For example, say it was reading numbers then all of the sudden it finds a good spot (or whatever), then I would like a sound to play or a light to light up. I am an absolute beginner and it would be nice if the answer would be easy for a absolute beginner to understand.
I am using the system AudioToolbox.framework for playing sounds in my simple game. I added this static function to common MyGame class:
+ (SystemSoundID) createSoundID: (NSString*)name
{
NSString *path = [NSString stringWithFormat: #"%#/%#",
[[NSBundle mainBundle] resourcePath], name];
NSURL* filePath = [NSURL fileURLWithPath: path isDirectory: NO];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)filePath, &soundID);
return soundID;
}
I added the "Morse.aiff" file to project Resources and initialized it in (any) class initialization with the following:
self.mySound = [MyGame createSoundID: #"Morse.aiff"];
And then I played sound with this call:
AudioServicesPlaySystemSound(mySound);
Also, don't forget to import AudioServices.h file.
This audio toolbox can play also different sound formats.
h
#import <AVFoundation/AVFoundation.h>
#interface CMAVSound : NSObject {
AVAudioPlayer *audioPlayer;
}
- (id)initWithPath:(NSString*)fileNameWithExctension;
- (void)play;
#end
m
#import "CMAVSound.h"
#implementation CMAVSound
-(void)dealloc {
[audioPlayer release];
}
- (id)initWithPath:(NSString*)fileNameWithExctension {
if ((self = [super init])) {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], fileNameWithExctension]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (audioPlayer == nil) {
NSLog(#"%#", [error description]);
}
}
return self;
}
- (void)play {
[audioPlayer play];
}
#end
Check out the documentation for the AVAudioPlayer class. It allows you to play sounds clips. If you have troubles implementing that, show us some code.
If the sounds gonna up to 5 seconds and no stereo output is needed I would recommend you to do that with system sounds. It is easy and better solution then any other.
Apple sample code is provided under name SysSound
EDIT1
Or maybe tutorial could help you more
http://howtomakeiphoneapps.com/2009/08/how-to-play-a-short-sound-in-iphone-code/
Take a look at following links;
http://blog.guvenergokce.com/avaudioplayer-on-iphone-simulator/57/
http://mobileorchard.com/easy-audio-playback-with-avaudioplayer/
Here you can find list of AudioServicesPlaySystemSound
http://iphonedevwiki.net/index.php/AudioServices
-(IBAction)playSound{ AVAudioPlayer *myExampleSound;
NSString *myExamplePath = [[NSBundle mainBundle] pathForResource:#"myaudiofile" ofType:#"caf"];
myExampleSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:myExamplePath] error:NULL];
myExampleSound.delegate = self;
[myExampleSound play];
}
I want to play a beep sound when a button is clicked. I had used the above code. But it is taking some delay in playing the sound.
Anyone please help.
There are two sources of the delay. The first one is bigger and can be eliminated using the prepareToPlay method of AVAudioPlayer. This means you have to declare myExampleSound as a class variable and initialize it some time before you are going to need it (and of course call the prepareToPlay after initialization):
- (void) viewDidLoadOrSomethingLikeThat
{
NSString *myExamplePath = [[NSBundle mainBundle]
pathForResource:#"myaudiofile" ofType:#"caf"];
myExampleSound =[[AVAudioPlayer alloc] initWithContentsOfURL:
[NSURL fileURLWithPath:myExamplePath] error:NULL];
myExampleSound.delegate = self;
[myExampleSound prepareToPlay];
}
- (IBAction) playSound {
[myExampleSound play];
}
This should take the lag down to about 20 milliseconds, which is probably fine for your needs. If not, you’ll have to abandon AVAudioPlayer and switch to some other way of playing the sounds (like the Finch sound engine).
See also my own question about lags in AVAudioPlayer.
AudioServicesPlaySystemSound is an option. Tutorial here, sample code here.
Considering the code:
soundFilePath = [[NSBundle mainBundle] pathForResource: #"Sound" ofType: #"wav"];
fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
avPlayerNextLevel = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error: nil];
avPlayerNextLevel1.volume = volume ;
[soundFilePath release];
[fileURL release];
To play the sound I then do
if([avPlayerNextLevel prepareToPlay]){
[avPlayerNextLevel play];
}
I do this many times within my game. Sometimes on the simulator , the sound stops playing. And when sometimes I detect memory leaks with AvAudioPlayer.
Does everything look alright with my code ?
You'll need to release the instance of AVAudioPlayer that you created. Since the above code sets the delegate to self, just implement the following method and release there:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
NSLog(#"Audio finished playing.");
[player release];
}
Why are you releaseing a non-owning reference (soundFilePath)?
You don't need to manually prepareToPlay if you're going to invoke play right after.
Quoting the documentation for play:
Discussion
Calling this method implicitly calls the prepareToPlay method if the audio player is not already prepared to play.
Personally, I do this:
NSURL *soundUrl = [NSURL fileURLWithPath:
[[NSBundle mainBundle] pathForResource:track
ofType:nil]];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl
error:nil];
[audioPlayer setDelegate:self];
[audioPlayer play];
Where audioPlayer is an ivar and track is an lvar of type NSString* which I obtained earlier from wherever I configure the next song to play.
I've been working on something similar but I was using a slightly different piece of code. Unfortunately, I got memory leaks, therefore I decided to give a try to the code you posted. Same result: memory leaks again when I invoke [audioPlayer play];
I can't really figure out what's going on. Are you guys aware of any memory leaks when AVAudioPlayer is involved?