Как пользовательский звуковой эффект может издавать звук после завершения воспроизведения AudioFileInputNode - PullRequest
0 голосов
/ 29 января 2020

Я разрабатываю аудио приложение в C# и UWP, используя AudioGraph API. Мои настройки AudioGraph следующие: AudioFileInputNode -> AudioSubmixNode -> AudioDeviceOutputNode.

Я добавил собственный эффект эха на AudioSubmixNode. Если я играю AudioFileInputNode, я слышу эхо. Но когда воспроизведение AudioFileInputNode заканчивается, эхо-звук жестоко прекращается. Я хотел бы, чтобы это прекратилось постепенно только через несколько секунд. Если я использую EchoEffectDefinition из AudioGraph API, звук эха не прекращается после завершения воспроизведения семпла.

Я не знаю, возникает ли проблема из-за моей реализации эффекта или это странное поведение AudioGraph API ... Поведение идентично в образце "AudioCreation" в SDK, сценарий 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, кажется, игнорирует вывод эффекта, когда нет активного воспроизведения ...

Вот снимок экрана аудио выхода: enter image description here

Итак, мой вопрос: как встроенный EchoEffectDefinition может выводить звук после завершения воспроизведения? Доступ к исходному коду EchoEffectDefinition очень помог бы ...

1 Ответ

1 голос
/ 11 февраля 2020

Бесконечно зацикливая узел ввода файла, он всегда будет обеспечивать входной кадр до тех пор, пока звуковой график не остановится. Но, конечно, мы не хотим слышать файл l oop, поэтому мы можем прослушивать событие FileCompleted AudioFileInputNode. Когда файл завершит воспроизведение, он вызовет событие, и нам просто нужно установить OutgoingGain для AudioFileInputNode на ноль. Таким образом, файл воспроизводится один раз, но он продолжает молча l oop пропускать входные кадры, в которых нет аудиосодержимого, к которому можно добавить эхо.

Все еще используя сценарий 4 в примере AudioCreation в качестве примера. В сценарии 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;
}
...