From b8aaa51eb1a0f2ac46f8da8152fb4446724c3d2d Mon Sep 17 00:00:00 2001 From: yuzhiran Date: Thu, 25 Jun 2026 08:49:55 +0800 Subject: [PATCH] fix: voice input recording format + ASR logging - Change recorder format from mp3 (8kHz) to aac (22kHz) for better whisper ASR accuracy - Move recorder.onStop binding before recorder.stop() to prevent race condition (onStop may fire before handler is registered) - Backend: add Logger, log ASR errors instead of silent catch {} - Backend: change default extension from .mp3 to .aac --- backend/src/modules/tts/tts.controller.ts | 11 ++++++--- zhiyin-app/src/pages/interview/interview.vue | 26 ++++++++++---------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/backend/src/modules/tts/tts.controller.ts b/backend/src/modules/tts/tts.controller.ts index 64fbbe2..f0374e7 100644 --- a/backend/src/modules/tts/tts.controller.ts +++ b/backend/src/modules/tts/tts.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Body, Param, Res, HttpException, HttpStatus, UseGuards, UploadedFile, UseInterceptors } from '@nestjs/common' +import { Controller, Get, Post, Body, Param, Res, HttpException, HttpStatus, UseGuards, UploadedFile, UseInterceptors, Logger } from '@nestjs/common' import { FileInterceptor } from '@nestjs/platform-express' import { Response } from 'express' import * as fs from 'fs' @@ -10,6 +10,7 @@ import { Public } from '../../common/decorators/public.decorator' @Controller('tts') export class TtsController { + private readonly logger = new Logger(TtsController.name) constructor(private ttsService: TtsService) {} @UseGuards(JwtAuthGuard) @@ -42,7 +43,7 @@ export class TtsController { if (!file) throw new HttpException('请上传音频文件', HttpStatus.BAD_REQUEST) const uploadDir = '/tmp/asr_uploads' if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true }) - const ext = path.extname(file.originalname) || '.mp3' + const ext = file.originalname ? path.extname(file.originalname) || '.aac' : '.aac' const dest = path.join(uploadDir, file.filename + ext) fs.renameSync(file.path, dest) try { @@ -63,7 +64,11 @@ export class TtsController { if (whisperResult && whisperResult.trim()) { return { text: whisperResult.trim() } } - } catch {} + } catch (e: any) { + this.logger.error(`ASR failed: ${e?.message || e}`) + } + // 清理临时文件 + try { fs.unlinkSync(dest) } catch {} return { text: '' } } } diff --git a/zhiyin-app/src/pages/interview/interview.vue b/zhiyin-app/src/pages/interview/interview.vue index 24e0bcf..7a782c0 100644 --- a/zhiyin-app/src/pages/interview/interview.vue +++ b/zhiyin-app/src/pages/interview/interview.vue @@ -351,19 +351,7 @@ function startRecord() { isRecording.value = true recorder = uni.getRecorderManager() recorder.onStart(() => {}) - recorder.onError(() => { isRecording.value = false }) - recorder.start({ format: 'mp3' }) - uni.vibrateShort({ type: 'medium' }) - // #endif - // #ifndef MP-WEIXIN - uni.showToast({ title: '语音输入仅支持小程序', icon: 'none' }) - // #endif -} - -function stopRecord() { - if (!recorder || !isRecording.value) return - isRecording.value = false - recorder.stop() + recorder.onError(() => { isRecording.value = false; uni.showToast({ title: '录音失败', icon: 'none' }) }) recorder.onStop(async (res) => { if (!res.tempFilePath) return const audioPath = res.tempFilePath @@ -387,6 +375,18 @@ function stopRecord() { } uni.showToast({ title: '语音识别失败,请手动输入', icon: 'none' }) }) + recorder.start({ format: 'aac', sampleRate: 22050, numberOfChannels: 1, encodeBitRate: 16000 }) + uni.vibrateShort({ type: 'medium' }) + // #endif + // #ifndef MP-WEIXIN + uni.showToast({ title: '语音输入仅支持小程序', icon: 'none' }) + // #endif +} + +function stopRecord() { + if (!recorder || !isRecording.value) return + isRecording.value = false + recorder.stop() }