datbkpro commited on
Commit
cfffabc
·
verified ·
1 Parent(s): 00dbcb7

Update services/streaming_voice_service.py

Browse files
Files changed (1) hide show
  1. services/streaming_voice_service.py +0 -454
services/streaming_voice_service.py CHANGED
@@ -14,460 +14,6 @@ from core.tts_service import EnhancedTTSService
14
  from core.speechbrain_vad import SpeechBrainVAD
15
  from core.silero_vad import SileroVAD
16
 
17
-
18
- # class StreamingVoiceService:
19
- # def __init__(self, groq_client: Groq, rag_system: EnhancedRAGSystem, tts_service: EnhancedTTSService):
20
- # self.client = groq_client
21
- # self.rag_system = rag_system
22
- # self.tts_service = tts_service
23
-
24
- # # Khởi tạo VAD
25
- # self.vad_processor = SileroVAD()
26
- # self.is_listening = False
27
- # self.speech_callback = None
28
- # self.is_processing = False # Tránh xử lý chồng chéo
29
- # self.last_speech_time = 0
30
- # self.silence_timeout = 2.0 # 2 giây im lặng thì dừng
31
-
32
- # # Conversation context
33
- # self.conversation_history = []
34
- # self.current_transcription = ""
35
-
36
- # # Audio buffer for VAD
37
- # self.audio_buffer = []
38
- # self.buffer_lock = threading.Lock()
39
-
40
- # def start_listening(self, speech_callback: Callable) -> bool:
41
- # """Bắt đầu lắng nghe với VAD"""
42
- # if self.is_listening:
43
- # return False
44
-
45
- # self.speech_callback = speech_callback
46
- # self.last_speech_time = time.time()
47
- # success = self.vad_processor.start_stream(self._on_speech_detected)
48
- # if success:
49
- # self.is_listening = True
50
- # self.is_processing = False
51
- # print("🎙️ Đã bắt đầu lắng nghe với VAD")
52
- # return success
53
-
54
- # def stop_listening(self):
55
- # """Dừng lắng nghe"""
56
- # self.vad_processor.stop_stream()
57
- # self.is_listening = False
58
- # self.is_processing = False
59
- # self.speech_callback = None
60
- # with self.buffer_lock:
61
- # self.audio_buffer = []
62
- # print("🛑 Đã dừng lắng nghe")
63
-
64
- # def process_audio_chunk(self, audio_data: tuple) -> Dict[str, Any]:
65
- # """Xử lý audio chunk với VAD (dùng cho real-time streaming)"""
66
- # if not audio_data or not self.is_listening or self.is_processing:
67
- # return {
68
- # 'transcription': "Đang lắng nghe...",
69
- # 'response': "",
70
- # 'tts_audio': None,
71
- # 'status': 'listening'
72
- # }
73
-
74
- # try:
75
- # sample_rate, audio_array = audio_data
76
-
77
- # # Thêm vào buffer và xử lý với VAD
78
- # with self.buffer_lock:
79
- # self.audio_buffer.extend(audio_array)
80
- # # Giới hạn buffer để tránh tràn bộ nhớ
81
- # max_buffer_samples = sample_rate * 10 # 10 giây
82
- # if len(self.audio_buffer) > max_buffer_samples:
83
- # self.audio_buffer = self.audio_buffer[-max_buffer_samples:]
84
-
85
- # # Xử lý với VAD
86
- # self.vad_processor.process_stream(audio_array, sample_rate)
87
-
88
- # # Kiểm tra timeout im lặng
89
- # current_time = time.time()
90
- # if current_time - self.last_speech_time > self.silence_timeout and len(self.audio_buffer) > 0:
91
- # self._process_final_audio()
92
-
93
- # return {
94
- # 'transcription': "Đang lắng nghe...",
95
- # 'response': "",
96
- # 'tts_audio': None,
97
- # 'status': 'listening'
98
- # }
99
-
100
- # except Exception as e:
101
- # print(f"❌ Lỗi xử lý audio chunk: {e}")
102
- # return {
103
- # 'transcription': "",
104
- # 'response': "",
105
- # 'tts_audio': None,
106
- # 'status': 'error'
107
- # }
108
-
109
- # def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
110
- # """Callback khi VAD phát hiện speech"""
111
- # print(f"🎯 VAD phát hiện speech segment: {len(speech_audio)/sample_rate:.2f}s")
112
- # self.last_speech_time = time.time()
113
-
114
- # # Chỉ xử lý nếu không đang xử lý cái khác
115
- # if self.is_processing:
116
- # print("⚠️ Đang xử lý request trước đó, bỏ qua...")
117
- # return
118
-
119
- # self.is_processing = True
120
-
121
- # try:
122
- # # Chuyển đổi speech thành text
123
- # transcription = self._transcribe_audio(speech_audio, sample_rate)
124
-
125
- # if not transcription or len(transcription.strip()) < 2:
126
- # print("⚠️ Transcription quá ngắn hoặc trống")
127
- # self.is_processing = False
128
- # return
129
-
130
- # print(f"📝 VAD Transcription: {transcription}")
131
- # self.current_transcription = transcription
132
-
133
- # # Tạo phản hồi AI
134
- # response = self._generate_ai_response(transcription)
135
-
136
- # # Tạo TTS
137
- # tts_audio_path = self._text_to_speech(response)
138
-
139
- # # Gửi kết quả đến callback
140
- # if self.speech_callback:
141
- # self.speech_callback({
142
- # 'transcription': transcription,
143
- # 'response': response,
144
- # 'tts_audio': tts_audio_path,
145
- # 'status': 'completed'
146
- # })
147
-
148
- # except Exception as e:
149
- # print(f"❌ Lỗi trong _on_speech_detected: {e}")
150
- # finally:
151
- # # Cho phép xử lý tiếp sau khi TTS kết thúc
152
- # threading.Timer(1.0, self._reset_processing).start()
153
-
154
- # def _reset_processing(self):
155
- # """Reset trạng thái xử lý sau khi hoàn thành"""
156
- # self.is_processing = False
157
- # with self.buffer_lock:
158
- # self.audio_buffer = []
159
-
160
- # def _process_final_audio(self):
161
- # """Xử lý audio cuối cùng khi hết thời gian im lặng"""
162
- # if self.is_processing or not self.audio_buffer:
163
- # return
164
-
165
- # try:
166
- # with self.buffer_lock:
167
- # if not self.audio_buffer:
168
- # return
169
-
170
- # final_audio = np.array(self.audio_buffer)
171
- # self.audio_buffer = []
172
-
173
- # # Chỉ xử lý nếu audio đủ dài
174
- # if len(final_audio) > 16000 * 0.5: # Ít nhất 0.5 giây
175
- # print("🔄 Xử lý audio cuối cùng do im lặng timeout")
176
- # self._on_speech_detected(final_audio, 16000)
177
-
178
- # except Exception as e:
179
- # print(f"❌ Lỗi xử lý final audio: {e}")
180
-
181
- # def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
182
- # """Xử lý audio streaming (phương thức cũ cho compatibility)"""
183
- # if not audio_data:
184
- # return {
185
- # 'transcription': "❌ Không có dữ liệu âm thanh",
186
- # 'response': "Vui lòng nói lại",
187
- # 'tts_audio': None,
188
- # 'status': 'error'
189
- # }
190
-
191
- # # Nếu đang xử lý VAD, trả về trạng thái listening
192
- # if self.is_processing:
193
- # return {
194
- # 'transcription': "Đang xử lý...",
195
- # 'response': "",
196
- # 'tts_audio': None,
197
- # 'status': 'processing'
198
- # }
199
-
200
- # try:
201
- # # Lấy dữ liệu audio từ Gradio
202
- # sample_rate, audio_array = audio_data
203
-
204
- # print(f"🎯 Nhận audio: {len(audio_array)} samples, SR: {sample_rate}")
205
-
206
- # # Kiểm tra kiểu dữ liệu và chuyển đổi nếu cần
207
- # if isinstance(audio_array, np.ndarray):
208
- # if audio_array.dtype == np.float32 or audio_array.dtype == np.float64:
209
- # # Chuyển từ float sang int16
210
- # audio_array = (audio_array * 32767).astype(np.int16)
211
-
212
- # # Kiểm tra audio có dữ liệu không
213
- # if len(audio_array) == 0:
214
- # return {
215
- # 'transcription': "❌ Âm thanh trống",
216
- # 'response': "Vui lòng nói lại",
217
- # 'tts_audio': None,
218
- # 'status': 'error'
219
- # }
220
-
221
- # # Tính toán âm lượng
222
- # audio_abs = np.abs(audio_array.astype(np.float32))
223
- # audio_rms = np.sqrt(np.mean(audio_abs**2)) / 32767.0
224
- # print(f"📊 Âm lượng RMS: {audio_rms:.4f}")
225
-
226
- # if audio_rms < 0.005:
227
- # return {
228
- # 'transcription': "❌ Âm thanh quá yếu",
229
- # 'response': "Xin vui lòng nói to hơn",
230
- # 'tts_audio': None,
231
- # 'status': 'error'
232
- # }
233
-
234
- # # Sử dụng VAD để kiểm tra speech
235
- # if not self.vad_processor.is_speech(audio_array, sample_rate):
236
- # return {
237
- # 'transcription': "❌ Không phát hiện giọng nói",
238
- # 'response': "Vui lòng nói rõ hơn",
239
- # 'tts_audio': None,
240
- # 'status': 'error'
241
- # }
242
-
243
- # # Chuyển đổi thành văn bản
244
- # transcription = self._transcribe_audio(audio_array, sample_rate)
245
-
246
- # if not transcription or len(transcription.strip()) == 0:
247
- # return {
248
- # 'transcription': "❌ Không nghe rõ",
249
- # 'response': "Xin vui lòng nói lại rõ hơn",
250
- # 'tts_audio': None,
251
- # 'status': 'error'
252
- # }
253
-
254
- # # Kiểm tra nếu transcription quá ngắn
255
- # if len(transcription.strip()) < 2:
256
- # return {
257
- # 'transcription': "❌ Câu nói quá ngắn",
258
- # 'response': "Xin vui lòng nói câu dài hơn",
259
- # 'tts_audio': None,
260
- # 'status': 'error'
261
- # }
262
-
263
- # print(f"📝 Đã chuyển đổi: {transcription}")
264
-
265
- # # Cập nhật transcription hiện tại
266
- # self.current_transcription = transcription
267
-
268
- # # Tạo phản hồi AI
269
- # response = self._generate_ai_response(transcription)
270
-
271
- # # Tạo TTS
272
- # tts_audio_path = self._text_to_speech(response)
273
-
274
- # return {
275
- # 'transcription': transcription,
276
- # 'response': response,
277
- # 'tts_audio': tts_audio_path,
278
- # 'status': 'completed'
279
- # }
280
-
281
- # except Exception as e:
282
- # print(f"❌ Lỗi xử lý streaming audio: {e}")
283
- # print(f"Chi tiết lỗi: {traceback.format_exc()}")
284
- # return {
285
- # 'transcription': f"❌ Lỗi: {str(e)}",
286
- # 'response': "Xin lỗi, có lỗi xảy ra trong quá trình xử lý",
287
- # 'tts_audio': None,
288
- # 'status': 'error'
289
- # }
290
-
291
- # def _transcribe_audio(self, audio_data: np.ndarray, sample_rate: int) -> Optional[str]:
292
- # """Chuyển audio -> text với xử lý sample rate cải tiến"""
293
- # try:
294
- # # Đảm bảo kiểu dữ liệu là int16
295
- # if audio_data.dtype != np.int16:
296
- # if audio_data.dtype in [np.float32, np.float64]:
297
- # audio_data = (audio_data * 32767).astype(np.int16)
298
- # else:
299
- # audio_data = audio_data.astype(np.int16)
300
-
301
- # # Chuẩn hóa audio data
302
- # if audio_data.ndim > 1:
303
- # audio_data = np.mean(audio_data, axis=1).astype(np.int16) # Chuyển sang mono
304
-
305
- # # Resample nếu sample rate không phải 16000Hz (Whisper yêu cầu)
306
- # target_sample_rate = 16000
307
- # if sample_rate != target_sample_rate:
308
- # audio_data = self._resample_audio(audio_data, sample_rate, target_sample_rate)
309
- # sample_rate = target_sample_rate
310
- # print(f"🔄 Đã resample từ {sample_rate}Hz xuống {target_sample_rate}Hz")
311
-
312
- # # Giới hạn độ dài audio
313
- # max_duration = 10 # giây
314
- # max_samples = sample_rate * max_duration
315
- # if len(audio_data) > max_samples:
316
- # audio_data = audio_data[:max_samples]
317
- # print(f"⚠️ Cắt audio xuống còn {max_duration} giây")
318
-
319
- # # Đảm bảo audio đủ dài
320
- # min_duration = 0.5 # giây
321
- # min_samples = int(sample_rate * min_duration)
322
- # if len(audio_data) < min_samples:
323
- # # Pad audio nếu quá ngắn
324
- # padding = np.zeros(min_samples - len(audio_data), dtype=np.int16)
325
- # audio_data = np.concatenate([audio_data, padding])
326
- # print(f"⚠️ Đã pad audio lên {min_duration} giây")
327
-
328
- # print(f"🔊 Gửi audio đến Whisper: {len(audio_data)} samples, {sample_rate}Hz")
329
-
330
- # # Tạo temporary file trong memory
331
- # buffer = io.BytesIO()
332
- # sf.write(buffer, audio_data, sample_rate, format='wav', subtype='PCM_16')
333
- # buffer.seek(0)
334
-
335
- # # Gọi API Whisper với timeout
336
- # import requests
337
- # try:
338
- # transcription = self.client.audio.transcriptions.create(
339
- # model=settings.WHISPER_MODEL,
340
- # file=("speech.wav", buffer.read(), "audio/wav"),
341
- # response_format="text",
342
- # language="vi",
343
- # temperature=0.0,
344
- # )
345
- # except requests.exceptions.Timeout:
346
- # print("❌ Whisper API timeout")
347
- # return None
348
- # except Exception as e:
349
- # print(f"❌ Lỗi Whisper API: {e}")
350
- # return None
351
-
352
- # # Xử lý response
353
- # if hasattr(transcription, 'text'):
354
- # result = transcription.text.strip()
355
- # elif isinstance(transcription, str):
356
- # result = transcription.strip()
357
- # else:
358
- # result = str(transcription).strip()
359
-
360
- # print(f"✅ Transcription thành công: '{result}'")
361
- # return result
362
-
363
- # except Exception as e:
364
- # print(f"❌ Lỗi transcription: {e}")
365
- # print(f"Audio details: dtype={audio_data.dtype}, shape={audio_data.shape}, sr={sample_rate}")
366
- # return None
367
-
368
- # def _resample_audio(self, audio_data: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray:
369
- # """Resample audio sử dụng scipy - cải tiến độ chính xác"""
370
- # try:
371
- # from scipy import signal
372
-
373
- # # Tính số samples mới
374
- # duration = len(audio_data) / orig_sr
375
- # new_length = int(duration * target_sr)
376
-
377
- # # Resample sử dụng scipy.signal.resample với windowing
378
- # resampled_audio = signal.resample(audio_data, new_length)
379
-
380
- # # Chuyển lại về int16
381
- # resampled_audio = np.clip(resampled_audio, -32768, 32767).astype(np.int16)
382
-
383
- # return resampled_audio
384
-
385
- # except ImportError:
386
- # print("⚠️ Không có scipy, sử dụng simple resampling")
387
- # # Simple resampling bằng interpolation
388
- # orig_length = len(audio_data)
389
- # new_length = int(orig_length * target_sr / orig_sr)
390
-
391
- # # Linear interpolation
392
- # x_old = np.linspace(0, 1, orig_length)
393
- # x_new = np.linspace(0, 1, new_length)
394
- # resampled_audio = np.interp(x_new, x_old, audio_data).astype(np.int16)
395
-
396
- # return resampled_audio
397
- # except Exception as e:
398
- # print(f"❌ Lỗi resample: {e}")
399
- # return audio_data
400
-
401
- # def _generate_ai_response(self, user_input: str) -> str:
402
- # """Sinh phản hồi AI với xử lý lỗi"""
403
- # try:
404
- # # Thêm vào lịch sử
405
- # self.conversation_history.append({"role": "user", "content": user_input})
406
-
407
- # # Tìm kiếm RAG
408
- # rag_results = self.rag_system.semantic_search(user_input, top_k=2)
409
- # context_text = "\n".join([f"- {result.get('text', str(result))}" for result in rag_results]) if rag_results else ""
410
-
411
- # system_prompt = f"""Bạn là trợ lý AI thông minh chuyên về tiếng Việt.
412
- # Hãy trả lời ngắn gọn, tự nhiên và hữu ích (dưới 100 từ).
413
- # Thông tin tham khảo:
414
- # {context_text}
415
- # """
416
-
417
- # messages = [{"role": "system", "content": system_prompt}]
418
- # # Giữ lại 4 tin nhắn gần nhất
419
- # messages.extend(self.conversation_history[-4:])
420
-
421
- # completion = self.client.chat.completions.create(
422
- # model="llama-3.1-8b-instant",
423
- # messages=messages,
424
- # max_tokens=150,
425
- # temperature=0.7
426
- # )
427
-
428
- # response = completion.choices[0].message.content
429
- # self.conversation_history.append({"role": "assistant", "content": response})
430
-
431
- # # Giới hạn lịch sử
432
- # if len(self.conversation_history) > 8:
433
- # self.conversation_history = self.conversation_history[-8:]
434
-
435
- # return response
436
-
437
- # except Exception as e:
438
- # print(f"❌ Lỗi tạo AI response: {e}")
439
- # return "Xin lỗi, tôi gặp lỗi khi tạo phản hồi. Vui lòng thử lại."
440
-
441
- # def _text_to_speech(self, text: str) -> Optional[str]:
442
- # """Chuyển văn bản thành giọng nói với xử lý lỗi"""
443
- # try:
444
- # if not text or text.startswith("❌") or text.startswith("Xin lỗi"):
445
- # return None
446
-
447
- # tts_bytes = self.tts_service.text_to_speech(text, 'vi')
448
- # if tts_bytes:
449
- # audio_path = self.tts_service.save_audio_to_file(tts_bytes)
450
- # print(f"✅ Đã tạo TTS: {audio_path}")
451
- # return audio_path
452
- # except Exception as e:
453
- # print(f"❌ Lỗi TTS: {e}")
454
- # return None
455
-
456
- # def clear_conversation(self):
457
- # """Xóa lịch sử hội thoại"""
458
- # self.conversation_history = []
459
- # self.current_transcription = ""
460
- # print("🗑️ Đã xóa lịch sử hội thoại")
461
-
462
- # def get_conversation_state(self) -> dict:
463
- # """Lấy trạng thái hội thoại"""
464
- # return {
465
- # 'is_listening': self.is_listening,
466
- # 'is_processing': self.is_processing,
467
- # 'history_length': len(self.conversation_history),
468
- # 'current_transcription': self.current_transcription,
469
- # 'last_update': time.strftime("%H:%M:%S")
470
- # }
471
  class StreamingVoiceService:
472
  def __init__(self, groq_client: Groq, rag_system: EnhancedRAGSystem, tts_service: EnhancedTTSService):
473
  self.client = groq_client
 
14
  from core.speechbrain_vad import SpeechBrainVAD
15
  from core.silero_vad import SileroVAD
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  class StreamingVoiceService:
18
  def __init__(self, groq_client: Groq, rag_system: EnhancedRAGSystem, tts_service: EnhancedTTSService):
19
  self.client = groq_client