【问题标题】:Google Speech API + Go - Transcribing Audio Stream of Unknown LengthGoogle Speech API + Go - 转录未知长度的音频流
【发布时间】:2018-02-06 16:31:59
【问题描述】:

我有一个视频通话的 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()
    }
}

transcribe 服务以每 3 秒 1 条的速率从队列中提取消息,有助于以与创建音频数据大致相同的速率处理音频数据。语音 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()
    }
})

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

我有几个问题:

1) 这种架构是否适合我的限制条件(未知的音频流长度等)?

2) 我可以做些什么来提高准确性并尽量减少丢失的数据?

(请注意,为了便于阅读,我已经简化了示例。如果有什么不合理的地方请指出,因为我在削减示例方面非常费力。)

【问题讨论】:

    标签: go ffmpeg google-cloud-platform google-speech-api


    【解决方案1】:

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

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

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

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

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

    • 使块尽可能大(接近 60 秒)
    • 使块在短时间内相互重叠(例如 5 秒)
    • 以编程方式检测重叠并删除它们

    【讨论】:

    • 所以标准输出缓冲区有一个互斥体,在读写时会锁定。很好的直觉,这会丢失数据,但是当我组装视频时,没有数据丢失。也就是说,视频是完整的,没有抖动或缺失部分。我有 2 项服务的原因是我预计将来需要更多的东西来使用转码后的数据。为每个用例转码似乎很疯狂。这样做似乎是明智的,并允许订阅者将结果用于自己的目的。不过感谢您的回答,很高兴有其他人关注它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    相关资源
    最近更新 更多