【问题标题】:How can a custom audio effect produce some sound after a AudioFileInputNode has finished playingAudioFileInputNode 播放完成后自定义音频效果如何产生一些声音
【发布时间】:2020-05-15 06:44:51
【问题描述】:

我正在使用 AudioGraph API 在 C# 和 UWP 中开发音频应用程序。 我的 AudioGraph 设置如下: AudioFileInputNode --> AudioSubmixNode --> AudioDeviceOutputNode.

我在 AudioSubmixNode 上附加了一个自定义回声效果。 如果我播放 AudioFileInputNode,我可以听到一些回声。 但是当 AudioFileInputNode 播放结束时,回声会粗暴地停止。 我希望它在几秒钟后逐渐停止。 如果我使用 AudioGraph API 中的 EchoEffectDefinition,则在示例播放完成后回声不会停止。

我不知道问题是来自我的效果实现还是 AudioGraph API 的奇怪行为... SDK 中的“AudioCreation”示例中的行为与场景 6 中的行为相同。

这是我的自定义效果实现:

public sealed class AudioEchoEffect : IBasicAudioEffect
{
    public AudioEchoEffect()
    {
    }

    private readonly AudioEncodingProperties[] _supportedEncodingProperties = new AudioEncodingProperties[]
    {
        AudioEncodingProperties.CreatePcm(44100, 1, 32),
        AudioEncodingProperties.CreatePcm(48000, 1, 32),
    };

    private AudioEncodingProperties _currentEncodingProperties;
    private IPropertySet _propertySet;

    private readonly Queue<float> _echoBuffer = new Queue<float>(100000);
    private int _delaySamplesCount;

    private float Delay
    {
        get
        {
            if (_propertySet != null && _propertySet.TryGetValue("Delay", out object val))
            {
                return (float)val;
            }
            return 500.0f;
        }
    }

    private float Feedback
    {
        get
        {
            if (_propertySet != null && _propertySet.TryGetValue("Feedback", out object val))
            {
                return (float)val;
            }
            return 0.5f;
        }
    }

    private float Mix
    {
        get
        {
            if (_propertySet != null && _propertySet.TryGetValue("Mix", out object val))
            {
                return (float)val;
            }
            return 0.5f;
        }
    }

    public bool UseInputFrameForOutput { get { return true; } }

    public IReadOnlyList<AudioEncodingProperties> SupportedEncodingProperties { get { return _supportedEncodingProperties; } }

    public void SetProperties(IPropertySet configuration)
    {
        _propertySet = configuration;
    }

    public void SetEncodingProperties(AudioEncodingProperties encodingProperties)
    {
        _currentEncodingProperties = encodingProperties;

        // compute the number of samples for the delay
        _delaySamplesCount = (int)MathF.Round((this.Delay / 1000.0f) * encodingProperties.SampleRate);

        // fill empty samples in the buffer according to the delay
        for (int i = 0; i < _delaySamplesCount; i++)
        {
            _echoBuffer.Enqueue(0.0f);
        }
    }

    unsafe public void ProcessFrame(ProcessAudioFrameContext context)
    {
        AudioFrame frame = context.InputFrame;

        using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.ReadWrite))
        using (IMemoryBufferReference reference = buffer.CreateReference())
        {
            ((IMemoryBufferByteAccess)reference).GetBuffer(out byte* dataInBytes, out uint capacity);
            float* dataInFloat = (float*)dataInBytes;
            int dataInFloatLength = (int)buffer.Length / sizeof(float);

            // read parameters once
            float currentWet = this.Mix;
            float currentDry = 1.0f - currentWet;
            float currentFeedback = this.Feedback;

            // Process audio data
            float sample, echoSample, outSample;
            for (int i = 0; i < dataInFloatLength; i++)
            {
                // read values
                sample = dataInFloat[i];
                echoSample = _echoBuffer.Dequeue();

                // compute output sample
                outSample = (currentDry * sample) + (currentWet * echoSample);
                dataInFloat[i] = outSample;

                // compute delay sample
                echoSample = sample + (currentFeedback * echoSample);
                _echoBuffer.Enqueue(echoSample);
            }
        }
    }

    public void Close(MediaEffectClosedReason reason)
    {
    }

    public void DiscardQueuedFrames()
    {
        // reset the delay buffer
        _echoBuffer.Clear();
        for (int i = 0; i < _delaySamplesCount; i++)
        {
            _echoBuffer.Enqueue(0.0f);
        }
    }
}

编辑: 我更改了音频效果以将输入样本与正弦波混合。 ProcessFrame 效果方法在样本播放之前和之后连续运行(当效果处于活动状态时)。所以在采样播放前后都应该听到正弦波。但是AudioGraph API似乎在没有活动播放时会忽略效果输出...

这是音频输出的屏幕截图:

所以我的问题是:内置的 EchoEffectDefinition 如何在播放完成后输出一些声音?访问 EchoEffectDefinition 源代码会很有帮助...

【问题讨论】:

  • 我使用了“AudioCreation”示例中的代码,场景 4 包含 AudioSubmixNode 并在其 AudioSubmixNode 上添加了您的自定义回声效果,当 AudioFileInputNode 播放完成时,回声可能会在几秒钟后逐渐停止。你的意思是当你将它与你的回声效果一起使用时,回声会立即停止?您可以尝试在自定义回声效果中将延迟值返回为 1000.0f,回声延迟会更加明显,您可以检查一下。
  • 感谢您回答@Faywang-MSFT。我在场景4中尝试了我的自定义音频效果,问题是一样的。尝试加载一个非常短的样本(例如鼓声)。如果样本长度是 1.5 秒,我的延迟是 0.5 秒,那么我只能听到 2 个回声。当样本播放完成时,我的自定义效果的输出声音似乎被残酷地切断了......它应该继续并逐渐减少(内置的 Echo 会这样做)。我还尝试使用 Audacity 记录扬声器输出,当我观察声音曲线时,很明显我的回声在样本播放结束时被切断。

标签: c# audio uwp signal-processing


【解决方案1】:

通过无限循环文件输入节点,它将始终提供输入帧,直到音频图停止。但是我们当然不想听到文件循环,所以我们可以监听 AudioFileInputNode 的 FileCompleted 事件。当文件播放完毕,它会触发事件,我们只需要将 AudioFileInputNode 的 OutgoingGain 设置为零。所以文件播放一次,但它会继续默默地循环传递没有可以添加回声的音频内容的输入帧。

仍以 AudioCreation 示例中的场景 4 为例。在场景 4 中,有一个名为 fileInputNode1 的属性。如上所述,请在 fileInputNode1 中添加以下代码,并使用您自定义的回显效果再次测试。

fileInputNode1.LoopCount = null; //Null makes it loop infinitely
fileInputNode1.FileCompleted += FileInputNode1_FileCompleted;          

private void FileInputNode1_FileCompleted(AudioFileInputNode sender, object args)
{
    fileInputNode1.OutgoingGain = 0.0;
}

【讨论】:

  • 谢谢,我没有使用循环的想法。这肯定会奏效!但在我的实际应用程序中,我想我会改用 AudioFrameInputNode,它会在 submix 节点中生成一些空帧。因为我已经将循环用于其他目的......我仍然认为这是一种黑客行为。我们应该在 IBasicAudioEffect 上有一个属性来控制这种行为......
猜你喜欢
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 2018-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多