diff --git a/dsLightRag/KeDaXunFei/__pycache__/XunFeiAudioEvaluator.cpython-310.pyc b/dsLightRag/KeDaXunFei/__pycache__/XunFeiAudioEvaluator.cpython-310.pyc index eda476bd..5cc0845c 100644 Binary files a/dsLightRag/KeDaXunFei/__pycache__/XunFeiAudioEvaluator.cpython-310.pyc and b/dsLightRag/KeDaXunFei/__pycache__/XunFeiAudioEvaluator.cpython-310.pyc differ diff --git a/dsLightRag/Routes/XunFeiRoute.py b/dsLightRag/Routes/XunFeiRoute.py index 43c6421d..48a075ad 100644 --- a/dsLightRag/Routes/XunFeiRoute.py +++ b/dsLightRag/Routes/XunFeiRoute.py @@ -7,6 +7,9 @@ from fastapi import APIRouter, HTTPException, BackgroundTasks, Query, UploadFile from pydantic import BaseModel from typing import Optional import tempfile + +from starlette.websockets import WebSocket + from Util.ObsUtil import ObsUploader from Config.Config import OBS_BUCKET, OBS_SERVER, XF_APPID, XF_APISECRET, XF_APIKEY from fastapi.responses import StreamingResponse @@ -72,7 +75,15 @@ async def evaluate_audio( # 添加async关键字 # 创建临时文件保存上传的音频 # 修改临时文件处理逻辑 - with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_audio: + import os + import uuid + # 创建持久化保存目录 + output_dir = os.path.join(os.path.dirname(__file__), '../static/audio') + os.makedirs(output_dir, exist_ok=True) + # 生成唯一文件名 + temp_audio_path = os.path.join(output_dir, f"eval_audio_{uuid.uuid4()}.wav") + + with open(temp_audio_path, 'wb') as temp_audio: # 先读取音频内容 audio_content = await audio_file.read() # 再进行格式转换 @@ -82,8 +93,6 @@ async def evaluate_audio( # 添加async关键字 wf.setsampwidth(2) wf.setframerate(16000) wf.writeframes(audio_content) - # 移除冗余的temp_audio.write(audio_content),避免重复写入 - temp_audio_path = temp_audio.name # 创建评测器实例 evaluator = XunFeiAudioEvaluator( @@ -122,8 +131,8 @@ async def evaluate_audio( # 添加async关键字 executor, evaluator.run_evaluation ) - # 清理临时文件 - os.unlink(temp_audio_path) + # 清理临时文件 - 注释掉此行以便保留文件 + # os.unlink(temp_audio_path) # 生成评测ID evaluation_id = str(uuid.uuid4()) @@ -160,3 +169,85 @@ async def get_evaluation_result(evaluation_id: str): "message": "请实现结果存储逻辑" } + +@router.websocket("/xunfei/streaming-evaluate") +async def streaming_evaluate(websocket: WebSocket): + await websocket.accept() + logger.info("讯飞语音评测WebSocket连接已建立") + + # 生成唯一音频文件名 + output_dir = os.path.join(os.path.dirname(__file__), '../static/audio') + os.makedirs(output_dir, exist_ok=True) + temp_audio_path = os.path.join(output_dir, f"eval_audio_{uuid.uuid4()}.wav") + + try: + # 初始化WAV文件 + with wave.open(temp_audio_path, 'wb') as wf: + wf.setnchannels(1) + wf.setsampwidth(2) + wf.setframerate(16000) + + # 持续接收音频流 + while True: + data = await websocket.receive_bytes() + if not data: # 结束标记 + break + wf.writeframes(data) + + # 执行评测(复用现有逻辑) + evaluator = XunFeiAudioEvaluator( + appid=XUNFEI_CONFIG["appid"], + api_key=XUNFEI_CONFIG["api_key"], + api_secret=XUNFEI_CONFIG["api_secret"], + audio_file=temp_audio_path, + language=await websocket.receive_json().get("language", "chinese") + ) + + # 根据语言设置不同的评测参数 + if language == "chinese": + # 中文评测配置 + evaluator.business_params = { + "category": "read_chapter", + "ent": "cn_vip", + "group": group, + "check_type": check_type, + "grade": grade, + } + else: + # 英文评测配置 + evaluator.business_params = { + "category": "read_chapter", + "ent": "en_vip", + "group": group, # 添加缺失参数 + "check_type": check_type, # 添加缺失参数 + "grade": grade, # 添加缺失参数 + } + + # 运行评测 + from concurrent.futures import ThreadPoolExecutor + executor = ThreadPoolExecutor(max_workers=4) + results, eval_time = await asyncio.get_event_loop().run_in_executor( + executor, evaluator.run_evaluation + ) + + # 清理临时文件 - 注释掉此行以便保留文件 + # os.unlink(temp_audio_path) + + # 生成评测ID + evaluation_id = str(uuid.uuid4()) + + return AudioEvaluationResponse( + evaluation_id=evaluation_id, + status="success", + results=results, + evaluation_time=eval_time.total_seconds() if eval_time else None + ) + + except Exception as e: + logger.error(f"评测失败: {str(e)}") + await websocket.send_json({ + "status": "error", + "message": str(e) + }) + await websocket.close() + diff --git a/dsLightRag/Routes/__pycache__/XunFeiRoute.cpython-310.pyc b/dsLightRag/Routes/__pycache__/XunFeiRoute.cpython-310.pyc index a60943b2..d278a822 100644 Binary files a/dsLightRag/Routes/__pycache__/XunFeiRoute.cpython-310.pyc and b/dsLightRag/Routes/__pycache__/XunFeiRoute.cpython-310.pyc differ diff --git a/dsLightRag/static/XunFei/audio_evaluation.html b/dsLightRag/static/XunFei/audio_evaluation.html index 5959033f..1837b184 100644 --- a/dsLightRag/static/XunFei/audio_evaluation.html +++ b/dsLightRag/static/XunFei/audio_evaluation.html @@ -3,34 +3,823 @@ - 语音评测系统 - + 在线录音机 +
-

语音评测系统

- -
- - + + +
-
- - +
+

录音设置

+
+ +
+ + 50% +
+
-
准备就绪
+
+ +
- - + - + \ No newline at end of file diff --git a/dsLightRag/static/XunFei/js/audio_evaluation.js b/dsLightRag/static/XunFei/js/audio_evaluation.js deleted file mode 100644 index 71a877b9..00000000 --- a/dsLightRag/static/XunFei/js/audio_evaluation.js +++ /dev/null @@ -1,205 +0,0 @@ -let mediaRecorder; -let audioChunks = []; -let audioBlob; - -// 获取DOM元素 -const languageSelect = document.getElementById('language'); -const textInput = document.getElementById('text'); -const recordBtn = document.getElementById('recordBtn'); -const stopBtn = document.getElementById('stopBtn'); -const statusDiv = document.getElementById('status'); -const resultDiv = document.getElementById('result'); -const resultContent = document.getElementById('resultContent'); - - -// 开始录音 -recordBtn.addEventListener('click', async () => { - try { - statusDiv.textContent = '正在获取麦克风权限...'; - statusDiv.className = 'status'; - - // 获取麦克风权限并处理可能的错误 - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) - .catch(err => { - if (err.name === 'NotAllowedError') { - throw new Error('用户拒绝了麦克风访问权限。请在浏览器设置中允许麦克风访问,或刷新页面重试。'); - } else if (err.name === 'NotFoundError') { - throw new Error('未检测到麦克风设备,请确保您的设备已正确连接麦克风。'); - } else if (err.name === 'NotReadableError') { - throw new Error('麦克风设备正在被其他程序占用,请关闭其他可能使用麦克风的程序后重试。'); - } else { - throw err; - } - }); - - // 检测浏览器支持的录制格式 - const getSupportedMimeType = () => { - const options = [ - 'audio/webm; codecs=opus', - 'audio/webm', - 'audio/mp4', - '' // 空字符串表示使用浏览器默认格式 - ]; - for (const option of options) { - if (MediaRecorder.isTypeSupported(option)) return option; - } - return ''; - }; - - // 使用检测到的格式初始化 - mediaRecorder = new MediaRecorder(stream, { - mimeType: getSupportedMimeType(), - audioBitsPerSecond: 16000 - }); - - // 同时修正Blob类型(确保前后一致) - mediaRecorder.onstop = () => { - audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); // 与录制格式匹配 - statusDiv.textContent = '录音完成,正在自动提交评测...'; - submitEvaluation(); - // 停止所有音轨以释放麦克风 - stream.getTracks().forEach(track => track.stop()); - }; - - // 设置60秒自动停止录音 - const maxRecordingTime = 60000; // 60秒 - const timeoutId = setTimeout(() => { - if (mediaRecorder && mediaRecorder.state === 'recording') { - alert('已达到最大录音时长(60秒)'); - mediaRecorder.stop(); - recordBtn.disabled = false; - stopBtn.disabled = true; - } - }, maxRecordingTime); - - mediaRecorder.start(); - statusDiv.textContent = '正在录音...(最多60秒)'; - recordBtn.disabled = true; - stopBtn.disabled = false; - - } catch (error) { - console.error('录音初始化失败:', error); - alert('录音失败: ' + error.message); - statusDiv.textContent = '录音失败: ' + error.message; - statusDiv.className = 'status error'; - recordBtn.disabled = false; - stopBtn.disabled = true; - } -}); - -// 停止录音 -stopBtn.addEventListener('click', () => { - if (mediaRecorder && mediaRecorder.state === 'recording') { - mediaRecorder.stop(); - recordBtn.disabled = false; - stopBtn.disabled = true; - } -}); - -// 提交评测 -async function submitEvaluation() { - if (!audioBlob) { - statusDiv.textContent = '请先完成录音'; - statusDiv.className = 'status error'; - return; - } - - // 移除文本验证 - // if (!textInput.value.trim()) { - // statusDiv.textContent = '请输入评测文本内容'; - // statusDiv.className = 'status error'; - // return; - // } - - try { - statusDiv.textContent = '正在提交评测...'; - statusDiv.className = 'status'; - - const formData = new FormData(); - formData.append('audio_file', audioBlob, 'recording.webm'); - formData.append('language', languageSelect.value); - // 移除文本参数 - // formData.append('text', textInput.value.trim()); - formData.append('group', 'adult'); - formData.append('check_type', 'common'); - formData.append('grade', 'middle'); - - const response = await fetch('/api/xunFei/evaluate-audio', { - method: 'POST', - body: formData - }); - - if (!response.ok) { - throw new Error(`服务器错误: HTTP状态码 ${response.status}`); - } - - const result = await response.json(); - - if (result.status === 'success') { - displayResults(result.results); - statusDiv.textContent = '评测成功完成'; - statusDiv.className = 'status success'; - } else { - throw new Error(result.error_message || '评测失败,服务器返回未知错误'); - } - - } catch (error) { - console.error('评测提交失败:', error); - statusDiv.textContent = '评测失败: ' + error.message; - statusDiv.className = 'status error'; - } -} - -// 显示评测结果 -function displayResults(results) { - resultDiv.style.display = 'block'; - - if (!results) { - resultContent.innerHTML = '

暂无评测结果

'; - return; - } - - let html = '
'; - - // 显示总分 - if (results.total_score !== undefined) { - html += `

总分: ${results.total_score.toFixed(4)} / 5.0

`; - } - - // 显示各项评分 - if (results.accuracy_score !== undefined) { - html += `

准确度: ${results.accuracy_score.toFixed(4)}

`; - } - // 添加结果对象有效性检查 - if (!results) { - showError("评测结果格式错误"); - return; - } - - if (results.fluency_score !== undefined) { - html += `

流利度: ${results.fluency_score.toFixed(4)}

`; - } else { - html += `

流利度: 未获取

`; // 添加默认值 - } - - if (results.completeness_score !== undefined) { - html += `

完整度: ${results.completeness_score.toFixed(4)}

`; - } else { - html += `

完整度: 未获取

`; // 添加默认值 - } - - html += '
'; - - // 显示单词级评分 - if (results.words && results.words.length > 0) { - html += '

单词评分:

    '; - results.words.forEach(word => { - // 为单词评分添加空值检查 - const score = word.score !== undefined ? word.score.toFixed(4) : '无'; - html += `
  • ${word.content}: ${score}
  • `; - }); - html += '
'; - } - - resultContent.innerHTML = html; -} \ No newline at end of file diff --git a/dsLightRag/static/audio/eval_audio_0b3f382e-865a-487c-ac18-e8d6ecaca28d.wav b/dsLightRag/static/audio/eval_audio_0b3f382e-865a-487c-ac18-e8d6ecaca28d.wav new file mode 100644 index 00000000..d26b87bd Binary files /dev/null and b/dsLightRag/static/audio/eval_audio_0b3f382e-865a-487c-ac18-e8d6ecaca28d.wav differ diff --git a/dsLightRag/static/audio/eval_audio_777ae5af-d41f-4cf2-ab72-91204622626f.wav b/dsLightRag/static/audio/eval_audio_777ae5af-d41f-4cf2-ab72-91204622626f.wav new file mode 100644 index 00000000..d26b87bd Binary files /dev/null and b/dsLightRag/static/audio/eval_audio_777ae5af-d41f-4cf2-ab72-91204622626f.wav differ