AVAudioEngine
でWWDCビデオ(実際にはセッション502 AVAudioEngine
)を見たところ、この技術で構築されたアプリを作成できることに非常に興奮しています。
マイク入力またはミキサーの出力のレベルモニタリングをどのように行うことができるかを理解できませんでした。
誰か助けてもらえますか?明確にするために、私は現在の入力信号を監視すること(そしてこれをUIに表示すること)について話しているのであって、チャンネル/トラックの入出力音量設定について話しているのではありません。
AVAudioRecorder
でこれを実行できることは知っていますが、これはAVAudioNode
が必要とするAVAudioEngine
ではありません。
メインミキサーにタップを取り付けてから、フレーム長を設定して高速化し、サンプルを読み取って平均を取得します。次のようになります。
フレームワークを上にインポート
#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_meamgvの代わりにvDSP_maxmgvを使用します。
LEVEL_LOWPASS_TRIGは、0.0〜1.0の値の単純なフィルターです。0.0を設定すると、すべての値がフィルター処理され、データは取得されません。 1.0に設定すると、ノイズが多すぎます。基本的に、値が高いほど、データのバリエーションが多くなります。 0.10から0.30の間の値は、ほとんどのアプリケーションに適しているようです。
同等のSwift 3コード'Farhad Malekpour'の答え
フレームワークを上にインポート
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)
}
}
#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
私は少し奇妙な別の解決策を発見しましたが、完全にうまく機能し、タップよりもはるかに優れています。ミキサーには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);
}
}
Swift 5 +
上記のプロジェクトをダウンロードして、プロジェクトの「Microphone.Swift」クラスをコピーします。
これらのファウリングコードをコピーしてプロジェクトに貼り付けます。
import AVFoundation
private var mic = MicrophoneMonitor(numberOfSamples: 1)
private var timer:Timer!
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(startMonitoring), userInfo: nil, repeats: true)
timer.fire()
}
@objc func startMonitoring() {
print("sound level:", normalizeSoundLevel(level: mic.soundSamples.first!))
}
private func normalizeSoundLevel(level: Float) -> CGFloat {
let level = max(0.2, CGFloat(level) + 50) / 2 // between 0.1 and 25
return CGFloat(level * (300 / 25)) // scaled to max at 300 (our height of our bar)
}
3.ビールを開けてお祝いしましょう!
#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