我正在使用Azure语音SDK做tts并生成OGG数据,我想将流式输出发送到WebRTC音轨中,我编写了这个函数,可以将数据发送到对等端,但我无法在客户端听到音频。
这段代码有什么问题,我怎么才能让它工作?
func sendAudioData(data io.Reader, audioTrack *webrtc.TrackLocalStaticSample) error {
ogg, _, oggErr := oggreader.NewWith(data)
if oggErr != nil {
return oggErr
}
var lastGranule uint64
for {
pageData, pageHeader, oggErr := ogg.ParseNextPage()
if errors.Is(oggErr, io.EOF) {
fmt.Printf("All audio pages parsed and sent")
return nil
}
if oggErr != nil {
return oggErr
}
slog.Info("ParseNextPage", "header", pageHeader)
// The amount of samples is the difference between the last and current timestamp
sampleCount := float64(pageHeader.GranulePosition - lastGranule)
lastGranule = pageHeader.GranulePosition
sampleDuration := time.Duration((sampleCount/48000)*1000) * time.Millisecond
slog.Info("Write Sample", "duration", sampleDuration, "length", len(pageData))
if oggErr = audioTrack.WriteSample(media.Sample{Data: pageData, Duration: sampleDuration}); oggErr != nil {
return oggErr
}
}
}
这是生成流数据的代码。
package speech
import (
"errors"
"fmt"
"os"
"time"
"github.com/Microsoft/cognitive-services-speech-sdk-go/audio"
"github.com/Microsoft/cognitive-services-speech-sdk-go/common"
"github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
)
func azureTTSStream(text, lang string) (*speech.AudioDataStream, error) {
// stream, err := audio.CreatePullAudioOutputStream()
// if err != nil {
// return nil, err
// }
// audioConfig, err := audio.NewAudioConfigFromStreamOutput(stream)
// if err != nil {
// panic(err)
// }
speechKey := os.Getenv("AZURE_SPEECH_KEY")
speechRegion := os.Getenv("AZURE_SPEECH_REGION")
speechConfig, err := speech.NewSpeechConfigFromSubscription(speechKey, speechRegion)
if err != nil {
panic(err)
}
speechConfig.SetSpeechSynthesisOutputFormat(common.Ogg48Khz16BitMonoOpus)
synthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, nil)
if err != nil {
panic(err)
}
voice := azureVoices[lang]
input := fmt.Sprintf(azureSAMLInput, lang, voice, text)
task := synthesizer.SpeakSsmlAsync(input)
var outcome speech.SpeechSynthesisOutcome
select {
case outcome = <-task:
case <-time.After(60 * time.Second):
fmt.Println("timeout")
return nil, errors.New("timeout")
}
defer outcome.Close()
if outcome.Error != nil {
fmt.Println("outcome error: ", outcome.Error)
}
return speech.NewAudioDataStreamFromSpeechSynthesisResult(outcome.Result)
// if outcome.Result.Reason == common.SynthesizingAudioCompleted {
// fmt.Println("outcome completed")
// }
// cancellation, _ := speech.NewCancellationDetailsFromSpeechSynthesisResult(outcome.Result)
// msg := fmt.Sprintf("CANCELED: Reason=%d.\n", cancellation.Reason)
// if cancellation.Reason == common.Error {
// fmt.Sprintf("%s ErrorCode=%d, ErrorDetails=[%s]\n", msg, cancellation.ErrorCode, cancellation.ErrorDetails)
// }
}
2条答案
按热度按时间zwghvu4y1#
我认为主要问题可能与最大OPUS帧尺寸有关。我做了很多实验,发现Azure文本到语音API生成的OGG文件的页面大小通常约为。520ms的音频数据。
代码使用
ogg.ParseNextPage()
获取整个页面数据,然后使用audioTrack.WriteSample
将其作为样本发送:根据RFC 7578和RFC 6716,数据包的最大长度为120 ms:
4.2. Opus编码器可以输出表示2.5、5、10、20、40或60ms语音或音频数据的编码帧。此外,可以将任意数量的帧组合成分组,直到表示120ms的语音或音频数据的最大分组持续时间。
2.1.4.帧持续时间Opus可以编码2.5、5、10、20、40或60 ms的帧。它还可以将多个帧组合成长达120 ms的数据包。
3.2.5.代码3:数据包M中的信号帧数不得为零,数据包中包含的音频持续时间不得超过120毫秒
[R5]代码3数据包包含至少一个帧,但不超过120毫秒的音频总量。
RFC 6716也有关于帧大小的建议:
对于实时应用,每秒发送较少的数据包可以降低比特率,因为它减少了IP、UDP和RTP报头的开销。然而,它增加了延迟和对分组丢失的敏感性,因为丢失一个分组构成丢失更大的音频块。增加帧持续时间也略微提高了编码效率,但是对于20ms以上的帧大小,增益变小。因此,对于大多数应用,20 ms帧是一个不错的选择。
最简单的解决方法是将帧大小转换为20ms。我使用了ffmpeg(不幸的是,我没有在纯Go中找到任何用于此目的的包):
我觉得你的代码也有问题。建议使用time.ticker来迭代音频文件页面。举例来说:
另外,请确保您的浏览器未阻止自动播放您的网站。
我也尝试过将页面分割成块,但这需要深入的Opus file format和RTP protocol知识,目前我甚至不确定这是否可行。pion/webrtc不支持
另外值得一提的是,你可以尝试使用oggwriter来转换帧大小,而不是ffmpeg,但是我还没有找到如何改变页面持续时间。看起来它只处理段的大小和段的数量(每个段should be 255 bytes length(除了页面中的最后一个和最大值)。段255的数量也)
另外,我可以推荐使用Chrome进行调试。它将为您提供有关WebRTC连接的大量信息。打开这个URL:
chrome://webrtc-internals/
如果想了解更多关于WebRTC的信息,有一些额外的信息可以帮助您进一步研究:
hgqdbh6s2#
你不应该使用webrtc.TrackLocalStaticRTP方法来处理语音吗?我注意到在您的代码中使用了webrtc.TrackLocalStaticSample,但它通常用于静态音频,而不是语音。
RTP(实时传输协议)是在实时应用中使用的网络协议,这将是对于这种情况的更合适的解决方案。
下面是一个使用SFU和WebSocket进行实时传输的例子:https://github.com/pion/example-webrtc-applications/blob/master/sfu-ws/main.go
这里是另一个使用示例,演示了如何处理具有多个联播rtp流的输入轨道,并将它们发送回https://github.com/pion/webrtc/tree/1390b16097d38597308a23a4473e8eb20b9f8efd/examples/simulcast
在您的代码中,您可以将webrtc.TrackLocalStaticSample替换为webrtc.TrackLocalStaticRTP,并包含以下代码段来处理格式:
有关WebRTC架构的更多示例,请查看:https://medium.com/secure-meeting/webrtc-architecture-basics-p2p-sfu-mcu-and-hybrid-approaches-e2aea14c80f9
我希望我在这件事上能帮上点忙。