You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将音频流分片转为MP3/WAV/WebM?除首片外其余无法播放

问题

我有一个业务场景,需要将音频流的每个分片发送到服务器进行转录,但除第一个分片外,其他所有音频分片(Blob)均无法正常工作。为此我制作了一个React原型,通过生成Blob URL,使用audio元素测试所有分片是否能正常播放,代码示例如下:

import React, { useState, useRef } from 'react'

const RecordAudio: React.FC = () => {
  const [isRecording, setIsRecording] = useState(false)
  const mediaRecorderRef = useRef<MediaRecorder | null>(null)
  const audioChunksRef = useRef<Blob[]>([])

  const startRecording = async () => {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
    const mediaRecorder = new MediaRecorder(stream)
    mediaRecorderRef.current = mediaRecorder
    audioChunksRef.current = []
    mediaRecorder.ondataavailable = async (event) => {
      if (event.data.size > 0) {
        console.log('event.data', event.data)
        audioChunksRef.current.push(event.data)
      }
    }

    mediaRecorder.start(5000) // 注释标注500ms但实际设置为5000ms
    setIsRecording(true)
  }

  const stopRecording = () => {
    if (mediaRecorderRef.current) {
      mediaRecorderRef.current.stop()
      setIsRecording(false)
      // sendAudioChunk(audioChunksRef.current)
    }
  }

  const getAudioBlobUrl = (chunk: Blob) => {
    const audioBlob = new Blob([chunk], {
      type: 'audio/wav',
    })
    const blobUrl = URL.createObjectURL(audioBlob)

    return blobUrl
  }
  return (
    <div>
      <button onClick={isRecording ? stopRecording : startRecording}>
        {isRecording ? 'Stop Recording' : 'Start Recording'}
      </button>
      <div>
        {audioChunksRef.current.map((blob) => {
          return (
            <>
              <audio
                style={{ marginLeft: '16px' }}
                src={getAudioBlobUrl(blob)}
                controls
              />
            </>
          )
        })}
      </div>
    </div>
  )
}

export default RecordAudio

测试发现除第一个音频分片外,其余播放器均无法正常加载播放。

原因分析

  1. 分片格式不完整:MediaRecorder.start()生成的分片只有第一个包含完整的媒体文件头(存储音频编码、采样率等元数据),后续分片仅为纯音频数据块,缺失文件头导致audio元素无法解析。
  2. 类型强制不匹配:你在getAudioBlobUrl中强行将分片包装为audio/wav类型,但MediaRecorder默认输出格式为浏览器支持的WebM/OGG等,强制指定类型会引发格式兼容问题。
  3. React渲染不触发:audioChunksRef.current是useRef变量,更新后不会触发组件重新渲染,页面上的播放器无法同步显示新生成的分片。

解决方案

一、修复分片播放问题

  1. 使用原生支持的媒体格式
    优先选用浏览器支持的容器格式,避免强行转换类型:

    // 初始化MediaRecorder时指定兼容格式
    const supportedType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') 
      ? 'audio/webm;codecs=opus'
      : 'audio/ogg;codecs=opus';
    const mediaRecorder = new MediaRecorder(stream, { mimeType: supportedType })
    
    // 直接使用原始分片生成URL,无需强制转WAV
    const getAudioBlobUrl = (chunk: Blob) => URL.createObjectURL(chunk)
    
  2. 触发React重新渲染
    将音频分片存储从useRef改为useState,更新时自动触发页面渲染:

    const [audioChunks, setAudioChunks] = useState<Blob[]>([])
    
    // 修改ondataavailable回调
    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        setAudioChunks(prev => [...prev, event.data])
      }
    }
    
    // 渲染时使用state变量
    {audioChunks.map((blob, index) => (
      <audio
        key={index}
        style={{ marginLeft: '16px' }}
        src={getAudioBlobUrl(blob)}
        controls
      />
    ))}
    

二、实现分片流式传输到服务器

核心逻辑是在分片生成时立即发送,无需等待录制结束:

  1. 编写分片发送函数

    const sendAudioChunk = async (chunk: Blob, index: number) => {
      try {
        const formData = new FormData()
        formData.append('audioChunk', chunk, `chunk-${index}.webm`)
        await fetch('/api/transcribe', {
          method: 'POST',
          headers: {
            'X-Chunk-Index': String(index), // 传递分片序号,方便服务器排序
          },
          body: formData,
        })
        console.log(`分片${index}发送成功`)
      } catch (error) {
        console.error(`分片${index}发送失败`, error)
      }
    }
    
  2. 实时发送分片
    在ondataavailable回调中立即调用发送函数:

    mediaRecorder.ondataavailable = async (event) => {
      if (event.data.size > 0) {
        const currentIndex = audioChunks.length
        await sendAudioChunk(event.data, currentIndex)
        setAudioChunks(prev => [...prev, event.data])
      }
    }
    
  3. 服务器端注意事项

    • 按X-Chunk-Index序号拼接音频分片,保证音频连续性
    • 优先选择支持流式转录的格式(如WebM/Opus),减少格式转换开销
    • 处理分片丢失、重复等异常情况,保证转录完整性

内容的提问来源于stack exchange,提问作者Algorithm Unlock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:08:14