how to make audio meter level with avaudiorecorder - iphone

I am trying to create a audio meter level while I am recording the user voice using avaudiorecorder. Can someone help me in that regard?

Actually, the code is pretty straightforward since AVAudioPlayer and AVAudioRecorder have built in methods to get you on your way. My approach was this:
make a repeating call to -updateMeters and the averagePowerForChannel: & peakPowerForChannel: methods and call a delegate method to notify the controlling object
Example:
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
NSInvocationOperation *operation=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateMeter) object:nil];
[queue addOperation: operation];
and
-(void)updateMeter
{
do {
//don't forget:
[recorder updateMeters];
self.averagePower = [recorder averagePowerForChannel:0];
self.peakPower = [recorder peakPowerForChannel:0];
// we don't want to surprise a ViewController with a method call
// not in the main thread
[self.delegate performSelectorOnMainThread: #selector(meterLevelsDidUpdate:) withObject:self waitUntilDone:NO];
[NSThread sleepForTimeInterval:.05]; // 20 FPS
}while(someCondition);
}
If your View Controller implements the meterLevelsDidUpdate: method, you can use this method to update your Level Meter.
create a UIView subclass with a subview that changes its height according to the average or peak value(s). Adjust to taste.

Easy, you can use NSTimer for that:
- (void)startAudioMetering {
self.meterTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(updateAudioMeter)userInfo:nil repeats:YES];
}
- (void)stopAudioMetering {
[self.meterTimer invalidate];
}
- (void)updateAudioMeter { //called by timer
// audioRecorder being your instance of AVAudioRecorder
[self.audioRecorder updateMeters];
self.dBLevel = [self.audioRecorder averagePowerForChannel:0];
}
WARNING: While creating your AVAudioRecorder instance, you have to call meteringEnabled AFTER you call prepareToRecord or record, otherwise it won't updateMeters:
[self.audioRecorder prepareToRecord];
self.audioRecorder.meteringEnabled = YES;

Swift code based on Tom's answer:
NSOperationQueue().addOperationWithBlock({[weak self] in
repeat {
self?.audioRecorder.updateMeters()
self?.averagePower = self?.audioRecorder.averagePowerForChannel(0)
self?.peakPower = self?.audioRecorder.peakPowerForChannel(0)
self?.performSelectorOnMainThread(#selector(DictaphoneViewController.updateMeter), withObject: self, waitUntilDone: false)
NSThread.sleepForTimeInterval(0.05)//20 FPS
}
while (someCondition)
})
Do the meter UI stuff inside func updateMeter(){//UI stuff here}

Its pretty simple,
The values you get in the buffer are positive and negative (this is how the waves work) so if you do the average of that values it will give you a near 0 value.
So what you have to do is just put all values positive (with the Math.abs() function) and then do the avarage, it will return you the sound level.
Hope this helps ;)

You can also use ReactiveCocoa and make use of interval:onScheduler::
Returns a signal that sends the current date/time every interval on scheduler.
Using a single audio channel:
#weakify(self)
RACDisposable *metersDisposable = [[RACSignal // make sure you dispose it eventually
interval:0.1
onScheduler:[RACScheduler scheduler]]
subscribeNext:^(NSDate *) {
#strongify(self)
[recorder updateMeters];
auto averagePower = [recorder averagePowerForChannel:0];
auto peakPower = [recorder peakPowerForChannel:0];
// Inform the delegate or make other use of averagePower and peakPower
}];

I found the answer in following link.
AVMeter for AVPlayer
Though it requires lot of customizations but I feel I will be able to do it.

Related

Image is already being captured or camera not yet ready

I am calling this takePicture function 5 times because i neeed to take 5 picture on on click(Burst Mode)
for(count=0;count<5;count++)
{
[picker takePicture];
[NSThread sleepForTimeInterval:0.5];
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
I am getting this error UIImagePickerController: ignoring request to take picture; image is already being captured or camera not yet ready.
Not sure but i think camera not yet ready ... Because you are trying to capture images continuously.... I think you will have to delay for few seconds before call take picture method again..... Dont do it in for loop i would like to suggest you please use NSTimer instead of looping.
something like this -
Declare
-(void)startTimer;
and
int count;
in your .h class then see below code -
-(void)yourTakePictureButtonClick:(id)sender
{
[self startTimer];
}
-(void)startTimer
{
count = 0;
yourTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(myFunctionForClickImage) userInfo:nil repeats:YES];
}
-(void)myFunctionForClickImage
{
[picker takePicture];
count ++;
if (count < 5)
{
[yourTimer invalidate];
}
}
Hi H2SO4 (Nice Name han)
The most probable reason seems to be the absence of the required key in your info.plist file. You will need to configure UIRequiredDeviceCapabilities. Also, you must implement the delegate object. For details, have a look at
http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/CameraAndPhotoLib_TopicsForIOS/Articles/TakingPicturesAndMovies.html#//apple_ref/doc/uid/TP40010406.
HTH,
Another thing you should release your resources in every call inside loop.
You can delay with following.
[picker performSelector:#selector(takePicture) withObject:nil afterDelay:2.0];
For more you can visit...
http://highoncoding.com/Articles/856_Building_Instagram_for_the_iPhone_Part_2.aspx

How to perform operations when playing sound in iPhone?

I play a MP3 in my iPhone app using AVAudioPlayer; i need to perform some operations at certain times (say 30th seconds, 1 minute); is there a way to invoke callback functions based on mp3 playing time?
I believe the best solution is to start an NSTimer as you start the AVAudioPlayer playing. You could set the timer to fire every half second or so. Then each time your timer fires, look at the currentTime property on your audio player.
In order to do something at certain intervals, I'd suggest you kept an instance variable for the playback time from last time your timer callback was called. Then if you had passed the critical point between last callback and this, do your action.
So, in pseudocode, the timer callback:
Get the currentTime of your AVAudioPlayer
Check to see if currentTime is greater than criticalPoint
If yes, check to see if lastCurrentTime is less than criticalPoint
If yes to that too, do your action.
Set lastCurrentTime to currentTime
If you're able to use AVPlayer instead of AVAudioPlayer, you can set boundary or periodic time observers:
// File URL or URL of a media library item
AVPlayer *player = [[AVPlayer alloc] initWithURL:url];
CMTime time = CMTimeMakeWithSeconds(30.0, 600);
NSArray *times = [NSArray arrayWithObject:[NSValue valueWithCMTime:time]];
id playerObserver = [player addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
NSLog(#"Playback time is 30 seconds");
}];
[player play];
// remove the observer when you're done with the player:
[player removeTimeObserver:playerObserver];
AVPlayer documentation:
http://developer.apple.com/library/ios/#documentation/AVFoundation/Reference/AVPlayer_Class/Reference/Reference.html
I found this link describing a property property which seems to indicate you can get the current playback time.
If the sound is playing, currentTime is the offset of the current
playback position, measured in seconds from the start of the sound. If
the sound is not playing, currentTime is the offset of where playing
starts upon calling the play method, measured in seconds from the
start of the sound.
By setting this property you can seek to a specific point in a sound
file or implement audio fast-forward and rewind functions.
To check the time and perform your action you can simply query it:
if (avAudioPlayerObject.currentTime == 30.0) //You may need a more broad check. Double may not be able to exactly represent 30.0s
{
//Do Something
}
with multithreading your goal is simple, just do like this :
1 : in your main thread create a variable for storing time passed
2 : create new thread like "checkthread" that check each 30-20 sec(as you need)
3 : if the time passed is what you want do the callback
Yes Sure you can ...it's tricky i hope it works for you but it works for me ..
1- you play your mp3 file.
2- [self performSelector:#selector(Operation:) withObject:Object afterDelay:30];
then the function
-(void)Operation:(id)sender;
called; so you fired function after 30 second of mp3 file .. you can make many of function based on time you want..
3- there is other solution using timers
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(CheckTime:) userInfo:nil repeats:YES];
it will fire function called Check Time
-(void)CheckTime:(id)sender{
if (avAudioPlayerObject.currentTime == 30.0)
{
//Do Something
//Fire and function call such
[self performSelector:#selector(Operation:) withObject:Object]
}
}
then you can change time interval you want and repeats is for you to control repeat this action every 5 seconds or not..
Hope that helpful..
Thanks
i think ,you want to play different sound-files after 30sec then use this code :
1) all sound-files put in Array and then retrieve from document directory
2)then try this:
-(IBAction)play_sound
{
BackgroundPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:[Arr_tone_selected objectAtIndex:j]ofType:#"mp3"]]error:NULL];
BackgroundPlayer.delegate=self;
[BackgroundPlayer play];
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[BackgroundPlayer stop];
j++;
[self performSelector:#selector(play_sound) withObject:Object afterDelay:30];
}

Sound playback delay (hiccup) when OpenAL is implemented on iphone

I implemented OpenAL code to my iphone game. When I starts the game, it runs for 1 sec and stalls for 2 sec then resumes (hiccup effect). I believe its delayed due to the sound files loading. What is the solution? Can anyone recommend any book, site or sources code (not the iphone reference, please)? Is there a loading process and where should I initialize the loading process? Would that help?
Below, I have included the related components of the OpenAL code that I have implemented. The sound file will be played and invoked by a "if" statement in the gameloop. The OpenALController class is for the sound sources and buffers creation and the InitOpenAL method is invoked in OpenALController. MyView is a customized subclass of UIView and connected to the main view (I didn't use the default view).
// MyView.m
// A customized UIView as the main view.
#import "OpenALSoundController.h"
- (void)startPlaying{
...
[self initializeValuables];
...
[self initializeTimer];
}
- (void)initializeTimer {
if (theTimer == nil) {
theTimer = [CADisplayLink displayLinkWithTarget:self selector:#selector)gameLoop)];
theTimer.frameInterval = 2;
[theTimer addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
}
}
- (void)gameLoop {
...
If something = true
// Play the sound
[[OpenALSoundController sharedSoundController] playSound1];
...
}
...
#end
// OpenALSoundController.h
#interface OpenALSoundController : NSObject {
...}
...
+ (OpenALSoundController*) sharedSoundController
...
#end
// OpenALSoundController.m
// Singleton accessor
{
static OpenALSoundController* shared_sound_controller;
#synchronized(self)
{
if(nil == shared_sound_controller)
{
shared_sound_controller = [[OpenALSoundController alloc] init];
}
return shared_sound_controller;
}
return shared_sound_controller;
}
- (void) initOpenAL{
...
file_url = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:#"fire" ofType:#"wav"]];
firePcmData = MyGetOpenALAudioDataAll((CFURLRef)file_url, &data_size, &al_format,&sample_rate);
alBufferData(fireOutputBuffer, al_format, firePcmData, data_size, sample_rate);
[file_url release];
...
alSourcei(outputSourceFire, AL_BUFFER, fireOutputBuffer);
...
}
You might be interested in Finch, an OpenAL sound engine for iOS. It’s very well suited to games. It’s usually better to reuse some already existing code than develop and maintain your own.
First its better to use mp3, as wav files are huge and loading from disk takes time. Mp3 files are smaller on disk, loaded into memory and decompressed there for playing. Try experimenting by reducing mp3 bitrate/encoding quality too.
Also you need to preload sounds to avoid hiccups, otherwise you will have a delay the first time a sound is played.

Is this a good way to do a game loop for an iPhone game?

I'm new to iPhone dev, but trying to build a 2D game. I was following a book, but the game loop it created basically said:
function gameLoop
update()
render()
sleep(1/30th second)
gameLoop
The reasoning was that this would run at 30fps. However, this seemed a little mental, because if my frame took 1/30th second, then it would run at 15fps (since it'll spend as much time sleeping as updating).
So, I did some digging and found the CADisplayLink class which would sync calls to my gameLoop function to the refresh rate (or a fraction of it). I can't find many samples of it, so I'm posting here for a code review :-) It seems to work as expected, and it includes passing the elapsed (frame) time into the Update method so my logic can be framerate-independant (however I can't actually find in the docs what CADisplayLink would do if my frame took more than its allowed time to run - I'm hoping it just does its best to catch up, and doesn't crash!).
//
// GameAppDelegate.m
//
// Created by Danny Tuppeny on 10/03/2010.
// Copyright Danny Tuppeny 2010. All rights reserved.
//
#import "GameAppDelegate.h"
#import "GameViewController.h"
#import "GameStates/gsSplash.h"
#implementation GameAppDelegate
#synthesize window;
#synthesize viewController;
- (void) applicationDidFinishLaunching:(UIApplication *)application
{
// Create an instance of the first GameState (Splash Screen)
[self doStateChange:[gsSplash class]];
// Set up the game loop
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(gameLoop)];
[displayLink setFrameInterval:2];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void) gameLoop
{
// Calculate how long has passed since the previous frame
CFTimeInterval currentFrameTime = [displayLink timestamp];
CFTimeInterval elapsed = 0;
// For the first frame, we want to pass 0 (since we haven't elapsed any time), so only
// calculate this in the case where we're not the first frame
if (lastFrameTime != 0)
{
elapsed = currentFrameTime - lastFrameTime;
}
// Keep track of this frames time (so we can calculate this next time)
lastFrameTime = currentFrameTime;
NSLog([NSString stringWithFormat:#"%f", elapsed]);
// Call update, passing the elapsed time in
[((GameState*)viewController.view) Update:elapsed];
}
- (void) doStateChange:(Class)state
{
// Remove the previous GameState
if (viewController.view != nil)
{
[viewController.view removeFromSuperview];
[viewController.view release];
}
// Create the new GameState
viewController.view = [[state alloc] initWithFrame:CGRectMake(0, 0, IPHONE_WIDTH, IPHONE_HEIGHT) andManager:self];
// Now set as visible
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
- (void) dealloc
{
[viewController release];
[window release];
[super dealloc];
}
#end
Any feedback would be appreciated :-)
PS. Bonus points if you can tell me why all the books use "viewController.view" but for everything else seem to use "[object name]" format. Why not [viewController view]?
You have Cocos2D listed as a tag in your question but you're not actually using any Cocos2D code. Have you considered doing a Cocos2D implementation for your games? It will save you some unneeded hassle.
As for your syntax question [myObject view] is used for calling methods on myObject while myObject.view is used for setting/getting instance variables exposed as properties. I don't recall if you can retrieve instance variables using [myObject view] as well but if that works then I guess the only difference between the two is the syntax and you could use both methods to retrieve instance variables.
Hope some of that rambling is useful to you.
From many GL example by Apple, I think you should use a timer.
animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0/60.0)
target:self
selector:#selector(updateAndRender)
userInfo:nil
repeats:TRUE];

AVAudioPlayer is not rentrant correct?

It appears that AVAudioPlayer is not reentent. So in the following would soundfx play to completion, delay 1 second, then play again, rather then the 1 second delay - and resultant overlapping playback I desire:
// ...
AVAudioPlayer* soundfx = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:nil];
(void) makeSomeNoise {
[soundfx play];
sleep(1);
[soundfx play];
}
Must I then resort to NSOperation - threading - to achieve my goal of overlapping playback?
Note: I am using IMA4/ADPCM format which is apparently the correct format for layered sound playback.
Cheers,
Doug
It's not that AVAudioPlayer is not reentrant. It is that AVAudioPlayer starts to play your sound after the runloop has ended, and not in your makeSomeNoise function. If you want to play your sound with a one second delay, you can do this:
(void) makeSomeNoise {
[soundfx play];
[soundfx performSelector:play withObject: nil afterDelay:1.0];
}