Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to understand AVAudioPlayer and audio level metering

I'm trying to understand AVAudioPlayer and audio level metering. What I have below is an object "AudioPlayer" that is playing a short audio sound. Now I want to output the power of this sound (decibels). Somehow I don't think I'm doing this right.

        audioPlayer.meteringEnabled = YES;
        [audioPlayer play];
        int channels = audioPlayer.numberOfChannels;
        [audioPlayer updateMeters];
        for (int i=0; i<channels; i++) {
            //Log the peak and average power
            NSLog(@"%d %0.2f %0.2f", i, [audioPlayer peakPowerForChannel:0],[audioPlayer averagePowerForChannel:0]);

The NSLog output of this is 0 -160.00 -160.00 1 -160.00 -160.00

Now according to Apple "A return value of 0 dB indicates full scale, or maximum power; a return value of -160 dB indicates minimum power" So does this mean this sound is at minimum power? I don't think this is true because the audio snippet is a fairly loud sound. I think I'm missing something here, any clarification would be appreciated.

like image 526
SNV7 Avatar asked May 08 '12 18:05

SNV7


2 Answers

There are several issues with your code - Jacques has already pointed out most of them.

You have to call [audioPlayer updateMeters]; each time before reading the values. You'll probably be best off instantiating a NSTimer.

Declare an iVar NSTimer *playerTimer; in your class @interface.

Also it doesn't hurt to adopt <AVAudioPlayerDelegate> in your class so you'll be able to invalidate the timer after player has finished playing.

Then change your code to:

audioPlayer.meteringEnabled = YES;
audioPlayer.delegate = self;

if (!playerTimer)
{
    playerTimer = [NSTimer scheduledTimerWithTimeInterval:0.001
                  target:self selector:@selector(monitorAudioPlayer)
                userInfo:nil
                 repeats:YES];
}

[audioPlayer play];

Add this two methods to your class:

-(void) monitorAudioPlayer
{   
    [audioPlayer updateMeters];
    
    for (int i=0; i<audioPlayer.numberOfChannels; i++)
    {
        //Log the peak and average power
         NSLog(@"%d %0.2f %0.2f", i, [audioPlayer peakPowerForChannel:i],[audioPlayer averagePowerForChannel:i]);
    }
}

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{   
    NSLog (@"audioPlayerDidFinishPlaying:");
    [playerTimer invalidate];
    playerTimer = nil;
}

And you should be good to go.

like image 142
Rok Jarc Avatar answered Oct 15 '22 16:10

Rok Jarc


You're updating and then asking for the value of the meters almost immediately after the sound starts -- that updateMeters is probably running a few tens of milliseconds after you send play. So if there's any silence at the beginning of the clip, you could very well be getting the correct reading. You should trying delaying your inspection, and you may also need to send updateMeters inside the loop, right before you inspect the values.

You're also never actually getting the meter values for channels > 0, because you pass 0 no matter what the value of i is in the loop. I think you meant to do this:

for (int currChan = 0; currChan < channels; currChan++) {
    //Log the peak and average power
    NSLog(@"%d %0.2f %0.2f", currChan, [audioPlayer peakPowerForChannel:currChan], [audioPlayer averagePowerForChannel:currChan]);
}
like image 33
jscs Avatar answered Oct 15 '22 16:10

jscs