2017-02-08 8 views
0

Я пытаюсь использовать AudioGraph API UWP для воспроизведения сочетания синтезированной речи и коротких звуков уведомлений («earcons»).AudioGraph выбрасывает XAUDIO2_E_INVALID_CALL на входе второго кадра

UWP имеет API синтеза речи, который дает мне поток, содержащий WAV-файл, но я не хочу делать слишком много предположений о параметрах (скорость передачи, глубину выборки и т. Д.), Поэтому идея состоит в том, чтобы иметь a 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. Немного копания показывает, что it corresponds to XAUDIO2_E_INVALID_CALL, но это не очень описательно.

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

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

+0

Вы могли бы заполнить код класса 'SpeechStreamPlayer'? –

+0

@ NicoZhu-MSFT, сделано. Я также добавил результаты разбора заголовка, так как я предполагаю, что вы пытаетесь устранить фиктивные параметры в качестве причины. –

ответ

0

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

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

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

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

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

Простое решение заключается в перемещении манипуляции графа на другой поток с

     Task.Run(() => StreamFinished?.Invoke(this, EventArgs.Empty));