I have a piano application. It's working fine, with a little error. If I play several keys at the same time very fast, the sounds disappears for a couple of seconds, and receive the following message in the console
AudioQueueStart posting message to kill mediaserverd
Here is the relevant code:
-(IBAction)playNoteFromKeyTouch:(id) sender{
[NSThread detachNewThreadSelector:#selector(playNote:) toTarget:self withObject:[NSString stringWithFormat:#"Piano.mf.%#",[sender currentTitle]]];
}
-(void)playNote:(NSString *) note{
NSError *err;
NSString *path = [[NSBundle mainBundle] pathForResource:note ofType:#"aiff"];
AVAudioPlayer *p = [[AVAudioPlayer alloc ] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&err];
p.delegate = self;
if (err) {
NSLog(#"%#", err);
}else{
[p prepareToPlay];
[p play];
}
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[player release];
}
I have tested with Instruments and I don't have any memory leak. If somebody could have an idea to avoid this error it would be appreciated.
I suffer from a similar issue.
I spent ages trying to solve the issue, and I think my particular issue takes place when:
I'm in an AudioCategory that doesn't allow sound to play while the mute switch is on.
I start to play a sound (I actually don't do this in the app, but this is how I can reproduce reliably).
With the sound still playing, I switch to another AudioCategory that doesn't allow sound to play while the mute switch is on.
From this point onwards, I get 'posting message to kill mediaserverd' from what looks like various points in calls in the AudioSession API. The app hangs, the device hangs, and I struggle to get the device back to a normal running state.
According to this message it's the device's mute switch.
It turns out that having the iPad muted via the device's physical switch was
causing the problem with my app. As long as the button is not
switched on the problem does not occur.
Sheesh. How to programmatically override?
I "solved" the issue using SoundBankPlayer instead of AVAudioPlayer. SoundBanker info.
Related
I have added an observer for the interrupt notification when recording audio.
This works fine when performing an outgoing-call, getting an incoming call and not answering, Siri, etc..
Now my app is running in the background with the red bar at the top of the screen, and continuing the recording in the states described above is not a problem.
But when I actually answer an incoming-call. I get another AVAudioSessionInterruptionTypeBegan notification and then when I stop the call, I never get a notification AVAudioSessionInterruptionTypeEnded type.
I have tried using the CTCallCenter to detect when a call has started, but I am unable to restart the recording from that callback.
Does anyone know how to get the interrupt mechanism to work with an incoming call that is actually getting answered?
This is (part of) the code I am using;
CFNotificationCenterAddObserver(
CFNotificationCenterGetLocalCenter(),
this,
&handle_interrupt,
(__bridge CFStringRef) AVAudioSessionInterruptionNotification,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately );
...
static void handle_interrupt( CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo )
{
au_recorder *recorder = static_cast<au_recorder*>( observer );
NSNumber* interruptionType = [( ( __bridge NSDictionary* ) userInfo ) objectForKey:AVAudioSessionInterruptionTypeKey];
switch ( [interruptionType unsignedIntegerValue] )
{
case AVAudioSessionInterruptionTypeBegan:
{
// pause recorder without stopping recording (already done by OS)
recorder->pause();
break;
}
case AVAudioSessionInterruptionTypeEnded:
{
NSNumber* interruptionOption = [( ( __bridge NSDictionary* ) userInfo ) objectForKey:AVAudioSessionInterruptionOptionKey];
if ( interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume )
{
recorder->resume();
}
break;
}
}
}
I have tried binding the notification to either the AppDelegate, a UIViewController and a separate class, but that doesn't appear to help.
Edit
This is what I tried using the CTCallCenter, but this is very flaky. When recorder->resume() is called from the callback, it either works, crashes violently or doesn't do anything at all until the app is put back in the foreground again manually.
callCenter = [[CTCallCenter alloc] init];
callCenter.callEventHandler = ^(CTCall *call)
{
if ([call.callState isEqualToString:CTCallStateDisconnected])
{
recorder->resume();
}
};
UPDATE
If you hackily wait for a second or so, you can restart the recording from your callEventHandler (although you haven't described your violent crashes, it works fine for me):
if ([call.callState isEqualToString:CTCallStateDisconnected])
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
self.recorder->resume();
});
}
This is all without a background task or changing to an exclusive audio session. The delay works around the fact that the call ending notification comes in before Phone.app deactivates its audio session & 1 second seems to be small enough to fall into some kind of background descheduling grace period. I've lost count of how many implementation details are assumed in this, so maybe you'd like to read on to the
Solution that seems to be backed by documentation:
N.B While there's a link to "suggestive" documentation for this solution, it was mostly guided by these two errors popping up and their accompanying comments in the header files:
AVAudioSessionErrorCodeCannotInterruptOthers
The app's audio session is non-mixable and trying to go active while in the background.
This is allowed only when the app is the NowPlaying app.
AVAudioSessionErrorInsufficientPriority
The app was not allowed to set the audio category because another app (Phone, etc.) is controlling it.
You haven't shown how your AVAudioSession is configured, but the fact that you're not getting an AVAudioSessionInterruptionTypeEnded notification suggests you're not using an exclusive audio session (i.e. you're setting "mix with others"):
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
The obsoleted CTCallCenter and newer CXCallObserver callbacks seem to happen too early, before the interruption ends, and maybe with some further work you could find a way to run a background task that restarts the recording a little while after the call ends (my initial attempts at this failed).
So right now, the only way I know to restart the recording after receiving a phone call is:
Use an exclusive audio session (this gives you an end interruption):
if (!([session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]
&& [session setActive:YES error:&error])) {
NSLog(#"Audio session error: %#", error);
}
and integrate with "Now Playing" (not joking, this is a structural part of the solution):
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand addTargetWithHandler:^(MPRemoteCommandEvent *event) {
NSLog(#"PLAY");
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.pauseCommand addTargetWithHandler:^(MPRemoteCommandEvent *event) {
NSLog(#"PAUSE");
return MPRemoteCommandHandlerStatusSuccess;
}];
// isn't this mutually exclusive with mwo? or maybe that's remote control keys
// not seeing anything. maybe it needs actual playback?
NSDictionary* nowPlaying = #{
MPMediaItemPropertyTitle: #"foo",
MPMediaItemPropertyArtist: #"Me",
MPMediaItemPropertyAlbumTitle: #"brown album",
};
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nowPlaying;
Pieces of this solution were cherry picked from the Audio Guidelines for User-Controlled Playback and Recording Apps documentation.
p.s. maybe the lock screen integration or non-exclusive audio session are deal breakers for you, but there are other situations where you'll never get an end interruption, e.g. launching another exclusive app, like Music (although this may not be an issue with mixWithOthers, so maybe you should go with the code-smell delay solution.
I can turn off my audio player by using:
- (IBAction)sndOnOff:(id)sender {
if (_sndBtn.selected == NO) {
[_sndBtn setSelected:YES];
[audioPlayer stop];
}
else{
[_sndBtn setSelected:NO];
[audioPlayer play];
}
But how do I turn off a System Sound that I've created?
NSURL *soundURL1 = [[NSBundle mainBundle] URLForResource:#"selectSound"
withExtension:#"wav"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL1, &sound2);
There is a reason that we don't get fine control over system sound - because it is supposed to be short (in iOS < 30 seconds) and complete. Imagine, how many times do we experience a system sound cut-off in any OS?
But you could do this:
AudioServicesDisposeSystemSoundID (sound2);
to stop it. But this means you would need to create the sound again by using:
AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL1, &sound2);
And to play it again:
AudioServicesPlaySystemSound(sound2);
You could of course instead to play your "system sound" on AVPlayer and stop it like you do a song, but that would mean it "shouldn't be" a system sound in the first place. Hope this helps.
Just do this...
AudioServicesDisposeSystemSoundID (sound2);
I cannot think of any negative consequences.
I am working on iOS app which will be compatible with iOS 6/7 and stream audio .mp3 files from a website.
I have already gotten this to work using the following code:
-(NSString*)documentsFolder
{
NSString* dataPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:NULL];
return dataPath;
}
-(NSString*)createURLFile:(NSString*)songURL
{
NSString* M3U_FILE = #"song.m3u";
NSString* path = [NSString stringWithFormat:#"%#",[[self documentsFolder] stringByAppendingPathComponent:M3U_FILE]];
if([[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil])
{
NSFileHandle* outFile = [NSFileHandle fileHandleForWritingAtPath:path];
if(outFile != nil)
{
NSData* buffer = [songURL dataUsingEncoding:NSUTF8StringEncoding];
[outFile writeData:buffer];
return path;
}
}
return nil;
}
- (void)createStreamer
{
// Remove any previous references.
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Create a new player.
NSString* fileURL = [self createURLFile:self.aSong.songpath];
self.songPlayer = [[AVPlayer alloc]initWithURL:[NSURL fileURLWithPath:fileURL]];
NSAssert(self.songPlayer != nil, #"NIL AVPlayer Created!!!");
// Observer for when the song ends...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.songPlayer currentItem]];
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
I store the url for the .mp3 file in a local .m3u file and use that to load up the AVPlayer. In earlier versions of iOS, I was told that the AVPlayer would load the song first and then play it, not stream it immediately. While this does not appear to be true in iOS 6/7 (the song starts streaming almost immediately), the .m3u file was being created in case there were any problems created by not having it done this way.
With this, a loop is monitoring the status of the AVPlayer and after a few seconds, the audio starts to play out the phone without a problem.
For testing purposes, I set up an MPVolumeView on the page which plays songs:
MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:CGRectMake(0, 0, 310, 20)] autorelease];
volumeView.center = CGPointMake(160,62);
[volumeView sizeToFit];
[self.view addSubview:volumeView];
The reason for this is that the volume slider will also show an indicator if the bluetooth is connected as an audio output source and allow me to change the audio route between the phone and the bluetooth device. So far, so good.
I connected my phone to my Jawbox Jambone via bluetooth, start the AVPlayer on a song, and the song comes out of the Jawbox as expected. The volume control has the small "rectangle with arrow" indicating that I can switch the audio output and indeed, while the song is playing, I can switch between the phone and the Jawbox. Happiness.
The problem occurs when I try to connect it to a car. I have two experiences with this:
The car is already paired with the phone for making/receiving calls. The phone even indicates it is paired when I get into the car. But when I use the same code to play the same audio files, they only come out of the phone. The volume slider does not show the "bluetooth route" indicator at all (like it does not recognize the car as a audio output route).
In another car, the audio was streaming from another app (some radio streaming app). The other app was stopped and this one started. The audio started playing for the same song tested above, but stopped after a second or two. Again, there was no indicator on the volume slider that the bluetooth was connected at this point.
Can somebody explain to me why the audio could stream fine out to one bluetooth device but not to another?
Have I missed something (an entitlement?) in the profile for my app that will allow it to stream audio via bluetooth to a car?
There is this project at GIT.
Play iOS project is a streaming client for Play that runs on your iPhone/iPad. It supports background audio as well as the media keys when backgrounded.
It supports:
Streams shoutcast stream
Displays currently playing track
Background audio
Lock screen album art & play controls
AirPlay (along with Bluetooth) streaming. Supports sending metadata
and album art
You can download the project here.
I have not tested this on CAR bluetooth audio player though. Hope it may be of any help to you.
In the first example, your car may simply be a remote player. You would need to register for remote events like this (consider using an AVAudioPlayer instead of an AVPlayer also)
Setup the AudioSession to recognize a bluetooth audio route:
- (BOOL)prepareAudioSession {
// deactivate existing session
NSError *setCategoryError = nil;
NSError *activationError = nil;
BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
if (!success) {
NSLog(#"deactivationError");
}
// set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&setCategoryError];
if (!success)
{
NSLog(#"setCategoryError %#",setCategoryError);
}
// activate audio session
success = [[AVAudioSession sharedInstance] setActive:YES error: &activationError];
if (!success) {
NSLog(#"activationError");
}
return success;
}
You can check the routes:
AVAudioSessionRouteDescription *mAVASRD = audioSession.currentRoute;
NSLog(#"the array is %#",mAVASRD.outputs);
for (int ctr = 0; ctr < [mAVASRD.outputs count]; ctr++)
{
AVAudioSessionPortDescription *myPortDescription = [mAVASRD.outputs objectAtIndex:ctr];
NSLog(#"the type is %#",myPortDescription.portType);
NSLog(#"the name is %#",myPortDescription.portName);
NSLog(#"the UID is %#",myPortDescription.UID);
NSLog(#"the data sources are %#",myPortDescription.dataSources);
}
Then initialize your AVAudioPlayer and turn on RemoteControlEvents (you can use the console in your car to send play/pause/etc)
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
then implement something like the delegate method for AVAudioPlayer in this stack overflow question to capture the received events and react accordingly in your code:
AVAudioPlayer on Lock Screen
In the scenario 2, when you moved one app to the background (the radio streaming app) and started your app, the likely culprit for the issue is the same cause - your app has to recognize the bluetooth route for audio.
By the way for phone calls and Siri, the iOS uses a different Bluetooth channel that the default for remote control (which is the one I am describing for your car).
When you setup this route and remote control events, you also get a bonus byproduct - your app will be controllable from the lock screen. Check out this technical note from Apple to configure your app to play in the background as well if that is also something you need to do when the screen locks: Technical QA document QA1668
Finally, for added integration via your bluetooth route, look at MPNowPlayingInfoCenter - put the title artist artwork and other good stuff on the lock screen and on most bluetooth screens in the car that are displaying that information.
I'm pretty sure MPVolumeView can only address Bluetooth devices which conform to the newer low power consumption Bluetooth spec... (Bluetooth Low Energy or BLE)...
I know the phone app doesn't use MPVolumeView, probably this other audio player doesn't either.. You may need to look into CoreBluetooth and implement your own :( good luck. There may be a solution on github
A bluetooth speaker designed as a speaker will be no problem.
However, a car will usually be a "phone" bluetooth speaker and will only accept a "phone" type of communications.
My guess would be that you would have to trick it by setting up a "phone audio" connection and having the incoming audio transfer into the void, and the outgoing music stream as a phone signal.
Mind you, signal quality might degrade and there probaly won't be a fix for that.
My ios app plays few audio (.m4a) files . It works well and i hear the audio on ios simulator running on ios 7, but when i run the app on my iphone 5, ios 7, it does not play the audio. At least i don't hear anything.
I ran the app on iPhone 4s , os 6.3 and 7.0 and it worked well. Looks like the issue is something related to iPhone 5, maybe it is some setting in my phone ?
I did a similar post here but its not really helping me.
Audio file doesn't play in iPhone 5
In the play audio method, i have the following statements
I have audioPlayer loaded and ready to play on a swipe gesture
self.audioPlayer = [self getAudioPlayer:imgAudFileName extension:#".m4a"];
[self.audioPlayer prepareToPlay];
When the use clicks on play button, this is the action that gets executed
- (IBAction)playOrPauseSound:(UIButton *)sender {
[self.audioPlayer play];
BOOL isAudioPlaying = [self.audioPlayer isPlaying];
NSLog(#" Audio Playing? %d", isAudioPlaying);
[self.audioPlayer setDelegate:self];
}
which returns 1 for both Simulator and iPhone5
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
successfully:(BOOL)flag {
NSLog(#" Did Finish Playing %d", flag);
}
delegate method is also returning 1 on iPhone 5..
Any help on this is appreciated.
Thanks,
Update : Nov-12
I tried using a different method to initialize and use AudioPlayer but the issue remains the same
- (IBAction)testAction:(id)sender {
NSURL *url = [[NSBundle mainBundle] URLForResource:#"hello" withExtension:#"m4a"];
_somePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:NULL];
_somePlayer.delegate = self;
[_somePlayer play];
}
I deployed the app on my friends iPhone 5, ios 7 and it worked there without any issues. I dont have a proper explanation for why it's not working on my phone. I guess I have to get it tested on more iphone 5.
Thanks,
In my application I am using an AVAudioRecorder object to allow the user to record a sound file, and when the user is done recording they can play the sound via the AVAudioPlayer. The issue I'm having is when the user tries to play the audio file it only plays for one second. What is even more odd is that this code worked perfect on iOS 5, but since upgrading to iOS 6 this issue has started to happen.
I have checked the file that is being created via iTunes file sharing, and I'm able to play the entire sound file on my desktop.
Below I have pasted the code from my manipulation file that loads the audio player. From what I can tell the
- (void)playRecordingBtnClk:(id)sender
{
NSLog(#"Play btn clk");
if (!isRecording) {
// disable all buttons
[_recordButton disableButton];
[_stopButton disableButton];
[_playButton disableButton];
[_saveButton disableButton];
// create the player and play the file from the beginning
if (audioPlayer) {
[audioPlayer release];
audioPlayer = nil;
}
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioRecorder.url error:&error];
audioPlayer.delegate = self;
audioPlayer.currentTime = 0.0;
[audioPlayer prepareToPlay];
if (error) {
NSLog(#"Error Playing Audio File: %#", [error debugDescription]);
return;
}
[audioPlayer play];
[self startTicker];
}
}
It appears that the
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
method is being called after one second which is why the audio player stops. Anyone have any ideas what is going on here?
For iOS 6.0, add two more lines:
soundPlayer.currentTime = soundPlayer.duration + soundPlayer.duration;
soundPlayer.numberOfLoops = 1;
Not a perfect solution. We may need to wait for iOS 6.0.1/6.1 update.
For more, please visit this, someone commented there, saying it might be a problem of CDAudioManager.