I have an application which has two kinds of sounds:
1. Background sounds, looping forever till next view.
2. Sounds clips that could be played separately or all in one go.
Previously i tried using AVAudioPlayer but there was always a very little silence interval between two clips (to which i didn't find any solution, if you have one, please post in reply), so shifted to Finch e.g. http://github.com/zoul/Finch/tree/master
Now here is the deal:
I am using AVAudioPlayer to play looping forever sounds while Finch to play those back-to-back clips. I am doing this:
- (void)viewDidLoad {
[super viewDidLoad];
soundEngine = [[Finch alloc] init];
/***
CLIPPED
***/
}
-(void) playclip:(id) sender {
NSString *fullPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"sitar.wav"];
NSLog(#"Song is %#", fullPath);
clip = [[Sound alloc] initWithFile:fullPath];
NSLog(#"Clip Length: %#", clip.length);
NSLog(#"Clip Playing ? : %d", clip.playing);
[clip play];
NSLog(#"Played Clip");
}
This is almost same code as the demo project provided as in the git repository. But for some reason i get this on console:
Song is /Users/shoaibi/Library/Application Support/iPhone Simulator/User/Applications/17FBA26F-6047-4D56-9E45-ADAFE07B7234/Test.app/sitar.wav
Failed to create OpenAL source, error code a004.
Clip Length: (null)
Clip Playing ? : 0
Played Clip
An ls on the directory printed on console reveals that there is sitar.wav there with the correct permissions. What could be the issue? may be the background sound AVAudioPlayer which i stop using:
- (void)viewWillAppear:(BOOL)animated {
//Stop the background music before my ears bleed :P
d = [[UIApplication sharedApplication] delegate];
if (d.bg.playing)
{
[d.bg stop];
NSLog(#"Background music stoped");
}
}
is not freeing up sound resource?
Latest Finch now checks whether OpenAL is initialized when you initialize a new Sound. If current OpenAL context is not set because you forgot or failed to initialize Finch, you get a better error message. Give it a try, it might be the case that your initialization code is buggy.
I got the same error when I called the sound clip allocs in my viewWillAppear - seems it was called before the awakeFromNib finished doing its stuff. Anyway, shifting the allocs so they happened later fixed it.
Related
I am creating an application which displays a certain video based on an external event, which may require the playing video to change quickly - once per second or more. However, there must not be a gap or lag between the videos.
What is the best way to do this? There are only four videos, each about two megabytes.
I was considering creating four MPMoviePlayerControllers, and have their views added to the main view but hidden, and switching by pausing and hiding the current video, then unhiding and playing the next video. Is there a more elegant solution?
Edit
Here's some more information for my exact sitaution:
The different video frames share mostly common pixels- so it's OK for a frame to stick during switch, but NOT okay for black frames to appear.
Each video is only about ten seconds long, and there are only four videos. The general state transitions are 1<->2<->3<->4->1.
The video playback should compatible with simultaneous AVAudioRecorder recording. As far as I can tell, MPMoviePlayerController is not.
You'll need to set up and prebuffer all the video streams to avoid hiccups, so I don't think your multiple MPMoviePlayerController solution is too far off the mark.
However, that specific solution is potentially problematic because each movie player has its own UI. The UIs do not synchronize with each other, so one might be showing the control bar, another not; one might be in full screen mode, etc. Switching among them will cause visual discontinuities.
Since it sounds like your video switching is not necessarily user-initiated, I'm guessing you don't care too much about the standard video player UI.
I would suggest dropping down to the underlying layer, AV Foundation. In theory, you can just create an AVPlayerItem for each video. This is a stream-management object with no UI overhead, so it's perfect for what you're doing. You could then -- again, in theory -- create one AVPlayer and one AVPlayerLayer to handle the display. When you wanted to switch from one AVPlayerItem stream to another, you could call the AVPlayer's replaceCurrentItemWithPlayerItem: message to swap out the data stream.
I made a little test project (GitHub) to verify this, and unfortunately the straightforward solution isn't quite perfect. There is no video flow glitching, but in the transition from AVPlayer to AVPlayer, the presentation layer seems to briefly flash the previous movie's last-seen frame at the size appropriate to the next movie. It seems to help to allocate separate AVPlayer objects for each movie and switch among them to a constant player layer. There still seems to be an instantaneous flash of background, but at least it's a subtle defect. Here's the gist of the code:
#interface ViewController : UIViewController
{
NSMutableArray *players;
AVPlayerLayer *playerLayer;
}
#property (nonatomic) IBOutlet UIView *videoView;
- (IBAction) selectVideo:(id)sender;
#end
#implementation ViewController
#synthesize videoView;
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *videoTitles = [NSArray arrayWithObjects:#"Ultimate Dog Tease",
#"Backin Up", #"Herman Cain", nil];
players = [NSMutableArray array];
for (NSString *title in videoTitles) {
AVPlayerItem *player = [AVPlayer playerWithURL:[[NSBundle mainBundle]
URLForResource:title withExtension:#"mp4"]];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
[players addObject:player];
}
playerLayer = [AVPlayerLayer playerLayerWithPlayer:[players objectAtIndex:0]];
playerLayer.frame = self.videoView.layer.bounds;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.videoView.layer addSublayer:playerLayer];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
[object removeObserver:self forKeyPath:#"status"];
for (AVPlayer *player in players) {
if (player.status != AVPlayerStatusReadyToPlay) {
return;
}
}
// All videos are ready to go
[self playItemAtIndex:0];
}
- (void) playItemAtIndex:(NSUInteger)idx
{
AVPlayer *newPlayer = [players objectAtIndex:idx];
if (newPlayer != playerLayer.player) {
[playerLayer.player pause];
playerLayer.player = newPlayer;
}
[newPlayer play];
}
- (IBAction) selectVideo:(id)sender
{
[self playItemAtIndex:((UILabel *)sender).tag];
}
#end
Half the code is there just to observe the state of the players and make sure that playback doesn't start until all videos have been buffered.
Allocating three separate AVPlayerLayers (in addition to three AVPlayers) prevents any sort of flash. Unfortunately, an AVPlayer connected to an undisplayed AVPlayerLayer seems to assume that it doesn't need to maintain a video buffer. Every switch among layers then produces a transient video stutter. So that's no good.
A couple of things to note when using AV Foundation are:
1) The AVPlayer object doesn't have built-in support for looped playback. You'll have to observe for the end of the current video and manually seek to time zero.
2) There's no UI at all other than the video frame, but again, I'm guessing that this might actually be an advantage for you.
The MPMoviePlayerController is a singleton. Four instances will share the same pointer, the same view, etc. With the native player, I think you have only two alternatives: one is to change the contentURL property when you want a transition. If the latency this way unacceptable, the other alternative is to produce a longer video with the shorter clips concatenated. You can create very quick jumps within the single, longer clip by setting currentPlaybackTime.
I ran audio in the background in my iPhone app using AVAudioPlayer and AVAudioSession...
Now my problem is that I already have a written code that takes a number of songs as input from the user and plays them one after the other using AVAudioPlayer...
The problem is that when the app goes to the background while one of the files is being played, the audio continues but when it's done, the sound disappears... In foreground when a file is done, I just do some code to play the next one but in the background this is not possible...
I thought about requesting some time to get this done and run my code but I can't do this as the audio files always change and they might be long... So please tell me what should I do? Is there any built-in playlist player that run as a background audio? or is there any other solution? I really need to get this into work...
thanks a lot
I ran into the same issue and haven't completely resolved it yet. The audioDidFinishPlaying delegate method still fires, and you can use UIAplication's beginBackgroundTask to create a new instance of AVaudioPlayer, make sure AVAudioSession is still active, etc. But when I try to play the new AVAudioPlayer instance, it pauses immediately. My suspicion/fear is that there is no way to start the audio FROM the background, only keep it playing when the application state transition happens. I'd love to be wrong, though.
this is easy ..just change the cateogry if your using an audio session that is ...
http://developer.apple.com/library/ios/#documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionCategories/AudioSessionCategories.html
the link has the categories ...so use one of these and you be able to start the sound in the background:
AVAudioSessionCategoryPlayback
kAudioSessionCategory_MediaPlayback
the ambient one might not work.
//ViewController.h ,write below code
#interface ViewController : UIViewController<AVAudioRecorderDelegate,AVAudioPlayerDelegate>
//assign property to player
#property(nonatomic,retain) AVAudioPlayer *player;
//then write in ViewController.m file in ViewDidLoad Method below code
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
NSError *soundError;
NSString *path=[[NSBundle mainBundle]pathForResource:#"soundFileName" ofType:#"mp3"]; //.mp3 file for player
NSURL *file=[[NSURL alloc]initFileURLWithPath:path]; //path
_player=[[AVAudioPlayer alloc]initWithContentsOfURL:file error:&soundError]; //player Object
if(_player == nil)
{
NSLog(#"player is empty because of %#",soundError);
}
else
{
[_player play];
_player.volume=1.0;
[_player setDelegate:self];
}
// for stop player you can use
// [_player stop]; //uncomment this line when you wants to stop it.
I initialize an AVAudioPlayer instance:
NSString* path = [[NSBundle mainBundle] pathForResource:#"foo" ofType:#"wav"];
AVAudioPlayer* player =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil]; //Returned object not nil, indicating that the file has loaded successfully
BOOL b = [player prepareToPlay]; //returns TRUE
BOOL b2 = [player play];
//returns TRUE, goes immediately to the next line since asynchronous
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]; //wait 1 sec
printf("%10.4f\n",player.duration); //duration is 2s
printf("%10.4f\n",player.currentTime); //position remains 0
/* There is no sound output, player is also stuck since position==0 */
Does anyone know why the AVAudioPlayer is not responding? Is there something that I am overlooking while initializing, playing the audioplayer? Boilerplate code maybe?
I am running this on the iPhone simulator. Also, AudioServicesPlaySystemSound() works for the same file, which is puzzling, but this indicates that sound playback might work with AVAudioPlayer as well.
I have 2 quick suggestions:
Check to see if that path actually gives you data. Alloc a NSData object using [[NSData alloc] initWithContentsOfFile:path] and inspect it in the debugger to see if you're actually loading that wav file.
I wrote a sample project that uses AVAudioPlayer here: AVAudioPlayer Example. As the code is pretty much the same as yours, the only thing I can imagine is that there is a problem with your data.
Check those out and see if it gets you anywhere!
I have just had a similar problem. I am not sure if it is the same solution, but my application records audio through the iphone and then plays it back to the user.
I thought I was doing the correct thing by telling my audio session that it was going to record data, so I did the following:
[audioSession setCategory:AVAudioSessionCategoryRecord error:nil];
Using this setting playback worked on the Simulator but not on the device.... the didFinishPlaying event never got raised.
I removed this line after realising I didn't need it an audio playback started working correctly. Now I just need to work out why it is so quiet :)
Paul
When I use my app (on the device), the actual volume works OK for a while, but after a few days it seems to get 'stuck' at a low level.
Adjusting the volume rocker has no effect - and it shows 'Ringer' text. I've noticed that other people's apps are similarly affected if they show the 'Ringer' text when adjusting the volume. But apps which don't show 'Ringer' text are not affected by this.
How would I remove the 'Ringer' text and get my app to respond properly to different volumes?
I found some code on Apple's forums which fixes it. You have to use the AVFoundation.framework and put a bit of code into your app delegate. And put an audio file called 'blank.aif' into your project. Basically, it's a hack which prepares a file to play, but then never plays it. This fools the system into thinking that sound is being played all the time, which allows the user to use the main volume control.
#import <AVFoundation/AVFoundation.h>
//...
-(void)applicationDidFinishLaunching:(UIApplication *)application {
//volume control hack
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"blanksound" ofType:#"aif"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath];
AVAudioPlayer *volumeHack = [[[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil]retain];
[fileURL release];
[volumeHack prepareToPlay];
// other applicationDidFinishLaunching stuff
}
I'm using the AVAudioPlayer to play sound FX on a separate thread in my app. Right now my app is fairly simple, I have a UIScrollView for a scrolling background, and a couple of CALayers that represent a player and an enemy in a simple side-scroller type game (think Mario or Kung Fu or whatnot)...I can move the player left and right, with the view scrolling appropriately with no problems. I have an "attack" button that's playing a sound effect...and many times when I play the sound effect, I get a little bit of a hitch, graphically. Is this just par for the course on the iPhone using Quartz? Is there some way to have this perform a little more smoothly without starting to investigate OpenGL?
Use NSData dataWithContentsOfMappedFile instead of dataWithContentsOfFile.
Also AVAudioPlayer handles it's own threading, you don't need to create one. [AVAudioPlayer play] returns after it started playing, not after it is done playing.
And don't forget to release your AVAudioPlayer when you no longer need it, for example if you only want to play a sound onces, implemement the audioPlayerDidFinishPlaying:successfully: delegate and release it in there.
How are you playing the sound? If I was to make a guess it would be that each time the sound is played the OS is actually loading it from disk as well as playing it.
Update:
Ok, you really don't need to be creating a new thread to play a sound. This alone could cause a stutter in a game and is also redundant - The AVAudioPlayer play method is asynchronous - it returns immediately and not when the sound ends.
I had the same problem with slowdown. To solve it, I first play and pause the sound when it first loads at volume 0 - this works better than prepareToPlay which still seems to result in slowdown:
player.volume = 0.0;
[player play];
[player pause];
player.volume = 1.0;
When it comes time to play a sound I just [player play];. Then every update cycle I loop through all my active sounds and catch ones that are about to end and rewind them instead of letting them expire - this avoids the overhead and slowdown of starting up the sound from scratch. The example here stops the sounds 1/15th of a second from the end of the sound:
for ({each player in active sounds})
{
if ((player.duration - player.currentTime) <= (1.0/15.0))
{
[player pause];
player.currentTime = 0.0;
}
}
Now it's ready to go for the next play. Seems to work fine!
Update: Turns out I was still getting some pauses the very first time the sound was played. I think playing it at zero volume wasn't actually doing anything, but I changed it to play at 0.0001 volume and then pause - it's inaudible but seems to do the job of prepping the sound.
Had the same problem at first. Per documentation you need to call "prepareToPlay" every time a sound has finished playing. Adopting the AVAudioPlayerDelegate protocol and adding the following method will do this.
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
[player prepareToPlay];
}
(Using an answer rather than a comment for length and so I can show code):
My sound playing method is as follows:
- (void)playSoundThreaded:(id)soundPlayer
{
if(soundPlayer == nil)
return;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[(AVAudioPlayer*)soundPlayer play];
[pool release];
}
- (void)playSound:(AVAudioPlayer*)player
{
if(player == nil)
return;
[NSThread detachNewThreadSelector:#selector(playSoundThreaded:)
toTarget:self withObject:(id)player];
}
And the sound is loaded up as follows:
fxPlayer = [[AVAudioPlayer alloc] initWithData:
[NSData dataWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:#"voicehiya" ofType:#"caf"]] error:NULL];
We've had some inkling that the sound is being reloaded from disk every time...but have no idea as to why that is or how to prevent it.
I think the thread is over complicating your player. Try this for a go:
/* setup the sound player */
AVAudioPlayer *player1=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:audioFilePath] error:NULL];
player1.numberOfLoops = 0;
player1.volume=1.0f;
[player1 prepareToPlay];
and then playSound becomes
[player play];
And don't forget to have the AudioSession category set correctly too.
Why not just use AudioServicesPlaySystemSound? This is well suited to sound effects.
I use it in my game (vConqr) which is also based on a UIScrollView, with numerous other views (so all having their own CALayer), and it doesn't significantly impact performance.
I've been investigating this for a few days now thanks to a project I'm on at work. I added some home-rolled profiling to my code and found that the calls to play sounds were causing frame rate issues in my OpenGL game. I've got a few recommendations for you based on what I've found:
Use an AVAudioPlayer playing an mp3 for your background music.
Use OpenAL for your sound effects.
For sound effects, use the audio converter command line tool from apple to get your sounds to .caf format. This is an uncompressed format that doesn't require the CPU to decompress your sound to memory before sending it to the sound card. It can go directly, saving you time. Instructions on using the tool can be found HERE. (scroll to the very bottom of the page)
Use the lowest bit depths that still sound good. You don't really need 128 kbps - you can probably get away with 20 for a lot of things. Lower bit rate = less processing = faster sound.
I'll let you know if I find anything else that's useful! Good luck!