Подтвердить что ты не робот

Измерение уровня с помощью AVAudioEngine

Я просто смотрел видео WWDC (сеанс 502 AVAudioEngine на практике) на AVAudioEngine и очень рад сделать приложение, основанное на этой технологии.

Мне не удалось выяснить, как я могу контролировать уровень входа микрофона или выход микшера.

Может ли кто-нибудь помочь? Чтобы быть ясным, я говорю о мониторинге текущего входного сигнала (и его отображении в пользовательском интерфейсе), а не о настройке уровня входного/выходного канала/дорожки.

Я знаю, что вы можете сделать это с помощью AVAudioRecorder, но это не AVAudioNode, который требуется AVAudioEngine.

4b9b3361

Ответ 1

Попробуйте установить кран на основной микшер, затем сделайте его быстрее, установив длину кадра, затем прочитайте образцы и получите среднее значение, что-то вроде этого:

импортировать рамки сверху

#import <Accelerate/Accelerate.h>

добавить недвижимость

@property float averagePowerForChannel0;
@property float averagePowerForChannel1;

тогда ниже тоже самое >>

self.mainMixer = [self.engine mainMixerNode];
[self.mainMixer installTapOnBus:0 bufferSize:1024 format:[self.mainMixer outputFormatForBus:0] block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
    [buffer setFrameLength:1024];
    UInt32 inNumberFrames = buffer.frameLength;

    if(buffer.format.channelCount>0)
    {
        Float32* samples = (Float32*)buffer.floatChannelData[0];
        Float32 avgValue = 0;

        vDSP_meamgv((Float32*)samples, 1, &avgValue, inNumberFrames);
        self.averagePowerForChannel0 = (LEVEL_LOWPASS_TRIG*((avgValue==0)?-100:20.0*log10f(avgValue))) + ((1-LEVEL_LOWPASS_TRIG)*self.averagePowerForChannel0) ;
        self.averagePowerForChannel1 = self.averagePowerForChannel0;
    }

    if(buffer.format.channelCount>1)
    {
        Float32* samples = (Float32*)buffer.floatChannelData[1];
        Float32 avgValue = 0;

        vDSP_meamgv((Float32*)samples, 1, &avgValue, inNumberFrames);
        self.averagePowerForChannel1 = (LEVEL_LOWPASS_TRIG*((avgValue==0)?-100:20.0*log10f(avgValue))) + ((1-LEVEL_LOWPASS_TRIG)*self.averagePowerForChannel1) ;
    }
}];

затем получите целевое значение, которое вы хотите

NSLog(@"===test===%.2f", self.averagePowerForChannel1);

чтобы получить пиковые значения, используйте vDSP_maxmgv вместо vDSP_meamgv.


LEVEL_LOWPASS_TRIG - это простой фильтр, значение которого варьируется от 0,0 до 1,0, если вы установите 0,0, вы будете фильтровать все значения и не получать никаких данных. Если вы установите его на 1,0, вы получите слишком много шума. В основном, чем выше значение, тем больше вариаций в данных. Кажется, значение от 0,10 до 0,30 подходит для большинства приложений.

Ответ 2

Эквивалентный код Swift 3 ответа "Фархад Малекпур"

импортировать рамки сверху

import Accelerate

объявить глобально

private var audioEngine: AVAudioEngine?
    private var averagePowerForChannel0: Float = 0
    private var averagePowerForChannel1: Float = 0
let LEVEL_LOWPASS_TRIG:Float32 = 0.30

используйте код ниже, где вам требуется

let inputNode = audioEngine!.inputNode//since i need microphone audio level i have used 'inputNode' otherwise you have to use 'mainMixerNode'
let recordingFormat: AVAudioFormat = inputNode!.outputFormat(forBus: 0)
 inputNode!.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) {[weak self] (buffer:AVAudioPCMBuffer, when:AVAudioTime) in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.audioMetering(buffer: buffer)
}

вычисления

private func audioMetering(buffer:AVAudioPCMBuffer) {
            buffer.frameLength = 1024
            let inNumberFrames:UInt = UInt(buffer.frameLength)
            if buffer.format.channelCount > 0 {
                let samples = (buffer.floatChannelData![0])
                var avgValue:Float32 = 0
                vDSP_meamgv(samples,1 , &avgValue, inNumberFrames)
                var v:Float = -100
                if avgValue != 0 {
                    v = 20.0 * log10f(avgValue)
                }
                self.averagePowerForChannel0 = (self.LEVEL_LOWPASS_TRIG*v) + ((1-self.LEVEL_LOWPASS_TRIG)*self.averagePowerForChannel0)
                self.averagePowerForChannel1 = self.averagePowerForChannel0
            }

            if buffer.format.channelCount > 1 {
                let samples = buffer.floatChannelData![1]
                var avgValue:Float32 = 0
                vDSP_meamgv(samples, 1, &avgValue, inNumberFrames)
                var v:Float = -100
                if avgValue != 0 {
                    v = 20.0 * log10f(avgValue)
                }
                self.averagePowerForChannel1 = (self.LEVEL_LOWPASS_TRIG*v) + ((1-self.LEVEL_LOWPASS_TRIG)*self.averagePowerForChannel1)
            }
    }

Ответ 3

Я обнаружил другое решение, которое немного странно, но работает отлично и намного лучше, чем крана. Микшер не имеет AudioUnit, но если вы передаете его на AVAudioIONode, вы можете получить AudioUnit и использовать средство измерения iOS. Вот как:

Чтобы включить или отключить измерение:

- (void)setMeteringEnabled:(BOOL)enabled;
{
    UInt32 on = (enabled)?1:0;
    AVAudioIONode *node = (AVAudioIONode*)self.engine.mainMixerNode;
    OSStatus err = AudioUnitSetProperty(node.audioUnit, kAudioUnitProperty_MeteringMode, kAudioUnitScope_Output, 0, &on, sizeof(on));
}

Чтобы обновить счетчики:

- (void)updateMeters;
{
    AVAudioIONode *node = (AVAudioIONode*)self.engine.mainMixerNode;

    AudioUnitParameterValue level;
    AudioUnitGetParameter(node.audioUnit, kMultiChannelMixerParam_PostAveragePower, kAudioUnitScope_Output, 0, &level);

    self.averagePowerForChannel1 = self.averagePowerForChannel0 = level;
    if(self.numberOfChannels>1)
    {
        err = AudioUnitGetParameter(node.audioUnit, kMultiChannelMixerParam_PostAveragePower+1, kAudioUnitScope_Output, 0, &level);
    }
}

Ответ 4

#define LEVEL_LOWPASS_TRIG .3

#import "AudioRecorder.h"





@implementation AudioRecord


-(id)init {
     self = [super init];
     self.recordEngine = [[AVAudioEngine alloc] init];

     return self;
}


 /**  ----------------------  Snippet Stackoverflow.com not including Audio Level Meter    ---------------------     **/


-(BOOL)recordToFile:(NSString*)filePath {

     NSURL *fileURL = [NSURL fileURLWithPath:filePath];

     const Float64 sampleRate = 44100;

     AudioStreamBasicDescription aacDesc = { 0 };
     aacDesc.mSampleRate = sampleRate;
     aacDesc.mFormatID = kAudioFormatMPEG4AAC; 
     aacDesc.mFramesPerPacket = 1024;
     aacDesc.mChannelsPerFrame = 2;

     ExtAudioFileRef eaf;

     OSStatus err = ExtAudioFileCreateWithURL((__bridge CFURLRef)fileURL, kAudioFileAAC_ADTSType, &aacDesc, NULL, kAudioFileFlags_EraseFile, &eaf);
     assert(noErr == err);

     AVAudioInputNode *input = self.recordEngine.inputNode;
     const AVAudioNodeBus bus = 0;

     AVAudioFormat *micFormat = [input inputFormatForBus:bus];

     err = ExtAudioFileSetProperty(eaf, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), micFormat.streamDescription);
     assert(noErr == err);

     [input installTapOnBus:bus bufferSize:1024 format:micFormat block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) {
       const AudioBufferList *abl = buffer.audioBufferList;
       OSStatus err = ExtAudioFileWrite(eaf, buffer.frameLength, abl);
       assert(noErr == err);


       /**  ----------------------  Snippet from stackoverflow.com in different context  ---------------------     **/


       UInt32 inNumberFrames = buffer.frameLength;
       if(buffer.format.channelCount>0) {
         Float32* samples = (Float32*)buffer.floatChannelData[0]; 
         Float32 avgValue = 0;
         vDSP_maxv((Float32*)samples, 1.0, &avgValue, inNumberFrames);
         self.averagePowerForChannel0 = (LEVEL_LOWPASS_TRIG*((avgValue==0)?
                                  -100:20.0*log10f(avgValue))) + ((1- LEVEL_LOWPASS_TRIG)*self.averagePowerForChannel0) ;
         self.averagePowerForChannel1 = self.averagePowerForChannel0;
       }

       dispatch_async(dispatch_get_main_queue(), ^{

         self.levelIndicator.floatValue=self.averagePowerForChannel0;

       });     


       /**  ---------------------- End of Snippet from stackoverflow.com in different context  ---------------------     **/

     }];

     BOOL startSuccess;
     NSError *error;

     startSuccess = [self.recordEngine startAndReturnError:&error]; 
     return startSuccess;
}



@end

Ответ 5

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/ExtendedAudioFile.h>
#import <CoreAudio/CoreAudio.h>
#import <Accelerate/Accelerate.h>
#import <AppKit/AppKit.h>

@interface AudioRecord : NSObject {

}

@property (nonatomic) AVAudioEngine *recordEngine;


@property float averagePowerForChannel0;
@property float averagePowerForChannel1;
@property float numberOfChannels;
@property NSLevelIndicator * levelIndicator;


-(BOOL)recordToFile:(NSString*)filePath;

@end

Ответ 6

AVAudioPlayer имеет функции измерения выходного сигнала.

AVAudioRecorder имеет функции для измерения входного сигнала.

Здесь сообщение в блоге и проект github, который использует рекордер.

Ответ 7

AVAudioPlayer и AVAudioRecorder - это способы "высокого уровня" для этого.

Вам нужен "жесткий" способ.

Затем установите кран на входе engineNode. Вы получаете звуковой буфер в закрытии.

let inputNode = audioEngine.inputNode
inputNode.installTapOnBus(etc.