mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-21 22:53:56 +08:00
update:内存泄漏
This commit is contained in:
@@ -10,7 +10,7 @@ TTS上报功能已集成到ConnectionHandler类中。
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import gc
|
||||
import opuslib_next
|
||||
|
||||
from config.manage_api_client import report as manage_report
|
||||
@@ -56,41 +56,47 @@ def opus_to_wav(conn, opus_data):
|
||||
Returns:
|
||||
bytes: WAV格式的音频数据
|
||||
"""
|
||||
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
||||
pcm_data = []
|
||||
decoder = None
|
||||
try:
|
||||
decoder = opuslib_next.Decoder(16000, 1) # 16kHz, 单声道
|
||||
pcm_data = []
|
||||
|
||||
for opus_packet in opus_data:
|
||||
try:
|
||||
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
||||
pcm_data.append(pcm_frame)
|
||||
except opuslib_next.OpusError as e:
|
||||
conn.logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
||||
for opus_packet in opus_data:
|
||||
try:
|
||||
pcm_frame = decoder.decode(opus_packet, 960) # 960 samples = 60ms
|
||||
pcm_data.append(pcm_frame)
|
||||
except opuslib_next.OpusError as e:
|
||||
conn.logger.bind(tag=TAG).error(f"Opus解码错误: {e}", exc_info=True)
|
||||
|
||||
if not pcm_data:
|
||||
raise ValueError("没有有效的PCM数据")
|
||||
if not pcm_data:
|
||||
raise ValueError("没有有效的PCM数据")
|
||||
|
||||
# 创建WAV文件头
|
||||
pcm_data_bytes = b"".join(pcm_data)
|
||||
num_samples = len(pcm_data_bytes) // 2 # 16-bit samples
|
||||
# 创建WAV文件头
|
||||
pcm_data_bytes = b"".join(pcm_data)
|
||||
num_samples = len(pcm_data_bytes) // 2 # 16-bit samples
|
||||
|
||||
# WAV文件头
|
||||
wav_header = bytearray()
|
||||
wav_header.extend(b"RIFF") # ChunkID
|
||||
wav_header.extend((36 + len(pcm_data_bytes)).to_bytes(4, "little")) # ChunkSize
|
||||
wav_header.extend(b"WAVE") # Format
|
||||
wav_header.extend(b"fmt ") # Subchunk1ID
|
||||
wav_header.extend((16).to_bytes(4, "little")) # Subchunk1Size
|
||||
wav_header.extend((1).to_bytes(2, "little")) # AudioFormat (PCM)
|
||||
wav_header.extend((1).to_bytes(2, "little")) # NumChannels
|
||||
wav_header.extend((16000).to_bytes(4, "little")) # SampleRate
|
||||
wav_header.extend((32000).to_bytes(4, "little")) # ByteRate
|
||||
wav_header.extend((2).to_bytes(2, "little")) # BlockAlign
|
||||
wav_header.extend((16).to_bytes(2, "little")) # BitsPerSample
|
||||
wav_header.extend(b"data") # Subchunk2ID
|
||||
wav_header.extend(len(pcm_data_bytes).to_bytes(4, "little")) # Subchunk2Size
|
||||
# WAV文件头
|
||||
wav_header = bytearray()
|
||||
wav_header.extend(b"RIFF") # ChunkID
|
||||
wav_header.extend((36 + len(pcm_data_bytes)).to_bytes(4, "little")) # ChunkSize
|
||||
wav_header.extend(b"WAVE") # Format
|
||||
wav_header.extend(b"fmt ") # Subchunk1ID
|
||||
wav_header.extend((16).to_bytes(4, "little")) # Subchunk1Size
|
||||
wav_header.extend((1).to_bytes(2, "little")) # AudioFormat (PCM)
|
||||
wav_header.extend((1).to_bytes(2, "little")) # NumChannels
|
||||
wav_header.extend((16000).to_bytes(4, "little")) # SampleRate
|
||||
wav_header.extend((32000).to_bytes(4, "little")) # ByteRate
|
||||
wav_header.extend((2).to_bytes(2, "little")) # BlockAlign
|
||||
wav_header.extend((16).to_bytes(2, "little")) # BitsPerSample
|
||||
wav_header.extend(b"data") # Subchunk2ID
|
||||
wav_header.extend(len(pcm_data_bytes).to_bytes(4, "little")) # Subchunk2Size
|
||||
|
||||
# 返回完整的WAV数据
|
||||
return bytes(wav_header) + pcm_data_bytes
|
||||
# 返回完整的WAV数据
|
||||
return bytes(wav_header) + pcm_data_bytes
|
||||
finally:
|
||||
if decoder:
|
||||
del decoder
|
||||
gc.collect()
|
||||
|
||||
|
||||
def enqueue_tts_report(conn, text, opus_data):
|
||||
|
||||
@@ -5,6 +5,7 @@ import hmac
|
||||
import base64
|
||||
import hashlib
|
||||
import asyncio
|
||||
import gc
|
||||
import requests
|
||||
import websockets
|
||||
import opuslib_next
|
||||
@@ -347,4 +348,7 @@ class ASRProvider(ASRProviderBase):
|
||||
|
||||
async def close(self):
|
||||
"""关闭资源"""
|
||||
await self._cleanup()
|
||||
await self._cleanup(None)
|
||||
if hasattr(self, 'decoder'):
|
||||
del self.decoder
|
||||
gc.collect()
|
||||
|
||||
@@ -10,6 +10,7 @@ import traceback
|
||||
import threading
|
||||
import opuslib_next
|
||||
import concurrent.futures
|
||||
import gc
|
||||
from abc import ABC, abstractmethod
|
||||
from config.logger import setup_logging
|
||||
from typing import Optional, Tuple, List
|
||||
@@ -241,6 +242,7 @@ class ASRProviderBase(ABC):
|
||||
@staticmethod
|
||||
def decode_opus(opus_data: List[bytes]) -> List[bytes]:
|
||||
"""将Opus音频数据解码为PCM数据"""
|
||||
decoder = None
|
||||
try:
|
||||
decoder = opuslib_next.Decoder(16000, 1)
|
||||
pcm_data = []
|
||||
@@ -265,3 +267,7 @@ class ASRProviderBase(ABC):
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"音频解码过程发生错误: {e}")
|
||||
return []
|
||||
finally:
|
||||
if decoder:
|
||||
del decoder
|
||||
gc.collect()
|
||||
|
||||
@@ -4,6 +4,7 @@ import uuid
|
||||
import asyncio
|
||||
import websockets
|
||||
import opuslib_next
|
||||
import gc
|
||||
from core.providers.asr.base import ASRProviderBase
|
||||
from config.logger import setup_logging
|
||||
from core.providers.asr.dto.dto import InterfaceType
|
||||
@@ -370,6 +371,17 @@ class ASRProvider(ASRProviderBase):
|
||||
pass
|
||||
self.forward_task = None
|
||||
self.is_processing = False
|
||||
|
||||
# 显式释放decoder资源
|
||||
if hasattr(self, 'decoder') and self.decoder:
|
||||
try:
|
||||
del self.decoder
|
||||
self.decoder = None
|
||||
gc.collect()
|
||||
logger.bind(tag=TAG).info("Doubao decoder resources released")
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Error releasing Doubao decoder: {e}")
|
||||
|
||||
# 清理所有连接的音频缓冲区
|
||||
if hasattr(self, '_connections'):
|
||||
for conn in self._connections.values():
|
||||
|
||||
@@ -5,6 +5,7 @@ import hashlib
|
||||
import asyncio
|
||||
import websockets
|
||||
import opuslib_next
|
||||
import gc
|
||||
from time import mktime
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlencode
|
||||
@@ -512,6 +513,17 @@ class ASRProvider(ASRProviderBase):
|
||||
pass
|
||||
self.forward_task = None
|
||||
self.is_processing = False
|
||||
|
||||
# 显式释放decoder资源
|
||||
if hasattr(self, 'decoder') and self.decoder:
|
||||
try:
|
||||
del self.decoder
|
||||
self.decoder = None
|
||||
gc.collect()
|
||||
logger.bind(tag=TAG).info("Xunfei decoder resources released")
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Error releasing Xunfei decoder: {e}")
|
||||
|
||||
# 清理所有连接的音频缓冲区
|
||||
if hasattr(self, "_connections"):
|
||||
for conn in self._connections.values():
|
||||
|
||||
@@ -2,6 +2,7 @@ import time
|
||||
import numpy as np
|
||||
import torch
|
||||
import opuslib_next
|
||||
import gc
|
||||
from config.logger import setup_logging
|
||||
from core.providers.vad.base import VADProviderBase
|
||||
|
||||
@@ -36,6 +37,11 @@ class VADProvider(VADProviderBase):
|
||||
# 至少要多少帧才算有语音
|
||||
self.frame_window_threshold = 3
|
||||
|
||||
def __del__(self):
|
||||
if hasattr(self, 'decoder'):
|
||||
del self.decoder
|
||||
gc.collect()
|
||||
|
||||
def is_vad(self, conn, opus_packet):
|
||||
try:
|
||||
pcm_frame = self.decoder.decode(opus_packet, 960)
|
||||
|
||||
@@ -6,6 +6,7 @@ Opus编码工具类
|
||||
import logging
|
||||
import traceback
|
||||
import numpy as np
|
||||
import gc
|
||||
from opuslib_next import Encoder
|
||||
from opuslib_next import constants
|
||||
from typing import Optional, Callable, Any
|
||||
@@ -128,5 +129,10 @@ class OpusEncoderUtils:
|
||||
|
||||
def close(self):
|
||||
"""关闭编码器并释放资源"""
|
||||
# opuslib没有明确的关闭方法,Python的垃圾回收会处理
|
||||
pass
|
||||
if hasattr(self, 'encoder') and self.encoder:
|
||||
try:
|
||||
del self.encoder
|
||||
self.encoder = None
|
||||
gc.collect()
|
||||
except Exception as e:
|
||||
logging.error(f"Error releasing Opus encoder: {e}")
|
||||
@@ -8,6 +8,7 @@ import requests
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import opuslib_next
|
||||
import gc
|
||||
from io import BytesIO
|
||||
from core.utils import p3
|
||||
from pydub import AudioSegment
|
||||
@@ -372,26 +373,30 @@ def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
|
||||
将opus帧列表解码为wav字节流
|
||||
"""
|
||||
decoder = opuslib_next.Decoder(sample_rate, channels)
|
||||
pcm_datas = []
|
||||
try:
|
||||
pcm_datas = []
|
||||
|
||||
frame_duration = 60 # ms
|
||||
frame_size = int(sample_rate * frame_duration / 1000) # 960
|
||||
frame_duration = 60 # ms
|
||||
frame_size = int(sample_rate * frame_duration / 1000) # 960
|
||||
|
||||
for opus_frame in opus_datas:
|
||||
# 解码为PCM(返回bytes,2字节/采样点)
|
||||
pcm = decoder.decode(opus_frame, frame_size)
|
||||
pcm_datas.append(pcm)
|
||||
for opus_frame in opus_datas:
|
||||
# 解码为PCM(返回bytes,2字节/采样点)
|
||||
pcm = decoder.decode(opus_frame, frame_size)
|
||||
pcm_datas.append(pcm)
|
||||
|
||||
pcm_bytes = b"".join(pcm_datas)
|
||||
pcm_bytes = b"".join(pcm_datas)
|
||||
|
||||
# 写入wav字节流
|
||||
wav_buffer = BytesIO()
|
||||
with wave.open(wav_buffer, "wb") as wf:
|
||||
wf.setnchannels(channels)
|
||||
wf.setsampwidth(2) # 16bit
|
||||
wf.setframerate(sample_rate)
|
||||
wf.writeframes(pcm_bytes)
|
||||
return wav_buffer.getvalue()
|
||||
# 写入wav字节流
|
||||
wav_buffer = BytesIO()
|
||||
with wave.open(wav_buffer, "wb") as wf:
|
||||
wf.setnchannels(channels)
|
||||
wf.setsampwidth(2) # 16bit
|
||||
wf.setframerate(sample_rate)
|
||||
wf.writeframes(pcm_bytes)
|
||||
return wav_buffer.getvalue()
|
||||
finally:
|
||||
del decoder
|
||||
gc.collect()
|
||||
|
||||
|
||||
def check_vad_update(before_config, new_config):
|
||||
|
||||
Reference in New Issue
Block a user