阿里云语音转文字踩坑经验(前端)

244 阅读3分钟

需求背景

最近在做项目需要语音转文字功能,由于我们公司购买了阿里云的语音转文字服务,所以后端的实现较为简单,只需要调用阿里云的服务即可。阿里云语音转文字文档

技术实现

  1. 获取音频资源
  2. 将音频资源上传至 sso
  3. 获取 sso 链接传给语音转文字接口
    tip: 由于历史原因,本次只讨论wav类型的音频资源

实现历程

其实这个需求步骤非常简单,对于前端来说只要获取下音频资源,再调用下后端提供的两个接口就大功告成了。但是问题就出现在了的第一步:获取音频资源。

方案一:MediaRecorder(pass)

const startRecord = async () => {
    try {
      // 获取媒体权限
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: true
      })
      const _recorder = new MediaRecorder(stream, {
        mimeType: 'audio/wav'
      })
      recorderRef.current = _recorder;
      // 开始录音
      _recorder.start();

      const chunks = [];
      // 监听数据可用事件
      _recorder.ondataavailable = event => {
        chunks.push(event.data);
      };
      // 监听录音结束事件
      _recorder.onstop = async () => {
        setIsRecording(false)

        // 🔒 关闭录音功能:停止所有音轨
        if (stream) {
          stream.getTracks().forEach((track) => track.stop());
        }

        // 将录音片段合并为一个 Blob 对象
        const blob = new Blob(chunks);

        const file = new File(
          [blob],
          'recording.wav',
          { type: 'audio/wav' }
        );

        const {
          fileOssLink
        } = (await uploadAudio(file)) ?? {};

        if (fileOssLink) {
          const text = await handleVoiceToText({
            fileName: decodeURIComponent(fileOssLink)
          })
        }
      };
    } catch (error) {
      // 处理错误
      console.error('无法获取录音权限或不支持录音功能', error);
    }
  }

但其实const _recorder = new MediaRecorder(stream, { mimeType: 'audio/wav' }) 这行代码就会报错,我使用的浏览器是Chrome@138.0.7204.101,会如下错误

image.png 所以其实用上面这些代码是无法获取到wav格式的音频资源的。

方案二 :record.js(pass)

import Recorder from 'record.js'

const recorderRef = useRef<Recorder>();
  const [isRecording, setIsRecording] = useState(false);
  const startRecord = async () => {
    try {
      setIsRecording(true);
      recorderRef.current.start();
    } catch (error) {
      // 处理错误
      console.error('无法获取录音权限或不支持录音功能', error);
    }
  }

  const stopRecord = () => {
    recorderRef.current.stop();
    recorderRef.current.exportWAV(async (blob) => {
      const file = new File(
        [blob],
        'recording.wav',
        { type: 'audio/wav' }
      );
      
      const {
        fileOssLink
      } = (await uploadAudio(file)) ?? {};
  
      if (fileOssLink) {
        await handleVoiceToText({
          fileName: decodeURIComponent(fileOssLink)
        })
      }
    });
  }

  useEffect(() => {
    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
      // const source = audioContext.createMediaStreamSource(stream);
      recorderRef.current = new Recorder(audioContext, {
        sampleRate: 16000,
        numChannels: 1,
        mimeType: 'audio/wav'
      });

      // 获取媒体权限
      navigator.mediaDevices.getUserMedia({
        audio: true
      }).then((stream) => {
        recorderRef.current.init(stream);
      })
  }, [])

用库解决,但是不知道怎么回事,start、stop、exportWAV这三个方法都不存在,recorderRef.current打印出的值为:

image.png 遂放弃。

方案三:js-audio-recorder (终极方案)

import Recorder from 'js-audio-recorder'

const [isRecording, setIsRecording] = useState(false);
const recorderRef = useRef<Recorder>(null);

  // 停止录音时,需要手动关闭底层音频流
  const supplyStopRecord = () => {
    // 获取底层音频流
    const stream = (recorderRef.current as any).stream as MediaStream;
    // 关闭所有音轨
    if (stream && stream.getTracks) {
      stream.getTracks().forEach(track => {
        track.stop(); // 停止每个音轨
      });
    }
  }

  const handleRecord = async () => {
    const wavBlob = recorderRef.current.getWAVBlob()
    const file = new File([wavBlob], 'recording.wav', { type: 'audio/wav' })

    const {
      fileOssLink
    } = (await uploadAudio(file)) ?? {};


    if (fileOssLink) {
      const data = await handleVoiceToText({
        fileName: decodeURIComponent(fileOssLink)
      })
    }
  }

  const startRecord = async () => {
    setIsRecording(true)

    recorderRef.current.start().then(
      () => {
        // 开始录音
        console.log('开始录音了=========')
      },
      (error) => {
        // 出错了
        console.log('录音报错了====', error)
      }
    )
  }

  const stopRecording = async () => {
    setIsRecording(false)
    clearDownCount()

    await recorderRef.current.stop()

    supplyStopRecord()

    await handleRecord()
  }

  useEffect(() => {
    recorderRef.current = new Recorder({
      sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
      sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
      numChannels: 1
    })
  }, [])

感兴趣可以翻看源码,我大致翻了下,包里为了获取wav格式的音频资源,做了很多专业性的音频处理。

总结

专业的事情还是交给专业的人干,发现路不通及时换一条