AI 音频产品开发模板及流程(二)

 AI 音频产品开发模板及流程(一)

6. 同声传译

  • 实时翻译,发言与翻译几乎同步,极大提升沟通效率。
  • 支持多语言互译,适用于国际会议、商务洽谈等多场景。
  • 自动断句、转写和翻译,减少人工干预,提升准确性。
  • 翻译结果可由设备自动播放,交流体验自然流畅。

功能展示

代码片段

  // 录音相关配置,并开始录音
  const startRcordFn = async () => {
    // 设备需要在线
    if (!isOnline) return;
    // 开启了翻译,但没有选择翻译语言时,给出提示
    if (needTranslate && !translationLanguage) {
      showToast({
        icon: 'none',
        title: Strings.getLang('realtime_recording_translation_no_select_tip'),
      });
      return;
    }
    try {
      setControlBtnLoading(true);
      const config: any = {
        // 录音类型,0:呼叫、1:会议
        recordType: currRecordType,
        // DP 控制超时时间,单位秒
        controlTimeout: 5,
        // 灌流超时时间,单位秒
        dataTimeout: 10,
        // 0:文件转写、1:实时转写
        transferType: 1,
        // 是否需要翻译
        needTranslate,
        // 输入语言
        originalLanguage: originLanguage,
        // 智能体 ID,后面具体根据提供的 SDK 获取 agentId
        agentId: '',
        // 录音通道,0:BLE、1:Bt、2:micro
        recordChannel,
        // TTS 流编码方式,通过编码后将流写入到耳机设备,0:opus_silk、1:opus_celt
        ttsEncode: isOpusCelt ? 1 : 0,
      };
      if (needTranslate) {
        // 目标语言
        config.targetLanguage = translationLanguage;
      }
      await tttStartRecord({
        deviceId,
        config,
      });
      setInterval(1000);
      lastTimeRef.current = Date.now();
      setControlBtnLoading(false);
    } catch (error) {
      setControlBtnLoading(false);
    }
  };
  // 点击开始录音对应的回调
  const handleStartRecord = useCallback(async () => {
    // 申请录音权限
    if (isBtEntryVersion) {
      ty.authorize({
        scope: 'scope.record',
        success: () => {
          startRcordFn();
        },
        fail: e => {
          ty.showToast({ title: Strings.getLang('no_record_permisson'), icon: 'error' });
          console.log('cope.record: ', e);
        },
      });
      return;
    }
    startRcordFn();
  }, [
    deviceId,
    isOnline,
    controlBtnLoading,
    currRecordType,
    needTranslate,
    originLanguage,
    translationLanguage,
    recordChannel,
    isBtEntryVersion,
    offlineUsage,
  ]);
  // 暂停
  const handlePauseRecord = async () => {
    if (controlBtnLoading) return;
    try {
      setControlBtnLoading(true);
      const d = await tttPauseRecord(deviceId);
      setInterval(undefined);
      setControlBtnLoading(false);
    } catch (error) {
      console.log('fail', error);
      setControlBtnLoading(false);
    }
  };
  // 继续录音
  const handleResumeRecord = async () => {
    if (controlBtnLoading) return;
    try {
      setControlBtnLoading(true);
      await tttResumeRecord(deviceId);
      setInterval(1000);
      lastTimeRef.current = Date.now();
      setControlBtnLoading(false);
    } catch (error) {
      setControlBtnLoading(false);
    }
  };
  // 停止
  const handleStopRecord = async () => {
    if (controlBtnLoading) return;
    try {
      ty.showLoading({ title: '' });
      await tttStopRecord(deviceId);
      setDuration(0);
      setInterval(undefined);
      ty.hideLoading({
        complete: () => {
          backToHome(fromType);
        },
      });
    } catch (error) {
      ty.hideLoading();
    }
  };
  // 监听 ASR 和翻译返回
  onRecordTransferRealTimeRecognizeStatusUpdateEvent(handleRecrodChange);
  // 处理 ASR 和翻译
  const handleRecrodChange = d => {
    try {
      const {
        // 阶段,0:任务、4:ASR、5:翻译、6:skill、7:TTS
        phase,
        // 阶段状态,0:未开启、1:进行中、2:结束、3:取消
        status,
        requestId,
        // 转写的文本
        text,
        // 错误码
        errorCode,
      } = d;
      // ASR 阶段,接收并实时更新对应 requestId 文本
      if (phase === 4) {
        const currTextItemIdx = currTextListRef.current.findIndex(item => item.id === requestId);
        if (currTextItemIdx > -1) {
          const newList = currTextListRef.current.map(item =>
            item.id === requestId ? { ...item, text } : item
          );
          currTextListRef.current = newList;
          setTextList(newList);
        } else {
          if (!text) return;
          const newList = [
            ...currTextListRef.current,
            {
              id: requestId,
              text,
            },
          ];
          currTextListRef.current = newList;
          setTextList(newList);
        }
        // 翻译返回阶段,接收并展示 status=2 即已完成翻译的
      } else if (phase === 5 && status === 2) {
        let resText = '';
        if (text && text !== 'null') {
          if (isJsonString(text)) {
            const textArr = JSON.parse(text);
            const isArr = Array.isArray(textArr);
            // 数字的 string 类型如 111,isJsonString 判断为 json 字符串,会导致 .join 失败
            resText = isArr ? textArr?.join('\n') : textArr;
          } else {
            resText = text;
          }
        }

        if (!resText) {
          return;
        }

        const newList = currTextListRef.current.map(item => {
          return item.id === requestId ? { ...item, text: `${item.text}\n${resText}` } : item;
        });
        currTextListRef.current = newList;
        setTextList(newList);
      }
    } catch (error) {
      console.warn(error);
    }
  };

7. 现场录音

  • 可自动录制现场环境中的所有声音,完整还原现场交流内容,便于后续查证和回顾。
  • 支持录音内容的转写和 AI 总结,快速提炼关键信息,提高信息处理效率。
  • 降低人工记录成本,避免遗漏重要细节,提升工作和沟通的准确性。
  • 适用于会议记录、课堂笔记、采访等多种场景,增强产品的实用性和智能化水平。

功能展示

代码片段

  // 录音相关配置,并调用 App 能力开始录音
  const startRecordFn = async () => {
    try {
      setControlBtnLoading(true);
      await tttStartRecord(
        {
          deviceId,
          config: {
            // 出错时是否要保留音频文件
            saveDataWhenError: true,
            // 录音类型,0:呼叫、1:会议
            recordType: currRecordType,
            // DP 控制超时时间,单位秒
            controlTimeout: 5,
            // 灌流超时时间 单位秒
            dataTimeout: 10,
            // 0:文件转写、1:实时转写
            transferType: 0,
            // 录音通道,0:BLE、1:Bt、2:micro
            recordChannel,
            // TTS 流编码方式,通过编码后将流写入到耳机设备,0:opus_silk、1:opus_celt
            ttsEncode: isOpusCelt ? 1 : 0,
          },
        },
      );
      setInterval(1000);
      lastTimeRef.current = Date.now();
      setControlBtnLoading(false);
    } catch (error) {
      setControlBtnLoading(false);
    }
  };  
  // 点击开始录音的回调
  const handleStartRecord = useCallback(async () => {
    // 申请权限
    if (isBtEntryVersion) {
      ty.authorize({
        scope: 'scope.record',
        success: () => {
          startRecordFn();
        },
        fail: e => {
          ty.showToast({ title: Strings.getLang('no_record_permisson'), icon: 'error' });
        },
      });
      return;
    }
    startRecordFn();
  }, [currRecordType, recordChannel, isBtEntryVersion]);
// 暂停
  const handlePauseRecord = async () => {
    try {
      setControlBtnLoading(true);
      await tttPauseRecord(deviceId);
      setInterval(undefined);
      setControlBtnLoading(false);
    } catch (error) {
      console.log('fail', error);
      setControlBtnLoading(false);
    }
  };
  // 继续录音
  const handleResumeRecord = async () => {
    try {
      setControlBtnLoading(true);
      await tttResumeRecord(deviceId);
      setInterval(1000);
      lastTimeRef.current = Date.now();
      setControlBtnLoading(false);
    } catch (error) {
      setControlBtnLoading(false);
    }
  };
  // 停止
  const handleStopRecord = async () => {
    try {
      ty.showLoading({ title: '' });
      await tttStopRecord(deviceId);
      setDuration(0);
      setInterval(undefined);
      ty.hideLoading();
      backToHome();
    } catch (error) {
      ty.hideLoading();
    }
  };

8. 转录和 AI 总结

  • 将音频内容转写为可编辑的文字,便于保存和后续处理。
  • 利用 AI 技术根据选择的模板对转写内容进行智能总结,快速提炼关键信息,提升信息获取效率。
  • 降低人工整理和阅读成本,避免遗漏重要内容。
  • 支持生成结构化的 Markdown 格式文本,方便文档归档和分享。
  • 适用于会议纪要、通话记录、面对面交流、课堂笔记等多种场景,增强产品的实用性和智能化水平。

功能展示

代码片段

{/* 转录结果 */}
<View className={styles.content}>
  <Tabs.SegmentedPicker
    activeKey={currTab}
    tabActiveTextStyle={{
      color: 'rgba(54, 120, 227, 1)',
      fontWeight: '600',
    }}
    style={{ backgroundColor: 'rgba(241, 241, 241, 1)' }}
    onChange={activeKey => {
      setCurrTab(activeKey);
    }}
  >
    <Tabs.TabPanel tab={Strings.getLang('recording_detail_tab_stt')} tabKey="stt" />
    <Tabs.TabPanel tab={Strings.getLang('recording_detail_tab_summary')} tabKey="summary" />
    <Tabs.TabPanel
      tab={Strings.getLang('recording_detail_tab_mind_map')}
      tabKey="mindMap"
    />
  </Tabs.SegmentedPicker>
  {currTab === 'stt' && (
    <SttContent
      playerStatus={playerStatus}
      wavFilePath={recordFile?.wavFilePath}
      transferStatus={transferStatus}
      sttData={sttData}
      recordType={recordFile?.recordType}
      currPlayTime={currPlayTime}
      onChangePlayerStatus={status => {
        setPlayerStatus(status);
      }}
      innerAudioContextRef={innerAudioContextRef}
      isEditMode={isEditMode}
      onUpdateSttData={handleUpdateSttData}
    />
  )}
  {currTab === 'summary' && (
    <SummaryContent summary={summary} transferStatus={transferStatus} />
  )}
  {currTab === 'mindMap' && (
    <MindMapContent summary={summary} transferStatus={transferStatus} />
  )}
  {(transferStatus === TRANSFER_STATUS.Initial ||
    transferStatus === TRANSFER_STATUS.Failed) &&
    !(currTab === 'stt' && recordFile?.transferType === TransferType.REALTIME) && (
      <>
        <EmptyContent type={EMPTY_TYPE.NO_TRANSCRIPTION} />
        <Button
          className={styles.generateButton}
          onClick={() => {
            // 先选择模版
            setShowTemplatePopup(true);
          }}
        >
          <Text className={styles.generateText}>{Strings.getLang('generate')}</Text>
        </Button>
      </>
    )}
</View>
  // 开始转录总结
  const handleStartTransfer = async (selectTemplate: TRANSFER_TEMPLATE) => {
    if (isLoading.current) return;
    try {
      isLoading.current = true;
      ty.showLoading({ title: '' });
      await tttTransfer({
        recordTransferId: currRecordTransferId.current,
        template: selectTemplate,
        language: recordFile?.originalLanguage || language,
      });
      setTransferStatus(TRANSFER_STATUS.Processing);
      const fileDetail: any = await tttGetFilesDetail({
        recordTransferId: currRecordTransferId.current,
        amplitudeMaxCount: 100,
      });
      setRecordFile(fileDetail);
      dispatch(updateRecordTransferResultList());
      ty.hideLoading();
      isLoading.current = false;
    } catch (error) {
      console.log(error);
      dispatch(updateRecordTransferResultList());
      ty.hideLoading();
      isLoading.current = false;
    }
  };
// 获取转录和转写详情
const getFileDetail = async () => {
  // loading
  const finishLoading = () => {
    ty.hideLoading();
    isLoading.current = false;
  };
  try {
    isLoading.current = true;
    ty.showLoading({ title: '' });
    const recordTransferId = currRecordTransferId.current;
    // 获取录音详情
    const fileDetail = await tttGetFilesDetail({
      recordTransferId,
      amplitudeMaxCount: 100,
    });
    if (fileDetail) {
      setRecordFile(fileDetail);
      const { storageKey, transfer, visit, status, recordId, transferType } = fileDetail;
      if (!visit) {
        updateFileVisitStatus();
      }
      setTransferStatus(transfer);
      // 实时转写直接取用 App 接口的转写数据
      if (transferType === TransferType.REALTIME) {
        // 获取转写数据
        const realTimeResult: any = await tttGetRecordTransferRealTimeResult({
          recordId,
        });
        const { list } = realTimeResult;
        const newData = list
          .filter(item => !!item?.asr && item?.asr !== 'null')
          .map(item => ({
            asrId: item.asrId,
            startSecond: Math.floor(item.beginOffset / 1000),
            endSecond: Math.floor(item.endOffset / 1000),
            text: item.asr,
            transText: item.translate,
            channel: item.channel,
          }));
        setSttData(newData);
        originSttData.current = newData;
        // 获取客户端本地总结数据
        tttGetRecordTransferSummaryResult({
          recordTransferId,
          from: 0,
        }).then((d: any) => {
          if (d?.text) {
            resolveSummaryText(d?.text);
          }
        });
        // 获取云端总结数据
        tttGetRecordTransferSummaryResult({
          recordTransferId,
          from: 1, // 云端
        }).then((d: any) => {
          if (d?.text) {
            tttSaveRecordTransferSummaryResult({ recordTransferId, text: d?.text });
            resolveSummaryText(d?.text);
          }
        });
      } else {
        // status 文件同步状态,0:未上传、1:上传中、2:已上传、3:上传失败
        // transfer 转录状态,0:未转录、1:转录中、2:已转录、3:转录失败
        if (status === 2 && transfer === 2) {
          // 获取客户端本地转写数据
          tttGetRecordTransferRecognizeResult({
            recordTransferId,
            from: 0, // 本地
          }).then((d: any) => {
            if (d?.text) {
              resolveSttText(d?.text);
            }
          });
          // 获取云端转写数据
          tttGetRecordTransferRecognizeResult({
            recordTransferId,
            from: 1, // 云端
          }).then((d: any) => {
            if (d?.text) {
              // 缓存到客户端本地
              tttSaveRecordTransferRecognizeResult({ recordTransferId, text: d?.text });
              resolveSttText(d?.text);
            }
          });
          // 获取客户端本地总结数据
          tttGetRecordTransferSummaryResult({
            recordTransferId,
            from: 0,
          }).then((d: any) => {
            if (d?.text) {
              resolveSummaryText(d?.text);
            }
          });
          // 获取云端总结数据
          tttGetRecordTransferSummaryResult({
            recordTransferId,
            from: 1, // 云端
          }).then((d: any) => {
            if (d?.text) {
              tttSaveRecordTransferSummaryResult({ recordTransferId, text: d?.text });
              resolveSummaryText(d?.text);
            }
          });
        }
      }
      finishLoading();
    }
  } catch (error) {
    console.log('error', error);
    finishLoading();
  }
};

9. 结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT砖家涂拉拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值