I will like to measure the sound volume of the surrounding, not too sure if I am doing the right thing.
I will like to create a VU meter of a range of 0(quiet) to 120(very noisy).
I gotten the Peak and Avg power but are very high in normal quiet environment. Do give me some pointer.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//creating an audio CAF file in the temporary directory, this isn’t ideal but it’s the only way to get this class functioning (the temporary directory is erased once the app quits). Here we also specifying a sample rate of 44.1kHz (which is capable of representing 22 kHz of sound frequencies according to the Nyquist theorem), and 1 channel (we do not need stereo to measure noise).
NSDictionary* recorderSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:44100],AVSampleRateKey,
[NSNumber numberWithInt:1],AVNumberOfChannelsKey,
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
nil];
NSError* error;
NSURL *url = [NSURL fileURLWithPath:@"/dev/null"];
recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recorderSettings error:&error];
//enable measuring
//tell the recorder to start recording:
[recorder record];
if (recorder) {
[recorder prepareToRecord];
recorder.meteringEnabled = YES;
[recorder record];
levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target: self selector: @selector(levelTimerCallback:) userInfo: nil repeats: YES];
} else
{
NSLog(@"%@",[error description]);
}
}
- (void)levelTimerCallback:(NSTimer *)timer {
[recorder updateMeters];
const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [recorder averagePowerForChannel:0]));
lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
NSLog(@"Average input: %f Peak input: %f Low pass results: %f", [recorder averagePowerForChannel:0], [recorder peakPowerForChannel:0], lowPassResults);
float tavgPow =[recorder averagePowerForChannel:0] + 120.0;
float tpPow = [recorder peakPowerForChannel:0] + 120.0;
float avgPow = tavgPow;//(float)abs([recorder averagePowerForChannel:0]);
float pPow = tpPow;//(float)abs([recorder peakPowerForChannel:0]);
NSString *tempAvg = [NSString stringWithFormat:@"%0.2f",avgPow];
NSString *temppeak = [NSString stringWithFormat:@"%0.2f",pPow];
[avg setText:tempAvg];
[peak setText:temppeak];
NSLog(@"Average input: %f Peak input: %f Low pass results: %f", avgPow,pPow , lowPassResults);
}
Apple uses a lookup table in their SpeakHere sample that converts from dB to a linear value displayed on a level meter. This is to save device power (I guess).
I also needed this, but didn't think a couple of float calculations every 1/10s (my refresh rate) would cost so much device power. So, instead of building up a table I moulded their code into:
float level; // The linear 0.0 .. 1.0 value we need.
const float minDecibels = -80.0f; // Or use -60dB, which I measured in a silent room.
float decibels = [audioRecorder averagePowerForChannel:0];
if (decibels < minDecibels)
{
level = 0.0f;
}
else if (decibels >= 0.0f)
{
level = 1.0f;
}
else
{
float root = 2.0f;
float minAmp = powf(10.0f, 0.05f * minDecibels);
float inverseAmpRange = 1.0f / (1.0f - minAmp);
float amp = powf(10.0f, 0.05f * decibels);
float adjAmp = (amp - minAmp) * inverseAmpRange;
level = powf(adjAmp, 1.0f / root);
}
I'm using an AVAudioRecorder
, hence you see getting the dB's with averagePowerForChannel:
, but you can fill your own dB value there.
Apple's example used double
calculations, which I don't understand because for audio metering float
accuracy is more than sufficient, and costs less device power.
Needless to say, you can now scale this calculated level
to your 0 .. 120 range with a simple level * 120.0f
.
The above code can be sped up when we fix root
at 2.0f
, by replacing powf(adjAmp, 1.0f / root)
with sqrtf(adjAmp)
; but that's a minor thing, and a very good compiler might be able to do this for us. And I'm almost sure that inverseAmpRange
will be calculated once at compile-time.