AudioGraph выбрасывает XAUDIO2_E_INVALID_CALL на входной узел второго кадра

Я пытаюсь использовать AudioGraph API UWP для воспроизведения смеси синтезированной речи и звуков коротких уведомлений ("наушников").

UWP имеет API синтеза речи, который дает мне поток, содержащий WAV-файл, но я не хочу делать слишком много предположений о параметрах (битрейт, глубина выборки и т. Д.), Поэтому идея состоит в том, чтобы AudioSubmixNode и добавить AudioFrameInputNode s, когда есть какая-то речь для воспроизведения. Есть некоторая сложность в постановке в очередь отдельных высказываний, чтобы они не перекрывались.

График инициализируется как

    private async Task InitAudioGraph()
    {
        var graphCreated = await AudioGraph.CreateAsync(new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Speech)
        {
            QuantumSizeSelectionMode = QuantumSizeSelectionMode.LowestLatency
        });
        if (graphCreated.Status != AudioGraphCreationStatus.Success) return;

        _Graph = graphCreated.Graph;
        var outputCreated = await _Graph.CreateDeviceOutputNodeAsync();
        if (outputCreated.Status != AudioDeviceNodeCreationStatus.Success) return;

        _Mixer = _Graph.CreateSubmixNode();
        _Mixer.AddOutgoingConnection(outputCreated.DeviceOutputNode);

        _Graph.Start();
    }

а затем текущее высказывание играется с

class SpeechStreamPlayer : IDisposable
{
    internal static void Play(AudioGraph graph, AudioSubmixNode mixer, SpeechSynthesisStream speechStream)
    {
        if (!speechStream.ContentType.Equals("audio/wav", StringComparison.OrdinalIgnoreCase)) throw new NotSupportedException("Content type: " + speechStream.ContentType);

        var stream = speechStream.AsStreamForRead();

        // Read the RIFF header
        uint chunkId = stream.ReadUint(); // "RIFF" - but in little-endian
        if (chunkId != 0x46464952) throw new NotSupportedException("Magic: " + chunkId);
        uint chunkSize = stream.ReadUint(); // Length of rest of stream
        uint format = stream.ReadUint(); // "WAVE"
        if (format != 0x45564157) throw new NotSupportedException("Stream format: " + format);

        // "fmt " sub-chunk
        uint subchunkId = stream.ReadUint();
        if (subchunkId != 0x20746d66) throw new NotSupportedException("Expected fmt sub-chunk, found " + subchunkId);
        uint subchunkSize = stream.ReadUint();
        uint subchunk2Off = (uint)stream.Position + subchunkSize;
        uint audioFormat = (uint)stream.ReadShort();
        uint chans = (uint)stream.ReadShort();
        uint sampleRate = stream.ReadUint();
        uint byteRate = stream.ReadUint();
        uint blockSize = (uint)stream.ReadShort();
        uint bitsPerSample = (uint)stream.ReadShort();

        // Possibly extra stuff added, so...
        stream.Seek(subchunk2Off, SeekOrigin.Begin);

        subchunkId = stream.ReadUint(); // "data"
        if (subchunkId != 0x61746164) throw new NotSupportedException("Expected data sub-chunk, found " + subchunkId);
        subchunkSize = stream.ReadUint();

        // Ok, the stream is in the correct place to start extracting data and we have the parameters.
        var props = AudioEncodingProperties.CreatePcm(sampleRate, chans, bitsPerSample);

        var frameInputNode = graph.CreateFrameInputNode(props);
        frameInputNode.AddOutgoingConnection(mixer);

        new SpeechStreamPlayer(frameInputNode, mixer, stream, blockSize);
    }

    internal event EventHandler StreamFinished;

    private SpeechStreamPlayer(AudioFrameInputNode frameInputNode, AudioSubmixNode mixer, Stream stream, uint sampleSize)
    {
        _FrameInputNode = frameInputNode;
        _Mixer = mixer;
        _Stream = stream;
        _SampleSize = sampleSize;

        _FrameInputNode.QuantumStarted += Source_QuantumStarted;
        _FrameInputNode.Start();
    }

    private AudioFrameInputNode _FrameInputNode;
    private AudioSubmixNode _Mixer;
    private Stream _Stream;
    private readonly uint _SampleSize;

    private unsafe void Source_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQuantumStartedEventArgs args)
    {
        if (args.RequiredSamples <= 0) return;
        System.Diagnostics.Debug.WriteLine("Requested {0} samples", args.RequiredSamples);

        var frame = new AudioFrame((uint)args.RequiredSamples * _SampleSize);
        using (var buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
        {
            using (var reference = buffer.CreateReference())
            {
                byte* pBuffer;
                uint capacityBytes;

                var directBuffer = reference as IMemoryBufferByteAccess;
                ((IMemoryBufferByteAccess)reference).GetBuffer(out pBuffer, out capacityBytes);

                uint bytesRemaining = (uint)_Stream.Length - (uint)_Stream.Position;
                uint bytesToCopy = Math.Min(capacityBytes, bytesRemaining);

                for (uint i = 0; i < bytesToCopy; i++) pBuffer[i] = (byte)_Stream.ReadByte();
                for (uint i = bytesToCopy; i < capacityBytes; i++) pBuffer[i] = 0;

                if (bytesRemaining <= capacityBytes)
                {
                    Dispose();
                    StreamFinished?.Invoke(this, EventArgs.Empty);
                }
            }
        }

        sender.AddFrame(frame);
    }

    public void Dispose()
    {
        if (_FrameInputNode != null)
        {
            _FrameInputNode.QuantumStarted -= Source_QuantumStarted;
            _FrameInputNode.Dispose();
            _FrameInputNode = null;
        }

        if (_Stream != null)
        {
            _Stream.Dispose();
            _Stream = null;
        }
    }
}

Это работает один раз. Когда первое высказывание заканчивается, StreamFinished?.Invoke(this, EventArgs.Empty); уведомляет систему управления очередью, что должно быть воспроизведено следующее высказывание, и

    var frameInputNode = graph.CreateFrameInputNode(props);

бросает Exception с сообщением Exception from HRESULT: 0x88960001, Небольшое копание показывает, что оно соответствует XAUDIO2_E_INVALID_CALL, но это не очень наглядно.

В обоих случаях параметры передаются AudioEncodingProperties.CreatePcm являются (22050, 1, 16),

Как я мог узнать больше о том, что пошло не так? В худшем случае, я полагаю, я мог бы выбросить весь график и построить новый каждый раз, но это кажется довольно неэффективным.

1 ответ

Решение

Проблема, кажется, в

Когда первое высказывание заканчивается, StreamFinished?.Invoke(this, EventArgs.Empty); уведомляет систему управления очередью, что должно быть воспроизведено следующее высказывание

Хотя документация для AudioFrameInputNode.QuantumStarted ничего не говорит о запрещенных действиях, в документах для AudioGraph.QuantumStarted говорится

Событие QuantumStarted является синхронным, что означает, что вы не можете обновить свойства или состояние AudioGraph или отдельных аудио узлов в обработчике для этого события. Попытка выполнить операцию, такую ​​как остановка звукового графа или добавление, удаление или запуск отдельного звукового узла, приведет к возникновению исключения.

Похоже, что это относится также к узлу QuantumStarted событие.

Простое решение - перенести манипулирование графиком в другой поток с помощью

                        Task.Run(() => StreamFinished?.Invoke(this, EventArgs.Empty));
Другие вопросы по тегам