Google Speech API + Go-转录长度未知的音频流

Jos*_*osh 5 ffmpeg go google-cloud-platform google-speech-api

我有一个视频通话的rtmp流,我想转录它。我在Go中创建了2个服务,但正在获得结果,但是它不是很准确,并且很多数据似乎丢失了。

让我解释。

我有一个transcode服务,我使用ffmpeg将视频转码为Linear16音频,然后将输出字节放置到PubSub队列中以供transcribe服务处理。显然,PubSub消息的大小是有限制的,我想在视频通话结束之前开始转录。因此,我将转码后的数据分成3个片段(长度不固定,看起来差不多),然后放入队列。

数据很简单地被转码:

var stdout Buffer

cmd := exec.Command("ffmpeg", "-i", url, "-f", "s16le", "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", "-")
cmd.Stdout = &stdout

if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

ticker := time.NewTicker(3 * time.Second)

for {
    select {
    case <-ticker.C:
        bytesConverted := stdout.Len()
        log.Infof("Converted %d bytes", bytesConverted)

        // Send the data we converted, even if there are no bytes.
        topic.Publish(ctx, &pubsub.Message{
            Data: stdout.Bytes(),
        })

        stdout.Reset()
    }
}
Run Code Online (Sandbox Code Playgroud)

transcribe服务每3秒以1的速率从队列中提取消息,从而以与所创建的速率几乎相同的速率处理音频数据。Speech API流有限制,不能超过60秒,因此我停止旧流并每30秒开始一个新流,因此无论视频通话持续多长时间,我们都不会达到限制。

这是我的转录方式:

stream := prepareNewStream()
clipLengthTicker := time.NewTicker(30 * time.Second)
chunkLengthTicker := time.NewTicker(3 * time.Second)

cctx, cancel := context.WithCancel(context.TODO())
err := subscription.Receive(cctx, func(ctx context.Context, msg *pubsub.Message) {

    select {
    case <-clipLengthTicker.C:
        log.Infof("Clip length reached.")
        log.Infof("Closing stream and starting over")

        err := stream.CloseSend()
        if err != nil {
            log.Fatalf("Could not close stream: %v", err)
        }

        go getResult(stream)
        stream = prepareNewStream()

    case <-chunkLengthTicker.C:
        log.Infof("Chunk length reached.")

        bytesConverted := len(msg.Data)

        log.Infof("Received %d bytes\n", bytesConverted)

        if bytesConverted > 0 {
            if err := stream.Send(&speechpb.StreamingRecognizeRequest{
                StreamingRequest: &speechpb.StreamingRecognizeRequest_AudioContent{
                    AudioContent: transcodedChunk.Data,
                },
            }); err != nil {
                resp, _ := stream.Recv()
                log.Errorf("Could not send audio: %v", resp.GetError())
            }
        }

        msg.Ack()
    }
})
Run Code Online (Sandbox Code Playgroud)

我认为问题在于我的3秒块不一定与短语或句子的开头和结尾对齐,因此我怀疑Speech API是一个循环神经网络,已经训练了完整的句子而不是单个单词。因此,从句子的中间开始剪辑会丢失一些数据,因为它无法找出短语自然结尾之前的前几个单词。另外,在从旧流更改为新流时,我会丢失一些数据。失去了一些环境。我猜重叠的片段可能对此有所帮助。

我有一些问题:

1)这种架构是否适合我的约束(音频流的长度未知等)?

2)我该怎么做才能提高准确性并减少丢失的数据?

(请注意,我已经简化了示例的可读性。请指出是否有任何不合理的地方,因为我在简化示例方面费了很多力。)

mbu*_*ann 1

我认为你是对的,将文本分成块会导致许多单词被砍掉。

我在出版中看到了另一个问题。在调用之间topic.Publishstdout.Reset()经过一段时间,ffmpeg 可能会将一些未发布的字节写入标准输出,这些字节将通过重置清除。

恐怕该架构不适合您的问题。消息大小的限制会导致许多问题。PubSub 系统的想法是发布者向订阅者通知事件,但不一定要保存大量有效负载。

您真的需要两项服务吗?您可以使用两个 go 例程通过通道进行通信。这将消除酒吧子系统。

一种策略是使块尽可能大。一个可能的解决方案:

  • 使块尽可能大(近 60 秒)
  • 使块彼此重叠一小段时间(例如5秒)
  • 以编程方式检测重叠并将其删除