使用 Audio Graph,了解环境噪声并在 UWP 应用中进行过滤

Posted

技术标签:

【中文标题】使用 Audio Graph,了解环境噪声并在 UWP 应用中进行过滤【英文标题】:Using Audio Graph, learn environment noise and filter in a UWP App 【发布时间】:2019-02-05 09:56:16 【问题描述】:

这是Using UWP monitor live audio and detect gun-fire/clap sound这里的一个扩展问题

感谢Dernis,我终于得到了可以监控实时音频并在分贝计数高于某个范围时触发事件的代码。

当我们在办公室/封闭/安静区域运行时,这非常有效。

但是当我用app开路时,会出现交通声、风声、人说话声等噪音,并且无法正确识别BLOW事件。

我想实现类似精益环境按钮。在应用开始监控之前,用户点击“Lean Environment”,它会识别灵敏度级别并将过滤设置为我的现场音频,然后我开始监控打击。 如果不增加太多负载,我想将音频录制到文件中。

任何关于从哪里开始的帮助将不胜感激。

OnNavigatedTo

protected override async void OnNavigatedTo(NavigationEventArgs e)
        
           //other logic
            await CreateInputDeviceNodeAsync(_deviceId);
        

CreateInputDeviceNodeAsync

public async Task<bool> CreateInputDeviceNodeAsync(string deviceId)
        
            Console.WriteLine("Creating AudioGraphs");
            // Create an AudioGraph with default settings
            AudioGraphSettings graphSettings = new AudioGraphSettings(AudioRenderCategory.Media)
            
                EncodingProperties = new AudioEncodingProperties
                
                    Subtype = "Float",
                    SampleRate = 48000,
                    ChannelCount = 2,
                    BitsPerSample = 32,
                    Bitrate = 3072000
                
            ;

            CreateAudioGraphResult audioGraphResult = await AudioGraph.CreateAsync(graphSettings);

            if (audioGraphResult.Status != AudioGraphCreationStatus.Success)
            
                _rootPage.NotifyUser("Cannot create graph", NotifyType.ErrorMessage);
                return false;
            

            _audioGraph = audioGraphResult.Graph;
            AudioGraphSettings audioGraphSettings =
                new AudioGraphSettings(AudioRenderCategory.GameChat)
                
                    EncodingProperties = AudioEncodingProperties.CreatePcm(48000, 2, 32),
                    DesiredSamplesPerQuantum = 990,
                    QuantumSizeSelectionMode = QuantumSizeSelectionMode.ClosestToDesired
                ;
            _frameOutputNode = _audioGraph.CreateFrameOutputNode(_audioGraph.EncodingProperties);
            _quantum = 0;
            _audioGraph.QuantumStarted += Graph_QuantumStarted;
            LoudNoise += BlowDetected;

            DeviceInformation selectedDevice = null;

            if (!string.IsNullOrWhiteSpace(_deviceId))
                selectedDevice = await DeviceInformation.CreateFromIdAsync(_deviceId);
            if (selectedDevice == null)
            
                string device = Windows.Media.Devices.MediaDevice.GetDefaultAudioCaptureId(
                    Windows.Media.Devices.AudioDeviceRole.Default);
                if (!string.IsNullOrWhiteSpace(device))
                    selectedDevice = await DeviceInformation.CreateFromIdAsync(device);
                else
                
                    _rootPage.NotifyUser($"Could not select Audio Device device", NotifyType.ErrorMessage);
                    return false;
                
            
            CreateAudioDeviceInputNodeResult result =
               await _audioGraph.CreateDeviceInputNodeAsync(MediaCategory.Media, audioGraphSettings.EncodingProperties,
                   selectedDevice);
            if (result.Status != AudioDeviceNodeCreationStatus.Success)
            
                _rootPage.NotifyUser("Cannot create device output node", NotifyType.ErrorMessage);
                return false;
            

            _selectedMicrophone = selectedDevice.Name;

            _deviceInputNode = result.DeviceInputNode;
            _deviceInputNode.AddOutgoingConnection(_frameOutputNode);
            _frameOutputNode.Start();
            _audioGraph.Start();
            return true;
        

Graph_QuantumStarted

private void Graph_QuantumStarted(AudioGraph sender, object args)
        
            if (++_quantum % 2 != 0) return;
            AudioFrame frame = _frameOutputNode.GetFrame();
            float[] dataInFloats;
            using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
            using (IMemoryBufferReference reference = buffer.CreateReference())
                unsafe
                
                    // Get the buffer from the AudioFrame
                    // ReSharper disable once SuspiciousTypeConversion.Global
                    ((IMemoryBufferByteAccess) reference).GetBuffer(out byte* dataInBytes,
                        out var capacityInBytes);

                    var dataInFloat = (float*) dataInBytes;
                    dataInFloats = new float[capacityInBytes / sizeof(float)];

                    for (var i = 0; i < capacityInBytes / sizeof(float); i++)
                    
                        dataInFloats[i] = dataInFloat[i];
                    
                

            double decibels = dataInFloats.Aggregate<float, double>(0f, (current, sample) => current + Math.Abs(sample));

            decibels = 20 * Math.Log10(decibels / dataInFloats.Length);
            _decibelList.Add(decibels);

            if (double.IsInfinity(decibels) || decibels < _threshold) return;//-45
            if (_watch != null && _watch.Elapsed <= TimeSpan.FromSeconds(1)) return;
            LoudNoise?.Invoke(this, decibels);
            _watch = Stopwatch.StartNew();
        

【问题讨论】:

【参考方案1】:

这只是统计数据。在实际运行之前,您可能希望收集至少 50 帧(1 秒)的数据(也许让用户通过按住和释放按钮来决定)。然后你可能想确定分贝水平通常在哪里。我可以想到 3 种方法来做到这一点。

private void Graph_QuantumStarted(AudioGraph sender, object args)
        
            ...
            double decibels = dataInFloats.Aggregate<float, double>(0f, (current, sample) => current + Math.Abs(sample)); // I dislike the fact that the decibels variable is initially inaccurate, but it's your codebase.
            decibels = 20 * Math.Log10(decibels / dataInFloats.Length);


            if (scanning) // class variable (bool), you can set it from the UI thread like this
            
                _decibelList.Add(decibels); // I assume you made this a class variable
            
            else if (decibels == Double.NaN)
            
                // Code by case below
            
            else if (decibels > _sensitivity) //_sensitivity is a class variable(double), initialized to Double.NaN
            
                LoudNoise?.Invoke(this, true); // Calling events is a wee bit expensive, you probably want to handle the sensitivity before Invoking it, I'm also going to do it like that to make this demo simpler
            
        
    如果您可以控制确保没有足够响亮的尖峰,您希望它消失,您可以取所有这些帧的最大值,并说如果超过灵敏度是maxDecibels + Math.Abs(maxDecibels* 0.2)(分贝可能是负数,因此绝对)。
double maxDecibels = _decibelList.OrderByDescending(x => x)[0];
_sensitivity = maxDecibels + Math.Abs(maxDecibels* 0.2);
    如果您无法控制何时出现峰值,那么您可以收集这些帧,进行排序,并让它获取项目 [24](您的 100 项列表中的)并说这就是灵敏度。
sensitivity = _decibelList.OrderByDescending(x => x)[24]; // If you do a variable time you can just take Count/4 - 1 as the index
    (我认为它是最好的,但我真的不知道统计数据)遍历帧的分贝列表并跟踪值的平均差异以及什么指数变化最大。然后,从该索引之后找到最大值,然后说 75% 的变化是敏感度。 (不要在此使用 LinkedList)
int greatestChange, changeIndex = 0;
double p = Double.NaN; // Previous
for (int i = 0; i < _decibelList.Count(); i++)

    if (p != Double.Nan)
    
        change = Math.Abs(_decibelList[i] - p);
        if (Math.Abs(change > greatestChange)
        
            greatestChange = change;
            changeIndex = i;
         
    
    p = _decibelList[i];


int i = changeIndex;
p = Double.NaN; // reused
double c= Double.NaN; // Current
do

    p = c != Double.NaN ? c : _decibelList[i];
    c = _decibelList[++i];
 while (c < p);

_sensitivity = ((3 * c) + _decibelList[changeIndex]) / 4;

注意:您可以(在某种程度上)通过使用 LinkedList 并在适当的位置插入来消除排序的需要

【讨论】:

这也能降低噪音吗?像低通滤波之类的东西...... 不,你需要一个快速傅里叶变换

以上是关于使用 Audio Graph,了解环境噪声并在 UWP 应用中进行过滤的主要内容,如果未能解决你的问题,请参考以下文章

WEB AUDIO API 产生雨水噼啪声

车载环境下的噪声分析

Audio Unit 介绍

Web Audio API:将 mp3 声音流合并到一个文件并在 <audio> 标签中播放

AudioKit - 尝试在 Audio Kit 5 中生成声音

Android Audio回声消除学习笔记