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: 0x88960001
와 Exception
발생 광고를 통지한다. 파기의 약간은 it corresponds to XAUDIO2_E_INVALID_CALL를 보여준다. 그러나 그것은 매우 기술적이 아니다.
두 경우 모두 AudioEncodingProperties.CreatePcm
에 전달 된 매개 변수는 (22050, 1, 16)
입니다.
어떻게 잘못되었는지 자세히 알 수 있습니까? 최악의 경우 전체 그래프를 버리고 매번 새로운 그래프를 작성할 수 있다고 생각하지만 이는 비효율적 인 것처럼 보입니다.
당신은'클래스 SpeechStreamPlayer'의 코드를 입력 할 수있는 다른 스레드에 그래프 조작을 이동하는 것입니다? –
@ NicoZhu-MSFT, 완료. 나는 또한 당신이 가짜 매개 변수를 제거하려고한다고 가정하기 때문에 헤더를 구문 분석 한 결과를 추가했습니다. –