Memory from AVAudioPlayer in ivar not reused - ios

I've set up an instance variable to reference an AVAudioPlayer that I'm using to play different audio data at different times. My header file contains:
AVAudioPlayer *player;
and
#property (nonatomic, retain) AVAudioPlayer *player;
And my implementation file contains:
- (void)playAudioData:(NSData *)data {
NSMutableData *trimmedData = [NSMutableData dataWithData:data];
int duration = 2; // seconds
int dataLength = duration * 2 * 44100; // assumes 16-bit mono data
[trimmedData setLength:dataLength];
NSError *playerError;
self.player = [[AVAudioPlayer alloc] initWithData:trimmedData error:&playerError];
[self.player autorelease]; // ADDED
if (playerError) {
NSLog(#"player error: %#", playerError);
}
[self.player play];
if (([[[UIDevice currentDevice] systemVersion] compare:#"6.0"] != NSOrderedAscending)&&([[[UIDevice currentDevice] systemVersion] compare:#"6.1"] == NSOrderedAscending)) {
[trimmedData autorelease]; // ADDED
}
}
- (void)dealloc {
[self.player release];
}
When profiling my app for memory usage, I've noticed that calling this method increases the live bytes by 500k, which is about the size of the audio data I'm playing. However, if I call the method again, the live bytes increases by another 500k, and another, each time I call the method.
According to this SO answer, I shouldn't have to do anything differently here because setting the self.player variable reduces the retain count of the existing value. But I also tried releasing and setting self.player to nil before giving it a new value, as shown in this answer, and autoreleasing self.player after setting it, as shown in this answer. (I'm not getting errors initializing the player, as was the issue in that last answer.) In all these cases, my memory usage keeps going up and never comes down again.
Is there something else I need to do to reuse the memory that is allocated each time I initialize the audio player?

I had the same issue with my app before switching to Automatic Reference Counting [ARC], memory usage will increase until the app crashes, right?. The fastest way for you to fix this problem is to use ARC too in your app/project, in fact it will do all the job of managing memory and release AVAudioPlayer when needed [you don't need any -void(dealloc) in your code if you use ARC], so no more leaks :-). If you don't know what ARC is you can check this link too:
http://www.raywenderlich.com/5677/beginning-arc-in-ios-5-part-1
Header:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVAudioPlayer.h>
#interface YourViewController : UIViewController <AVAudioPlayerDelegate>
{
AVAudioPlayer *player;
/* Your other code here */
}
/* Your other code here */
#end
Main:
#import "YourViewController.h"
#implementation YourViewController
/* Your other code here */
- (void)playAudioData:(NSData *)data
{
NSError *playerError;
self.player = [[AVAudioPlayer alloc] initWithData:data error:&playerError]; error:NULL];
if (playerError)
{
NSLog(#"player error: %#", playerError);
}
[self.player play];
/* No more need to release AVAudioPlayer, ARC will do the job */
}
/* Your other code here */
#end

Related

Playing Audio from a class in SpriteKit

Okay, so I'm working on a game using SpriteKit, and there is background music throughout the app. Initially, I had just used a new AVAudioPlayer in each scene with the appropriate audio file, but now I am finding that I'd like to have uninterrupted audio playback when moving between menus and such. So I am attempting use a separate class to handle all of the background music. I'm not sure if this is technically possible, if not, I'd like to see if there's another way to solve my problem.
So onto the code... here's the header file for my audio class:
#import <AVFoundation/AVFoundation.h>
typedef NS_ENUM(int, songTitle) {
Menu_Music,
Level_1,
Level_2,
Game_Over,
};
#interface NWAudioPlayer : AVAudioPlayer
#property (nonatomic, assign) songTitle songName;
#property (nonatomic) AVAudioPlayer* bgPlayer;
-(void)createAllMusicWithAudio: (songTitle)audio;
#end
and here's the implementation:
#import "NWAudioPlayer.h"
#implementation NWAudioPlayer
-(void)createAllMusicWithAudio: (songTitle)audio {
if ([[GameState sharedGameData] audioWillPlay] == YES) {
switch (audio) {
case Menu_Music:
[self playMusicWithString:#"menuMusic"];
break;
case Level_1:
[self playMusicWithString:#"Level-1-Music"];
break;
case Level_2:
[self playMusicWithString:#"Level-2-Music"];
break;
case Game_Over:
break;
default:
break;
}
}
}
-(void)playMusicWithString: (NSString *)file {
NSString *soundFile = [[NSBundle mainBundle] pathForResource:#"menuMusic" ofType:#"m4a"];
NSURL *soundFileUrl = [NSURL fileURLWithPath:soundFile];
NSError *Error = nil;
_bgPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileUrl error:&Error];
_bgPlayer.numberOfLoops = -1;
[_bgPlayer prepareToPlay];
NSLog(soundFile);
}
#end
And then here's what I have in my scene's implementation:
-(void)createAudio
{
[[NWAudioPlayer alloc] createAllMusicWithAudio:Menu_Music];
[[NWAudioPlayer alloc].bgPlayer play];
}
and obviously I'll call [self createAudio] in the init for that scene.
The frustrating part is that I'm not getting a single error, and my NSLog is showing that the method is calling the correct audio file, but nothing is playing. Clearly I'm missing something... I'm stumped. Any help is appreciated! Thanks so much!
Separate the code that manages an AVAudioPlayer from the scene code. You could put it in your application delegate, for example, or create your own music player class with a singleton instance.

How to fetch audio file which downloaded in NsData?

in my project i have Url of audio...look at my code....
ViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController<AVAudioPlayerDelegate,NSURLConnectionDelegate>
{
AVAudioPlayer *song;
// NSMutableData *fileData;
NSData *myData;
}
#property (nonatomic, retain) AVAudioPlayer *song;
#property(nonatomic,retain) NSData *myData;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize song,myData;
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *myUrl;
myUrl = [NSURL URLWithString:#"http://p0.bcbits.com/download/track/60523f9784a2912348eff92f7b7e21e8/mp3-128/1269403107?c22209f7da1bd60ad42305bae71896761f6cd1f4d6c29bf401ef7e97b90c3d5939b57f5479b37ad4d41c48227740d7ba8b5f79b76aa8d6735b8f87c781f44fb019762a2903fe65aeafb30d90cb56b385e27cd770cc9f9d60e5ea3f130da6ad3db728ff7e24a09fab9f351120810ee91f78f1e94fcabc98aabb37d4248358f6da4a0fa7a74fe8c89da2a768&fsig=cd06c325fc41b6f8edb0409011e8839c&id=1269403107&stream=1&ts=1395489600.0"];
myData = [NSData dataWithContentsOfURL:myUrl];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
in above code how can convert mydata into one array?????
or how can i download this audio and store in phone memory??
You can store your audio data to device memory as like below
myData = [NSData dataWithContentsOfURL:myUrl];
[myData writeToFile:[NSHomeDirectory() stringByAppendingPathComponent:#"Documents/audio.mp3"] atomically:YES];
To play an audio file that you saved in device :
Add AudioToolbox.framework and AVFoundation.framework to your project build
In .h file
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
AVAudioPlayer *player;
In .m file
-(void)initPlayer
{
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
NSError *err;
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err];
audioSession.delegate = self;
[audioSession setActive:YES error:&err];
player = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:#"Documents/audio.mp3"]] error:nil];
}
-(void)playFile
{
if (player.playing)
{
[player pause];
isPlaying = NO;
}
else
{
[player play];
isPlaying = YES;
}
}
Thanks!
Your code for downloading the file has several problems. First, as you say, it will download the file every time. Second, you are using a synchronous download call. That code will lock up the app's user interface until the download completes or fails. If there is a network problem that could take up to 2 minutes. Rule of thumb: Don't EVER download synchronously.
You should take a look at the NSURLConnection method sendAsynchronousRequest:queue:completionHandler:. That method will let you download a file asynchronously and execute a block of code once the download is complete.
If you want to be able to play your sound quickly, it's a good idea to move the code to download the file into your applicationDidFinishLaunching:withOptions: code in the app delegate. Put code there that checks to see if the file exists, and starts downloading it if not using sendAsynchronousRequest:queue:completionHandler:. Then, by the time you need to play the sound, it should be loaded. (The download might have failed or not be completed, so you do still need to check if it is available at the time you need to play it.)

Playing back audio using AVAudioPlayer iOS 7

I'm struggling to follow Apple's documentation regarding playing back a small .wav file using the AVAudioPlayer class. I'm also not sure what toolboxes I need for basic audio playback. So far I have imported:
AVFoundation.framework
CoreAudio.framework
AudioToolbox.framework
Here is my code .h and .m:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController <AVAudioPlayerDelegate>
- (IBAction)playAudio:(id)sender;
#end
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)playAudio:(id)sender {
NSLog(#"Button was Pressed");
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"XF_Loop_028" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
// create new audio player
AVAudioPlayer *myPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:fileURL error:nil];
[myPlayer play];
}
#end
I don't seem to have any errors. However, I am not getting any audio.
You're having an ARC issue here. myPlayer is being cleaned up when it's out of scope. Create a strong property, assign the AVAudioPlayer and you're probably all set!
#property(nonatomic, strong) AVAudioPlayer *myPlayer;
...
// create new audio player
self.myPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:fileURL error:nil];
[self.myPlayer play];
Is the filePath coming back with a valid value? Is the fileURL coming back with a valid value? Also, you should use the error parameter of AVAudioPlayer initWithContentsOfURL. If you use it it will likely tell you EXACTLY what the problem is.
Make sure you check for errors and non-valid values in your code. Checking for nil filepath and fileURL is the first step. Checking the error parameter is next.
Hope this helps.
What I do is create an entire miniature class just for this purpose. That way I have an object that I can retain and which itself retains the audio player.
- (void) play: (NSString*) path {
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
NSError* err = nil;
AVAudioPlayer *newPlayer =
[[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error: &err];
// error-checking omitted
self.player = newPlayer; // retain policy
[self.player prepareToPlay];
[self.player setDelegate: self];
[self.player play];
}

Performing actions from accelerometer

I'm very new to programming in Xcode and have already hit a brick wall.
What I am trying to do is to get a sound to play if there is sufficient acceleration detected.
Can someone please advise me on what I am doing wrong? (I suspect its everything).
CODE:
#import "LZDViewController.h"
#interface LZDViewController ()
#end
#implementation LZDViewController
- (void)viewDidLoad;{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CMAccelerometerData *accelerometerData = self.motionManager.accelerometerData;
if (fabsf(accelerometerData.acceleration.x) > 1.2
|| fabsf(accelerometerData.acceleration.y) > 1.2
|| fabsf(accelerometerData.acceleration.z) > 1.2)
{
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/audio.wav", [[NSBundle mainBundle] resourcePath]]];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
audioPlayer.numberOfLoops = 0;
[audioPlayer play];
}
}
#end
EDIT:
Here is the code for my .h
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
#import <AVFoundation/AVFoundation.h>
#interface LZDViewController : UIViewController
//{AVAudioPlayer *audioPlayer;}
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
#end
I'm still not getting any joy at all, and I do apologise for the hand holding.
All I get is a white screen but the sound file never plays. I have imported the correct framework and can get the sound playing with just a button, but I really want to play with the accelerometer.
Thanks
Alright, the first thing you're going to want to do is define two properties in either your interface file, or in the interface extension at the top of your implementation file. These will be the motion manager, and the audio player.
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
Then we can get everything else done in viewDidLoad. The code below sets up a new operation queue for the motion manager to receive accelerometer events on, initializes the audio player and the motion manager, and then starts getting accelerometer updates at an interval of 30 times per second.
Finally, within the accelerometers update block, just use the same logic you already build to check the acceleration data and if it meets your requirements continue on to the next condition.
**I have configured this example to not start playing the sound again if the player is already playing
- (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *motionQueue = [NSOperationQueue new];
NSURL *url = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:#"audioFile" ofType:#"m4a"]];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[self.audioPlayer prepareToPlay];
self.motionManager = [CMMotionManager new];
[self.motionManager setAccelerometerUpdateInterval:1.0f/30.0f];
[self.motionManager startAccelerometerUpdatesToQueue:motionQueue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (fabsf(accelerometerData.acceleration.x) > 1.8 || fabsf(accelerometerData.acceleration.y) > 1.8 || fabsf(accelerometerData.acceleration.z) > 1.8) {
NSLog(#"%#",accelerometerData);
if (self.audioPlayer) {
if (!self.audioPlayer.isPlaying) {
[self.audioPlayer play];
}
}
}
}];
}

AudioServicesPlaySystemSound not playing except when I step over

I'm writing an alarm clock app and I need to have the user select the chime to wake up to. I have a UITableView with a list of my sounds and when the user taps a sound I want it to play.
However, my sounds do not play unless I step over the call to AudioServicesPlaySystemSound (or if I put a breakpoint on the call to AudioServicesDisposeSystemSoundID which triggers after I hear the sound).
I had a theory that this was a main thread issue so I used performSelectorOnMainThread:
[self performSelectorOnMainThread:#selector(playWakeUpSound)
withObject:nil
waitUntilDone:true];
but that didn't help. (The value of waitUntilDone doesn't seem to matter.)
This question was promising but I checked and I'm only calling my method once.
This is only a guess, since I don't know what is in playWakeUpSound. If you are using AVAudioPlayer within that call and ARC, it might be because it is getting released. Store the instance of AVAudioPlayer as a member of the class and it should play.
#interface MyClass
{
AVAudioPlayer *player;
}
#end
Then in the implementation:
#implementation MyClass
- (void)playWakeUpSound {
// Assuming name and extension is set somewhere.
NSURL* musicFile = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:name ofType:extension]];
NSError* error = nil;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:musicFile error:&error];
if (error) {
NSLog(#"%#", error.localizedDescription);
} else {
[player play];
}
}
#end

Resources