Merge pull request #2584 from xinnan-tech/py_test_websocket

fix: decoder 引发的内存泄漏
This commit is contained in:
欣南科技
2025-11-23 15:15:12 +08:00
committed by GitHub
8 changed files with 125 additions and 50 deletions
@@ -10,7 +10,7 @@ TTS上报功能已集成到ConnectionHandler类中。
""" """
import time import time
import gc
import opuslib_next import opuslib_next
from config.manage_api_client import report as manage_report from config.manage_api_client import report as manage_report
@@ -56,6 +56,8 @@ def opus_to_wav(conn, opus_data):
Returns: Returns:
bytes: WAV格式的音频数据 bytes: WAV格式的音频数据
""" """
decoder = None
try:
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道 decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
pcm_data = [] pcm_data = []
@@ -91,6 +93,13 @@ def opus_to_wav(conn, opus_data):
# 返回完整的WAV数据 # 返回完整的WAV数据
return bytes(wav_header) + pcm_data_bytes return bytes(wav_header) + pcm_data_bytes
finally:
if decoder is not None:
try:
del decoder
gc.collect()
except Exception as e:
conn.logger.bind(tag=TAG).debug(f"释放decoder资源时出错: {e}")
def enqueue_tts_report(conn, text, opus_data): def enqueue_tts_report(conn, text, opus_data):
@@ -5,6 +5,7 @@ import hmac
import base64 import base64
import hashlib import hashlib
import asyncio import asyncio
import gc
import requests import requests
import websockets import websockets
import opuslib_next import opuslib_next
@@ -347,4 +348,12 @@ class ASRProvider(ASRProviderBase):
async def close(self): async def close(self):
"""关闭资源""" """关闭资源"""
await self._cleanup() await self._cleanup(None)
if hasattr(self, 'decoder') and self.decoder is not None:
try:
del self.decoder
self.decoder = None
gc.collect()
logger.bind(tag=TAG).debug("Aliyun decoder resources released")
except Exception as e:
logger.bind(tag=TAG).debug(f"释放Aliyun decoder资源时出错: {e}")
@@ -10,6 +10,7 @@ import traceback
import threading import threading
import opuslib_next import opuslib_next
import concurrent.futures import concurrent.futures
import gc
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from config.logger import setup_logging from config.logger import setup_logging
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
@@ -241,6 +242,7 @@ class ASRProviderBase(ABC):
@staticmethod @staticmethod
def decode_opus(opus_data: List[bytes]) -> List[bytes]: def decode_opus(opus_data: List[bytes]) -> List[bytes]:
"""将Opus音频数据解码为PCM数据""" """将Opus音频数据解码为PCM数据"""
decoder = None
try: try:
decoder = opuslib_next.Decoder(16000, 1) decoder = opuslib_next.Decoder(16000, 1)
pcm_data = [] pcm_data = []
@@ -265,3 +267,10 @@ class ASRProviderBase(ABC):
except Exception as e: except Exception as e:
logger.bind(tag=TAG).error(f"音频解码过程发生错误: {e}") logger.bind(tag=TAG).error(f"音频解码过程发生错误: {e}")
return [] return []
finally:
if decoder is not None:
try:
del decoder
gc.collect()
except Exception as e:
logger.bind(tag=TAG).debug(f"释放decoder资源时出错: {e}")
@@ -4,6 +4,7 @@ import uuid
import asyncio import asyncio
import websockets import websockets
import opuslib_next import opuslib_next
import gc
from core.providers.asr.base import ASRProviderBase from core.providers.asr.base import ASRProviderBase
from config.logger import setup_logging from config.logger import setup_logging
from core.providers.asr.dto.dto import InterfaceType from core.providers.asr.dto.dto import InterfaceType
@@ -370,6 +371,17 @@ class ASRProvider(ASRProviderBase):
pass pass
self.forward_task = None self.forward_task = None
self.is_processing = False self.is_processing = False
# 显式释放decoder资源
if hasattr(self, 'decoder') and self.decoder is not None:
try:
del self.decoder
self.decoder = None
gc.collect()
logger.bind(tag=TAG).debug("Doubao decoder resources released")
except Exception as e:
logger.bind(tag=TAG).debug(f"释放Doubao decoder资源时出错: {e}")
# 清理所有连接的音频缓冲区 # 清理所有连接的音频缓冲区
if hasattr(self, '_connections'): if hasattr(self, '_connections'):
for conn in self._connections.values(): for conn in self._connections.values():
@@ -5,6 +5,7 @@ import hashlib
import asyncio import asyncio
import websockets import websockets
import opuslib_next import opuslib_next
import gc
from time import mktime from time import mktime
from datetime import datetime from datetime import datetime
from urllib.parse import urlencode from urllib.parse import urlencode
@@ -512,6 +513,17 @@ class ASRProvider(ASRProviderBase):
pass pass
self.forward_task = None self.forward_task = None
self.is_processing = False self.is_processing = False
# 显式释放decoder资源
if hasattr(self, 'decoder') and self.decoder is not None:
try:
del self.decoder
self.decoder = None
gc.collect()
logger.bind(tag=TAG).debug("Xunfei decoder resources released")
except Exception as e:
logger.bind(tag=TAG).debug(f"释放Xunfei decoder资源时出错: {e}")
# 清理所有连接的音频缓冲区 # 清理所有连接的音频缓冲区
if hasattr(self, "_connections"): if hasattr(self, "_connections"):
for conn in self._connections.values(): for conn in self._connections.values():
@@ -2,6 +2,7 @@ import time
import numpy as np import numpy as np
import torch import torch
import opuslib_next import opuslib_next
import gc
from config.logger import setup_logging from config.logger import setup_logging
from core.providers.vad.base import VADProviderBase from core.providers.vad.base import VADProviderBase
@@ -36,6 +37,14 @@ class VADProvider(VADProviderBase):
# 至少要多少帧才算有语音 # 至少要多少帧才算有语音
self.frame_window_threshold = 3 self.frame_window_threshold = 3
def __del__(self):
if hasattr(self, 'decoder') and self.decoder is not None:
try:
del self.decoder
gc.collect()
except Exception:
pass
def is_vad(self, conn, opus_packet): def is_vad(self, conn, opus_packet):
try: try:
pcm_frame = self.decoder.decode(opus_packet, 960) pcm_frame = self.decoder.decode(opus_packet, 960)
@@ -6,6 +6,7 @@ Opus编码工具类
import logging import logging
import traceback import traceback
import numpy as np import numpy as np
import gc
from opuslib_next import Encoder from opuslib_next import Encoder
from opuslib_next import constants from opuslib_next import constants
from typing import Optional, Callable, Any from typing import Optional, Callable, Any
@@ -128,5 +129,10 @@ class OpusEncoderUtils:
def close(self): def close(self):
"""关闭编码器并释放资源""" """关闭编码器并释放资源"""
# opuslib没有明确的关闭方法,Python的垃圾回收会处理 if hasattr(self, 'encoder') and self.encoder:
pass try:
del self.encoder
self.encoder = None
gc.collect()
except Exception as e:
logging.error(f"Error releasing Opus encoder: {e}")
+9
View File
@@ -8,6 +8,7 @@ import requests
import subprocess import subprocess
import numpy as np import numpy as np
import opuslib_next import opuslib_next
import gc
from io import BytesIO from io import BytesIO
from core.utils import p3 from core.utils import p3
from pydub import AudioSegment from pydub import AudioSegment
@@ -372,6 +373,7 @@ def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
将opus帧列表解码为wav字节流 将opus帧列表解码为wav字节流
""" """
decoder = opuslib_next.Decoder(sample_rate, channels) decoder = opuslib_next.Decoder(sample_rate, channels)
try:
pcm_datas = [] pcm_datas = []
frame_duration = 60 # ms frame_duration = 60 # ms
@@ -392,6 +394,13 @@ def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
wf.setframerate(sample_rate) wf.setframerate(sample_rate)
wf.writeframes(pcm_bytes) wf.writeframes(pcm_bytes)
return wav_buffer.getvalue() return wav_buffer.getvalue()
finally:
if decoder is not None:
try:
del decoder
gc.collect()
except Exception:
pass
def check_vad_update(before_config, new_config): def check_vad_update(before_config, new_config):