I'm attempting to play an AVAudioFile using the AVAudioEngine. The code is largely taken and adapted from the Apple Developer on-line videos, but there is no playback. Have spent some time going through the forums, but nothing seems to throw any light on it.
I have two methods. The first one calls the standard open file dialog, opens the audio file, allocates an AVAudioBuffer object (which I will use later) and fills it with the audio data. The second one sets up the AVAudioEngine and AVAudioPlayerNode objects, connects everything up and plays the file. The two methods are listed below .
- (IBAction)getSoundFileAudioData:(id)sender {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
openPanel.title = #"Choose a .caf file";
openPanel.showsResizeIndicator = YES;
openPanel.showsHiddenFiles = NO;
openPanel.canChooseDirectories = NO;
openPanel.canCreateDirectories = YES;
openPanel.allowsMultipleSelection = NO;
openPanel.allowedFileTypes = #[#"caf"];
[openPanel beginWithCompletionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
NSURL* theAudioFileURL = [[openPanel URLs] objectAtIndex:0];
// Open the document.
theAudioFile = [[AVAudioFile alloc] initForReading:theAudioFileURL error:nil];
AVAudioFormat *format = theAudioFile.processingFormat;
AVAudioFrameCount capacity = (AVAudioFrameCount)theAudioFile.length;
theAudioBuffer = [[AVAudioPCMBuffer alloc]
initWithPCMFormat:format frameCapacity:capacity];
NSError *error;
if (![theAudioFile readIntoBuffer:theAudioBuffer error:&error]) {
NSLog(#"problem filling buffer");
}
else
playOrigSoundFileButton.enabled = true;
}
}];}
- (IBAction)playSoundFileAudioData:(id)sender {
AVAudioEngine *engine = [[AVAudioEngine alloc] init]; // set up the audio engine
AVAudioPlayerNode *player = [[AVAudioPlayerNode alloc] init]; // set up a player node
[engine attachNode:player]; // attach player node to engine
AVAudioMixerNode *mixer = [engine mainMixerNode];
[engine connect:player to:mixer format:theAudioFile.processingFormat]; // connect player node to mixer
[engine connect:player to:mixer format:[mixer outputFormatForBus:0]]; // connect player node to mixer
NSError *error;
if (![engine startAndReturnError:&error]) {
NSLog(#"Problem starting engine ");
}
else {
[player scheduleFile:theAudioFile atTime: nil completionHandler:nil];
[player play];
}
}
I have checked that the functions are being executed, that the audio engine is running and the audio file has been read. I have also tried with different audio file formats, both mono and stereo. Any ideas?
I'm running Xcode 7.3.1.
declare your AVAudioPlayerNode *player as global
refer this link Can't play file from documents in AVAudioPlayer
Related
I have an app for iPhone and iPad that plays an audio stream using AVPlayer, I am using the same player of the Apple Sample StitchedStreamPlayer, but I made some changes to play music instead of video.
When I run the app, I can listen for some few seconds and then, the device restarts and following error is displayed:
Terminating in response to SpringBoard's termination.
(when I am running using xcode on the device it plays some minutes, but when I unplug the device and run the app again the app crashes)
I am using the iPhone 4 and an iPad mini for testing, none of them are Jailbroken and booth are iOS 6.
The code is quite big, but here is some parts:
header:
#interface NewPlayer : NSObject <AVAudioSessionDelegate>
#property (strong) AVPlayer *player;
#property (strong) AVPlayerItem *playerItem;
some important methods of Implementation
-(void)play:(NSString *)audio
{
/* Has the user entered a audio URL? */
NSURL *audioUrl = [NSURL URLWithString:audio];
if ([audioUrl scheme]) /* Sanity check on the URL. */
{
/*
Create an asset for inspection of a resource referenced by a given URL.
Load the values for the asset keys "tracks", "playable".
*/
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:audioUrl options:nil];
NSArray *requestedKeys = [NSArray arrayWithObjects:kTracksKey, kPlayableKey, nil];
/* Tells the asset to load the values of any of the specified keys that are not already loaded. */
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
/* IMPORTANT: Must dispatch to main queue in order to operate on the AVPlayer and AVPlayerItem. */
[self prepareToPlayAsset:asset withKeys:requestedKeys];
});
}];
}
}
- (void)prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys
{
/* Make sure that the value of each key has loaded successfully. */
for (NSString *thisKey in requestedKeys)
{
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed)
{
[self assetFailedToPrepareForPlayback:error];
return;
}
/* If you are also implementing the use of -[AVAsset cancelLoading], add your code here to bail
out properly in the case of cancellation. */
}
/* Use the AVAsset playable property to detect whether the asset can be played. */
if (!asset.playable)
{
/* Generate an error describing the failure. */
NSString *localizedDescription = NSLocalizedString(#"Item cannot be played", #"Item cannot be played description");
NSString *localizedFailureReason = NSLocalizedString(#"The assets tracks were loaded, but could not be made playable.", #"Item cannot be played failure reason");
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
localizedDescription, NSLocalizedDescriptionKey,
localizedFailureReason, NSLocalizedFailureReasonErrorKey,
nil];
NSError *assetCannotBePlayedError = [NSError errorWithDomain:#"StitchedStreamPlayer" code:0 userInfo:errorDict];
/* Display the error to the user. */
[self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
return;
}
/* At this point we're ready to set up for playback of the asset. */
/* Stop observing our prior AVPlayerItem, if we have one. */
if (self.playerItem)
{
/* Remove existing player item key value observers and notifications. */
[self.playerItem removeObserver:self forKeyPath:kStatusKey];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
}
/* Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. */
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
/* Observe the player item "status" key to determine when it is ready to play. */
[self.playerItem addObserver:self
forKeyPath:kStatusKey
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:MyStreamingAudioViewControllerPlayerItemStatusObserverContext];
/* When the player item has played to its end time we'll toggle
the movie controller Pause button to be the Play button */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
/* Create new player, if we don't already have one. */
if (![self player])
{
/* Get a new AVPlayer initialized to play the specified player item. */
[self setPlayer:[AVPlayer playerWithPlayerItem:self.playerItem]];
/* Observe the AVPlayer "currentItem" property to find out when any
AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
occur.*/
[self.player addObserver:self
forKeyPath:kCurrentItemKey
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:MyStreamingAudioViewControllerCurrentItemObservationContext];
}
/* Make our new AVPlayerItem the AVPlayer's current item. */
if (self.player.currentItem != self.playerItem)
{
/* Replace the player item with a new player item. The item replacement occurs
asynchronously; observe the currentItem property to find out when the
replacement will/did occur*/
[[self player] replaceCurrentItemWithPlayerItem:self.playerItem];
[self syncPlayPauseButtons];
}
}
- (void)observeValueForKeyPath:(NSString*) path
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context
{
/* AVPlayerItem "status" property value observer. */
if (context == MyStreamingAudioViewControllerPlayerItemStatusObserverContext)
{
[self syncPlayPauseButtons];
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status)
{
/* Indicates that the status of the player is not yet known because
it has not tried to load new media resources for playback */
case AVPlayerStatusUnknown:
{
NSLog(#"desconhecido");
}
break;
case AVPlayerStatusReadyToPlay:
{
/* Once the AVPlayerItem becomes ready to play, i.e.
[playerItem status] == AVPlayerItemStatusReadyToPlay,
its duration can be fetched from the item. */
NSLog(#"ready to play");
[player play];
[self.delegate tocandoMusica];
}
break;
case AVPlayerStatusFailed:
{
AVPlayerItem *thePlayerItem = (AVPlayerItem *)object;
[self assetFailedToPrepareForPlayback:thePlayerItem.error];
NSLog(#"falhou");
[self.delegate acabouMusica];
}
break;
}
}
/* AVPlayer "rate" property value observer. */
else if (context == MyStreamingAudioViewControllerRateObservationContext)
{
//[self syncPlayPauseButtons];
}
/* AVPlayer "currentItem" property observer.
Called when the AVPlayer replaceCurrentItemWithPlayerItem:
replacement will/did occur. */
else if (context == MyStreamingAudioViewControllerCurrentItemObservationContext)
{
AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
/* New player item null? */
if (newPlayerItem == (id)[NSNull null])
{
//[self disablePlayerButtons];
//[self disableScrubber];
}
else /* Replacement of player currentItem has occurred */
{
/* Specifies that the player should preserve the video’s aspect ratio and
fit the video within the layer’s bounds. */
[self syncPlayPauseButtons];
}
}
/* Observe the AVPlayer "currentItem.timedMetadata" property to parse the media stream
timed metadata. */
else if (context == MyStreamingAudioViewControllerTimedMetadataObserverContext)
{
//NSArray* array = [[player currentItem] timedMetadata];
//for (AVMetadataItem *metadataItem in array)
//{
//}
}
else
{
[super observeValueForKeyPath:path ofObject:object change:change context:context];
}
return;
}
If you want to take a deep look, just take a look on StitchedStreamPlayer Sample, I have no idea. I have looked at:
Failed to play audio file using AVPlayer in iPhone
memory leak in AudioToolbox library AVAudioPlayer
AudioToolBox leak in iOS6?
and many others..
I have tried to forget all this implementation and use just
player = [AVPlayer playerWithURL:[NSURL URLWithString:url]];
[player play];
but it crashes!
Some idea?
EDITED
I Have tried the MPMoviePlayerController but the same happened, the music started and then the device restarted.
This is the code I have used:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:NULL];
player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:[[arrRadios objectAtIndex:indexPath.row] objectForKey:#"url"]]];
[player play];
Im making a radio streaming application, my app is working perfectly. I have two buttons in my screen one for playing and the other for pausing, also I have a label that indicates the state of my player. I have no problem making this label show the state "Playing" or "Paused" my problem is that when I press the play button there is a time where the buffer is gathering information and I can't figure out a way to show a "Buffering..." label before the player start to stream the audio.
This is the code Im using for streaming the radio station.
NSString *url = [NSString stringWithFormat:#"http://66.7.218:8816"];
player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:url]];
player.movieSourceType = MPMovieSourceTypeStreaming;
player.view.hidden = YES;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance]setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[player prepareToPlay];
[player play];
Im usin a function I created called changeStatus and this function is called each second to identify the status of the player.
-(void)changeStatus
{
if (player.loadState == MPMovieLoadStateUnknown) {
status.text = #"Buffering...";
}
if(player.playbackState == MPMoviePlaybackStatePlaying)
{
status.text = #"Playing.";
}
if(player.playbackState == MPMoviePlaybackStatePaused)
{
status.text = #"Paused";
}
}
I really need to solve this problem and Ive tried all I can to solve it. Hope you can help me! Thanks in advance.
I achieved to solve the problem. The part that was missing is the if clause which gives the signal to start playing only when the player is prepared to play. Without that the player starts to play before the buffer is completely loaded so it plays no audio. And that was the reason my Playing label showed immediately instead of the buffering one.
NSString *url = [NSString stringWithFormat:#"http://66.7.218:8816"];
player = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:url]];
player.movieSourceType = MPMovieSourceTypeStreaming;
player.view.hidden = YES;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance]setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[player prepareToPlay];
if(player.isPreparedToPlay)
{
[player play];
}
Adittionally, the MPMoviePlaybackStateInterrupted refers to the buffering process while doing an stream. So if you want to make something happens in the buffering process refer to this method.
-(void)changeStatus
{
if(player.playbackState == MPMoviePlaybackStatePlaying)
{
status.text = #"Playing.";
}
if(player.playbackState == MPMoviePlaybackStateInterrupted)
{
status.text = #"Buffering...";
}
}
Thanks a lot to the user That Guy who helped me solving this.
Actually there's a notification called
MPMediaPlaybackIsPreparedToPlayDidChangeNotification
The observer will get notified after it is ready to play. It is more or less the same effect but with different mechanism.
I want to play more than two song in my app. how do I can do it using AVAudioPlayer ?
I was going to type up the answer, but google provides a perfectly good answer:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/27285-play-sequence-audio-files-avaudioplayer.html
The first piece of code from the page will just call all the sounds in sequence:
- (void)playSoundSequence {
// Make array containing audio file names
NSArray *theSoundArray = [NSArray arrayWithObjects:#"one",#"two",nil];
int totalSoundsInQueue = [theSoundArray count];
for (int i=0; i < totalSoundsInQueue; i) {
NSString *sound = [theSoundArray objectAtIndex:i];
// Wait until the audio player is not playing anything
while(![audioPlayer isPlaying]){
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:sound ofType:#"wav"]] error:NULL];
self.audioPlayer = player;
[player release];
[audioPlayer setVolume:1.0f];
[audioPlayer play];
//Increment for loop counter & move onto next iteration
i++;
}
}
}
But, you are probably better off using an AVAudioPlayerDelegate:
If you don't want to wait in the loop, you can also make your
controller an AVAudioPlayerDelegate and implement
Code:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
This will be called when the end of the audio file is reached, at
which point you would start playing the next audio file.
audioPlayerDidFinishPlaying will then be called again when that one
finishes playing, and you start the next one after that, etc.
I am writing an application that uses the AVAudioRecorder class. It works great except for when a phone call comes in. I am handling this per apple's guidelines of using the AVAudioRecorderDelegate methods
– (void) audioRecorderBeginInterruption:
– (void) audioRecorderEndInterruption:
It works great until the interruption ends and I attempt to "resume" the recording by calling the record method again (per the documentation). However it does not resume my recording but instead throws out the old one and starts up an entirely new one in its place. I have not been able to find a solution to this problem, if anyone has figured this out, or if it is a bug with apple's AVAudioRecorder please let me know. I really hope I do not have to write this using AudioQueues.
thanks
Looks like its a bug with apple's API. Great fun....
This was the response we received from a support ticket.
"The behavior you described is a bug and unfortunately there's nothing in the API that you can change to work around to actually append to the original recording. The interruption is resulting in capturing only the audio recorded after the interruption. You could try and stop the recording after the interruption then creating a new file after which would at least not cause the user to loose any information, but the result would be two separate files.
Please file a bug report at for this issue since bugs filed by external developers are critical when iOS engineering is evaluating critical features of fixes to address. It's easily reproducible but if you have a test app you can include please do, iOS engineering like having apps that show the bug directly.
"
My solution was:
Start record on temp file
Watch for AVAudioSessionInterruptionNotificatio
On AVAudioSessionInterruptionTypeBegan - stop the recording.
On AVAudioSessionInterruptionTypeEnded - Start new recording.
When the user stops - Marge the files.
Full Code
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(audioSessionInterruptionNotification:)
name:AVAudioSessionInterruptionNotification
object:audioSession];
-(void)audioSessionInterruptionNotification:(NSNotification*)notification {
NSString* seccReason = #"";
//Check the type of notification, especially if you are sending multiple AVAudioSession events here
NSLog(#"Interruption notification name %#", notification.name);
NSError *err = noErr;
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
seccReason = #"Interruption notification received";
//Check to see if it was a Begin interruption
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
seccReason = #"Interruption began";
NSLog(#"Interruption notification name %# audio pause", notification.name);
dispatch_time_t restartTime = dispatch_time(DISPATCH_TIME_NOW,
0.01 * NSEC_PER_SEC);
dispatch_after(restartTime, dispatch_get_global_queue(0, 0), ^{
AVAudioRecorder *recorder = [[self recorderPool] objectForKey:lastRecID];
if (recorder) {
if(recorder.isRecording) {
[recorder stop];
NSLog(#"Interruption notification name Pauseing recording %#", lastRecID);
} else {
NSLog(#"Interruption notification name Already Paused %#", lastRecID);
}
}else {
NSLog(#"Interruption notification name recording %# not found", lastRecID);
}
NSLog(#"Interruption notification Pauseing recording status %d",recorder.isRecording);
});
} else if([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]){
seccReason = #"Interruption ended!";
NSLog(#"Interruption notification name %# audio resume", notification.name);
//Start New Recording
dispatch_time_t restartTime = dispatch_time(DISPATCH_TIME_NOW,
0.1 * NSEC_PER_SEC);
dispatch_after(restartTime, dispatch_get_global_queue(0, 0), ^{
AVAudioRecorder *recorder = [[self recorderPool] objectForKey:lastRecID];
NSLog(#"Interruption notification Resumeing recording status %d",recorder.isRecording);
if (recorder) {
if(!recorder.isRecording) {
NSString *filePath = [[self orgFileNames] objectForKey:lastRecID];
NSArray * fileNames =[[self fileNames] objectForKey:lastRecID];
NSString *tmpFileName = [self gnrTempFileName:filePath AndNumber:fileNames.count];
[[[self fileNames] objectForKey:lastRecID] addObject:tmpFileName];
NSURL *url = [NSURL fileURLWithPath:tmpFileName];
NSError *error = nil;
recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&error];
if (![recorder record]) {
NSLog(#"Interruption notification Error Resumeing recording %#",tempRecorder);
return;
}
[[self recorderPool] setObject:recorder forKey:lastRecID];
NSLog(#"Interruption notification nameResumeing recording %#",lastRecID);
}else {
NSLog(#"Interruption notification Already Recording %d",recorder.isRecording);
}
}else {
NSLog(#"Interruption notification name recording %# not found",lastRecID);
}
});
}
}
}
You will try by using this piece of code
-(IBAction)pauseandplay:(id)sender
{
BOOL status= [player isPlaying];
if(status)
{
[pauseplay setImage:[UIImage imageNamed:#"play.png"]];
[player pause];
}
else
{
[pauseplay setImage:[UIImage imageNamed:#"icon-pause.png"]];
[player play];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(updateCurrentTime) userInfo:player repeats:YES];
}
}
I am looking for something like
PlaySound (uint frequency)
Does it exist?
From the HowTo at: http://wiki.monotouch.net/HowTo/Sound/Play_a_Sound_or_Alert
var sound = SystemSound.FromFile (new NSUrl ("File.caf"));
sound.PlaySystemSound ();
I don't know about mono, but in the iPhone SDK it isn't easy that easy to create and play sound. Other alternatives are to provide the sound as a file and play that, or create an array representing a sinusoid, and wrap it in a audio wrapper, and pass it to one of many sound APIs.
If mono proves to be just as limited, then search stackoverflow.com for System Sound Services and AVAudioPlayer as starting points.
Here are two ways to play a sound file:
SoundEffect.c (based on Apple's)
#import "SoundEffect.h"
#implementation SoundEffect
+ (id)soundEffectWithContentsOfFile:(NSString *)aPath {
if (aPath) {
return [[[SoundEffect alloc] initWithContentsOfFile:aPath] autorelease];
}
return nil;
}
- (id)initWithContentsOfFile:(NSString *)path {
self = [super init];
if (self != nil) {
NSURL *aFileURL = [NSURL fileURLWithPath:path isDirectory:NO];
if (aFileURL != nil) {
SystemSoundID aSoundID;
OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)aFileURL, &aSoundID);
if (error == kAudioServicesNoError) { // success
_soundID = aSoundID;
} else {
NSLog(#"Error %d loading sound at path: %#", error, path);
[self release], self = nil;
}
} else {
NSLog(#"NSURL is nil for path: %#", path);
[self release], self = nil;
}
}
return self;
}
-(void)dealloc {
AudioServicesDisposeSystemSoundID(_soundID);
NSLog(#"Releasing in SoundEffect");
[super dealloc];
// self = nil;
}
-(void)play {
AudioServicesPlaySystemSound(_soundID);
}
-(void)playvibe {
AudioServicesPlayAlertSound(_soundID);
}
+(void)justvibe {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
}
#end
SoundEffect.h:
#import <AudioToolbox/AudioServices.h>
#interface SoundEffect : NSObject {
SystemSoundID _soundID;
}
+ (id)soundEffectWithContentsOfFile:(NSString *)aPath;
- (id)initWithContentsOfFile:(NSString *)path;
- (void)play;
- (void)playvibe;
+ (void)justvibe;
#end
How to use it:
// load the sound
gameOverSound = [[SoundEffect alloc] initWithContentsOfFile:[mainBundle pathForResource:#"buzz" ofType:#"caf"]];
// play the sound
[gameOverSound playvibe];
This is useful for when you want to play sound at the same volume as the iPhone's volume control setting, and you won't need to stop or pause the sound.
Another way is:
+ (AVAudioPlayer *) newSoundWithName: (NSString *) name;
{
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: name ofType: #"caf"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
// if the sound is large and you need to preload it:
[newPlayer prepareToPlay];
return (newPlayer);
}
and use it (you can see all the extras when you go with AVAudioPlayer):
timePassingSound = [AVAudioPlayer newSoundWithName:#"ClockTicking"];
[timePassingSound play];
// change the volume
[timePassingSound volume:0.5];
// pause to keep it at the same place in the sound
[timePassingSound pause];
// stop to stop completely, and go to beginning
[timePassingSound stop];
// check to see if sound is still playing
[timePassingSound isPlaying];