5050parser .add_argument ("--ssl" , type = int , default = 1 , help = "1 for ssl connect, 0 for no ssl" )
5151parser .add_argument ("--use_itn" , type = int , default = 1 , help = "1 for using itn, 0 for not itn" )
5252parser .add_argument ("--mode" , type = str , default = "2pass" , help = "offline, online, 2pass" )
53+ parser .add_argument (
54+ "--result_timeout" ,
55+ type = float ,
56+ default = 300.0 ,
57+ help = "seconds to wait for the server's end-of-input acknowledgement" ,
58+ )
5359
5460# ✅ 验收日志输出目录(每个 meeting 单独写,避免多进程抢文件)
5561parser .add_argument ("--log_dir" , type = str , default = "./asr_logs" , help = "验收日志输出目录" )
@@ -185,7 +191,7 @@ async def record_microphone():
185191 await asyncio .sleep (0.01 )
186192
187193
188- async def record_from_scp (chunk_begin , chunk_size ):
194+ async def record_from_scp (chunk_begin , chunk_size , completion_queue ):
189195 """从 wav/scp 文件读取音频分片发送,用于压测和延迟测试"""
190196 global voices , latency_first_audio_time , latency_last_audio_time
191197 if args .audio_in .endswith (".scp" ):
@@ -268,7 +274,6 @@ async def record_from_scp(chunk_begin, chunk_size):
268274 )
269275
270276 await websocket .send (message )
271- is_speaking = True
272277
273278 # 初始化该 wav 的统计状态
274279 latency_first_audio_time [wav_name ] = None
@@ -286,10 +291,6 @@ async def record_from_scp(chunk_begin, chunk_size):
286291
287292 await websocket .send (data )
288293
289- if i == chunk_num - 1 :
290- is_speaking = False
291- await websocket .send (json .dumps ({"is_speaking" : is_speaking }, ensure_ascii = False ))
292-
293294 # ✅ sleep策略:默认按实时节奏;若开启 send_without_sleep 则几乎不 sleep(压测)
294295 if args .send_without_sleep :
295296 sleep_duration = 0.001
@@ -301,20 +302,30 @@ async def record_from_scp(chunk_begin, chunk_size):
301302 )
302303 await asyncio .sleep (sleep_duration )
303304
304- if not args .mode == "offline" :
305- await asyncio .sleep (2 )
306-
307- if args .mode == "offline" :
308- global offline_msg_done
309- while not offline_msg_done :
310- await asyncio .sleep (1 )
311-
312- await asyncio .sleep (10 )
305+ await websocket .send (
306+ json .dumps ({"is_speaking" : False , "is_end" : True }, ensure_ascii = False )
307+ )
308+ try :
309+ acknowledgement = await asyncio .wait_for (
310+ completion_queue .get (), timeout = args .result_timeout
311+ )
312+ except asyncio .TimeoutError as e :
313+ await websocket .close ()
314+ raise TimeoutError (
315+ f"server did not acknowledge end of input for { wav_name !r} "
316+ f"within { args .result_timeout :g} seconds"
317+ ) from e
318+ if not acknowledgement .get ("is_final" , False ):
319+ await websocket .close ()
320+ error = acknowledgement .get ("error" ) or "server did not finalize input"
321+ raise RuntimeError (
322+ f"server failed to finalize { wav_name !r} : { error } "
323+ )
313324
314325 await websocket .close ()
315326
316327
317- async def message (id , writer : MeetingWriter ):
328+ async def message (id , writer : MeetingWriter , completion_queue ):
318329 """接收服务端识别结果 + 打印实时文本 + 打印延迟 + 写验收日志(events.jsonl)"""
319330 import websockets
320331 global websocket , voices , offline_msg_done
@@ -324,6 +335,13 @@ async def message(id, writer: MeetingWriter):
324335 text_print = ""
325336 text_print_2pass_online = ""
326337 text_print_2pass_offline = ""
338+ end_ack_received = False
339+
340+ def release_sender_with_error (error ):
341+ if completion_queue is not None and not end_ack_received :
342+ completion_queue .put_nowait (
343+ {"is_end" : True , "is_final" : False , "error" : error }
344+ )
327345
328346 if args .output_dir is not None :
329347 ibest_writer = open (
@@ -372,6 +390,10 @@ async def message(id, writer: MeetingWriter):
372390
373391 timestamp = meg .get ("timestamp" , "" )
374392 offline_msg_done = meg .get ("is_final" , False )
393+ if meg .get ("is_end" ):
394+ end_ack_received = True
395+ if completion_queue is not None :
396+ completion_queue .put_nowait (meg )
375397
376398 # ✅ 验收友好:每条消息落 events.jsonl(便于后处理)
377399 event = {
@@ -460,8 +482,10 @@ async def message(id, writer: MeetingWriter):
460482
461483 except websockets .exceptions .ConnectionClosedOK :
462484 print (f"[MEETING { id } ] connection closed normally" )
485+ release_sender_with_error ("connection closed before end-of-input acknowledgement" )
463486 except Exception as e :
464487 print (f"[MEETING { id } ] Exception:" , e )
488+ release_sender_with_error (f"receiver failed before end-of-input acknowledgement: { e } " )
465489 finally :
466490 try :
467491 if ibest_writer is not None :
@@ -497,12 +521,15 @@ async def ws_client(id, chunk_begin, chunk_size):
497521 ) as websocket :
498522 meeting_tag = f"{ id } _{ i } "
499523 writer = MeetingWriter (args .log_dir , meeting_id = meeting_tag , flush_every = args .log_flush_every )
524+ completion_queue = asyncio .Queue () if args .audio_in is not None else None
500525 try :
501526 if args .audio_in is not None :
502- task = asyncio .create_task (record_from_scp (i , 1 ))
527+ task = asyncio .create_task (record_from_scp (i , 1 , completion_queue ))
503528 else :
504529 task = asyncio .create_task (record_microphone ())
505- task3 = asyncio .create_task (message (str (id ) + "_" + str (i ), writer )) # processid+fileid
530+ task3 = asyncio .create_task (
531+ message (str (id ) + "_" + str (i ), writer , completion_queue )
532+ ) # processid+fileid
506533 await asyncio .gather (task , task3 )
507534 finally :
508535 writer .close ()
0 commit comments