2017-02-08 7 views
0

UWP의 AudioGraph API를 사용하여 합성 된 음성 및 짧은 알림 사운드 ("earcons")를 재생하려고합니다.AudioGraph가 두 번째 프레임 입력 노드에서 XAUDIO2_E_INVALID_CALL을 throw합니다.

UWP에는 WAV 파일이 포함 된 스트림을 제공하는 음성 합성 API가 있지만 매개 변수 (비트 전송률, 샘플 깊이 등)에 대해 너무 많은 가정을하고 싶지는 않습니다. AudioSubmixNode이고 재생할 음성이있을 때마다 AudioFrameInputNode을 추가하십시오. 서로 겹치지 않도록 별도의 발언을 대기열에 두는 것은 다소 복잡합니다.

그래프는

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 from HRESULT: 0x88960001Exception 발생 광고를 통지한다. 파기의 약간은 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에 대한 설명서가 없지만 다음 발언이

재생되어야 큐 관리 시스템을 통지

첫 발언이 완료

것 같다 금지 된 행위에 대해 아무 말도하지 말자. 에 대한 문서

The QuantumSta rted 이벤트는 동기식입니다. 즉,이 이벤트의 처리기에서 AudioGraph 또는 개별 오디오 노드의 속성이나 상태를 업데이트 할 수 없습니다. 오디오 그래프를 중지하거나 개별 오디오 노드를 추가, 제거 또는 시작하는 등의 작업을 수행하면 예외가 발생합니다.

이는 노드의 QuantumStarted 이벤트에도 적용되는 것으로 보입니다.

간단한 해결책은

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