datbkpro commited on
Commit
361b4a9
·
verified ·
1 Parent(s): cc18c18

Update services/streaming_voice_service.py

Browse files
Files changed (1) hide show
  1. services/streaming_voice_service.py +508 -798
services/streaming_voice_service.py CHANGED
@@ -1,4 +1,219 @@
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  # import io
3
  # import numpy as np
4
  # import soundfile as sf
@@ -91,7 +306,7 @@
91
  # return False
92
 
93
  # def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
94
- # """Xử lý audio chunk và trả về kết quả - FIXED VERSION"""
95
  # if self.recognizer is None or not self.is_streaming:
96
  # return {"text": "", "partial": "", "is_final": False}
97
 
@@ -111,12 +326,16 @@
111
  # else:
112
  # audio_chunk = audio_chunk.astype(np.int16)
113
 
114
- # # Kiểm tra âm lượng
 
 
 
115
  # audio_rms = np.sqrt(np.mean(audio_chunk.astype(np.float32)**2)) / 32767.0
116
- # print(f"📊 Audio RMS: {audio_rms:.4f}")
117
 
118
- # if audio_rms < 0.01: # Âm lượng quá thấp
119
- # print("⚠️ Âm lượng quá thấp, bỏ qua")
 
120
  # return {"text": "", "partial": "", "is_final": False}
121
 
122
  # # Chuyển đổi sang bytes
@@ -146,6 +365,27 @@
146
 
147
  # return {"text": "", "partial": "", "is_final": False}
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  # def stop_stream(self) -> str:
150
  # """Kết thúc stream và lấy kết quả cuối"""
151
  # if self.recognizer:
@@ -180,7 +420,7 @@
180
  # self.rag_system = rag_system
181
  # self.tts_service = tts_service
182
 
183
- # # Khởi tạo VOSK ASR - FIXED: Thêm timeout và retry
184
  # print("🔄 Đang khởi tạo VOSK ASR...")
185
  # self.vosk_asr = VoskStreamingASR()
186
 
@@ -200,15 +440,19 @@
200
  # self.processing_threads = []
201
  # self.max_workers = 2
202
 
203
- # # Streaming state
204
  # self.vosk_stream_active = False
205
  # self.last_voice_time = 0
206
- # self.silence_timeout = 3.0 # Tăng timeout lên 3 giây
207
 
208
  # # Audio buffer để cải thiện nhận diện
209
  # self.audio_buffer = []
210
- # self.buffer_duration = 1.0 # Buffer 1 giây
211
- # self.max_buffer_samples = 16000 # 1 giây ở 16kHz
 
 
 
 
212
 
213
  # # Latency tracking
214
  # self.latency_metrics = {
@@ -252,6 +496,7 @@
252
  # self.vosk_stream_active = True
253
  # self.last_voice_time = time.time()
254
  # self.audio_buffer = []
 
255
 
256
  # # Khởi động worker threads
257
  # if not self.processing_threads:
@@ -264,8 +509,8 @@
264
  # thread.start()
265
  # self.processing_threads.append(thread)
266
 
267
- # # Bắt đầu thread theo dõi VOSK streaming
268
- # threading.Thread(target=self._vosk_streaming_monitor, daemon=True).start()
269
 
270
  # print("🎙️ Đã bắt đầu lắng nghe với VOSK ASR streaming")
271
 
@@ -282,118 +527,184 @@
282
 
283
  # return False
284
 
285
- # def _vosk_streaming_monitor(self):
286
- # """Theo dõi VOSK streaming và xử lý kết quả real-time"""
287
- # while self.is_listening and self.vosk_stream_active:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  # try:
289
  # current_time = time.time()
290
  # silence_duration = current_time - self.last_voice_time
291
 
292
- # # Xử lý audio buffer nếu có dữ liệu
293
- # if self.audio_buffer and silence_duration > 0.5: # 0.5 giây im lặng
294
- # combined_audio = np.concatenate(self.audio_buffer)
295
- # if len(combined_audio) > 1600: # Ít nhất 0.1 giây audio
296
- # result = self.vosk_asr.process_audio_chunk(combined_audio, 16000)
297
- # self._handle_vosk_result(result)
298
- # self.audio_buffer = []
299
-
300
- # # Timeout im lặng
301
- # if silence_duration > self.silence_timeout and self.partial_transcription:
302
  # print(f"⏰ Silence timeout, xử lý: '{self.partial_transcription}'")
303
- # if len(self.partial_transcription) > 2: # Chỉ xử lý nếu có nội dung
304
- # self._process_final_transcription(self.partial_transcription)
 
 
 
 
 
 
 
 
 
305
  # self.partial_transcription = ""
306
  # self.vosk_asr.start_stream()
307
 
308
- # time.sleep(0.1)
309
 
310
  # except Exception as e:
311
- # print(f"❌ Lỗi VOSK monitor: {e}")
312
- # break
313
-
314
- # def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
315
- # """Callback khi VAD phát hiện speech - FIXED VERSION"""
316
- # if not self.vosk_stream_active or not self.is_listening:
317
- # return
318
-
319
- # # Cập nhật thời gian có giọng nói
320
- # self.last_voice_time = time.time()
321
-
322
- # # Thêm vào audio buffer để cải thiện nhận diện
323
- # self.audio_buffer.append(speech_audio)
324
-
325
- # # Giới hạn buffer size
326
- # total_samples = sum(len(chunk) for chunk in self.audio_buffer)
327
- # if total_samples > self.max_buffer_samples:
328
- # # Giữ lại các chunk gần nhất
329
- # while total_samples > self.max_buffer_samples and len(self.audio_buffer) > 1:
330
- # removed = self.audio_buffer.pop(0)
331
- # total_samples -= len(removed)
332
-
333
- # print(f"🎯 VAD detected: {len(speech_audio)} samples, Buffer: {len(self.audio_buffer)} chunks")
334
-
335
- # def _handle_vosk_result(self, result: Dict[str, Any]):
336
- # """Xử lý kết quả từ VOSK"""
337
- # # Xử lý kết quả partial
338
- # if result['partial'] and len(result['partial']) > 1:
339
- # self.partial_transcription = result['partial']
340
- # print(f"🎯 VOSK Partial: '{result['partial']}'")
341
-
342
- # # Gửi partial result real-time
343
- # if self.current_callback:
344
- # self.current_callback({
345
- # 'transcription': result['partial'],
346
- # 'response': "",
347
- # 'tts_audio': None,
348
- # 'status': 'partial'
349
- # })
350
-
351
- # # Xử lý kết quả final
352
- # if result['is_final'] and result['text'] and len(result['text']) > 1:
353
- # print(f"✅ VOSK Final: '{result['text']}'")
354
- # self._process_final_transcription(result['text'])
355
- # self.partial_transcription = ""
356
- # self.audio_buffer = [] # Clear buffer
357
- # self.vosk_asr.start_stream() # Bắt đầu stream mới
358
-
359
- # def _process_final_transcription(self, transcription: str):
360
- # """Xử lý transcription cuối cùng"""
361
- # if not transcription or len(transcription.strip()) < 2:
362
- # return
363
-
364
- # print(f"📝 Final Transcription: '{transcription}'")
365
- # self.current_transcription = transcription
366
-
367
- # # Đưa vào queue để xử lý
368
- # try:
369
- # self.response_queue.put(transcription, timeout=0.5)
370
- # print(f"📦 Đã đưa vào queue: '{transcription}'")
371
- # except queue.Full:
372
- # print("⚠️ Queue đầy, bỏ qua transcription")
373
 
374
  # def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
375
- # """Xử lý audio streaming manual mode với VOSK - FIXED VERSION"""
376
  # if not audio_data:
377
  # return self._create_error_response("❌ Không có dữ liệu âm thanh")
378
 
379
  # try:
380
  # sample_rate, audio_array = audio_data
381
 
382
- # print(f"🎤 Manual audio: {len(audio_array)} samples, {sample_rate}Hz")
 
 
 
383
 
384
- # # Kiểm tra âm lượng
385
  # if isinstance(audio_array, np.ndarray):
386
  # if audio_array.dtype in [np.float32, np.float64]:
387
  # audio_rms = np.sqrt(np.mean(audio_array**2))
388
- # print(f"📊 Manual audio RMS: {audio_rms:.4f}")
 
389
 
390
- # if audio_rms < 0.01:
391
- # return {
392
- # 'transcription': "Âm thanh quá nhỏ, hãy nói to hơn",
393
- # 'response': "",
394
- # 'tts_audio': None,
395
- # 'status': 'listening'
396
- # }
 
 
 
397
 
398
  # # Khởi động VOSK stream tạm thời
399
  # if not self.vosk_asr.start_stream():
@@ -425,7 +736,7 @@
425
  # }
426
  # else:
427
  # return {
428
- # 'transcription': "Đang nghe... Hãy nói rõ hơn",
429
  # 'response': "",
430
  # 'tts_audio': None,
431
  # 'status': 'listening'
@@ -435,6 +746,80 @@
435
  # print(f"❌ Lỗi xử lý streaming audio: {e}")
436
  # traceback.print_exc()
437
  # return self._create_error_response(f"❌ Lỗi: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
 
439
  # def _create_error_response(self, message: str) -> Dict[str, Any]:
440
  # """Tạo response lỗi chuẩn"""
@@ -445,21 +830,27 @@
445
  # 'status': 'error'
446
  # }
447
 
448
- # def _add_latency_sample(self, component: str, latency: float):
449
- # """Thêm mẫu latency"""
450
- # if component in self.latency_metrics:
451
- # self.latency_metrics[component].append(latency)
452
- # if len(self.latency_metrics[component]) > 100:
453
- # self.latency_metrics[component] = self.latency_metrics[component][-100:]
 
 
 
 
 
 
 
 
454
 
455
- # def _log_latency_metrics(self, latencies: dict):
456
- # """Log theo dõi latency metrics"""
457
- # for key, value in latencies.items():
458
- # self._add_latency_sample(key, value)
459
-
460
- # print("📊 LATENCY REPORT:")
461
- # for component, latency in latencies.items():
462
- # print(f" {component.upper()}: {latency:.2f}s")
463
 
464
  # def get_latency_stats(self) -> dict:
465
  # """Lấy thống kê latency"""
@@ -481,685 +872,4 @@
481
  # 'avg': 0, 'min': 0, 'max': 0, 'count': 0,
482
  # 'recent_avg': 0, 'recent_min': 0, 'recent_max': 0
483
  # }
484
- # return stats
485
-
486
- # def get_conversation_state(self) -> dict:
487
- # """Lấy trạng thái hội thoại"""
488
- # return {
489
- # 'is_listening': self.is_listening,
490
- # 'is_processing': self.is_processing,
491
- # 'history_length': len(self.conversation_history),
492
- # 'current_transcription': self.current_transcription,
493
- # 'partial_transcription': self.partial_transcription,
494
- # 'queue_size': self.response_queue.qsize(),
495
- # 'worker_threads': len(self.processing_threads),
496
- # 'vosk_active': self.vosk_stream_active,
497
- # 'audio_buffer_chunks': len(self.audio_buffer),
498
- # 'last_voice_time': time.strftime("%H:%M:%S", time.localtime(self.last_voice_time)),
499
- # 'last_update': time.strftime("%H:%M:%S")
500
- # }
501
- # def clear_conversation(self):
502
- # """Xóa lịch sử hội thoại"""
503
- # self.conversation_history = []
504
- # self.current_transcription = ""
505
- # self.partial_transcription = ""
506
- # print("🗑️ Đã xóa lịch sử hội thoại")
507
- import io
508
- import numpy as np
509
- import soundfile as sf
510
- import time
511
- import traceback
512
- import threading
513
- import queue
514
- import json
515
- import os
516
- from vosk import Model, KaldiRecognizer
517
- from groq import Groq
518
- from typing import Optional, Dict, Any, Callable
519
- from config.settings import settings
520
- from core.rag_system import EnhancedRAGSystem
521
- from core.tts_service import EnhancedTTSService
522
- from core.silero_vad import SileroVAD
523
-
524
- class VoskStreamingASR:
525
- def __init__(self, model_path: str = None):
526
- """Khởi tạo VOSK ASR streaming với debug"""
527
- self.model = None
528
- self.recognizer = None
529
- self.sample_rate = 16000
530
- self.is_streaming = False
531
-
532
- # Tự động tải model nếu không có đường dẫn
533
- if model_path is None:
534
- model_path = self._download_vosk_model()
535
-
536
- if model_path and os.path.exists(model_path):
537
- print(f"🔄 Đang tải VOSK model từ: {model_path}")
538
- try:
539
- self.model = Model(model_path)
540
- self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
541
- self.recognizer.SetWords(True)
542
- print("✅ Đã tải VOSK model thành công")
543
- except Exception as e:
544
- print(f"❌ Lỗi khởi tạo VOSK model: {e}")
545
- else:
546
- print(f"❌ Không tìm thấy VOSK model tại: {model_path}")
547
-
548
- def _download_vosk_model(self):
549
- """Tải VOSK model tiếng Việt tự động"""
550
- try:
551
- import urllib.request
552
- import zipfile
553
-
554
- model_url = "https://alphacephei.com/vosk/models/vosk-model-small-vn-0.4.zip"
555
- model_dir = "models/vosk-model-small-vn-0.4"
556
- zip_path = "models/vosk-model-small-vn-0.4.zip"
557
-
558
- # Tạo thư mục nếu chưa có
559
- os.makedirs("models", exist_ok=True)
560
-
561
- if not os.path.exists(model_dir):
562
- print("📥 Đang tải VOSK Vietnamese model...")
563
- urllib.request.urlretrieve(model_url, zip_path)
564
-
565
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
566
- zip_ref.extractall("models/")
567
-
568
- # Đảm bảo thư mục tồn tại
569
- if os.path.exists("models/vosk-model-small-vn-0.4"):
570
- os.rename("models/vosk-model-small-vn-0.4", model_dir)
571
-
572
- if os.path.exists(zip_path):
573
- os.remove(zip_path)
574
- print("✅ Đã tải VOSK model thành công")
575
-
576
- return model_dir if os.path.exists(model_dir) else None
577
-
578
- except Exception as e:
579
- print(f"❌ Lỗi tải VOSK model: {e}")
580
- return None
581
-
582
- def start_stream(self):
583
- """Bắt đầu stream mới"""
584
- if self.model is None:
585
- print("❌ VOSK model chưa được khởi tạo")
586
- return False
587
-
588
- try:
589
- self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
590
- self.recognizer.SetWords(True)
591
- self.is_streaming = True
592
- print("🎤 Đã khởi động VOSK stream")
593
- return True
594
- except Exception as e:
595
- print(f"❌ Lỗi khởi động VOSK stream: {e}")
596
- return False
597
-
598
- def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
599
- """Xử lý audio chunk và trả về kết quả - FIXED VOLUME VERSION"""
600
- if self.recognizer is None or not self.is_streaming:
601
- return {"text": "", "partial": "", "is_final": False}
602
-
603
- try:
604
- # DEBUG: Thông tin audio chunk
605
- print(f"🔊 Audio chunk: {len(audio_chunk)} samples, dtype: {audio_chunk.dtype}, max: {np.max(audio_chunk):.4f}")
606
-
607
- # Chuẩn hóa audio - QUAN TRỌNG: VOSK cần audio ở dạng int16
608
- if sample_rate and sample_rate != self.sample_rate:
609
- audio_chunk = self._resample_audio(audio_chunk, sample_rate, self.sample_rate)
610
-
611
- # Đảm bảo là int16 với giá trị phù hợp
612
- if audio_chunk.dtype != np.int16:
613
- if audio_chunk.dtype in [np.float32, np.float64]:
614
- # Audio float cần được scale về [-32768, 32767]
615
- audio_chunk = (audio_chunk * 32767).astype(np.int16)
616
- else:
617
- audio_chunk = audio_chunk.astype(np.int16)
618
-
619
- # FIXED: Tăng cường âm lượng trước khi kiểm tra
620
- audio_chunk = self._boost_audio_volume(audio_chunk)
621
-
622
- # Kiểm tra âm lượng - GIẢM ngưỡng xuống
623
- audio_rms = np.sqrt(np.mean(audio_chunk.astype(np.float32)**2)) / 32767.0
624
- print(f"📊 Audio RMS: {audio_rms:.4f}, Max: {np.max(audio_chunk)}")
625
-
626
- # FIXED: Giảm ngưỡng âm lượng từ 0.01 xuống 0.001
627
- if audio_rms < 0.001: # Giảm ngưỡng 10 lần
628
- print(f"⚠️ Âm lượng quá thấp (RMS: {audio_rms:.6f}), bỏ qua")
629
- return {"text": "", "partial": "", "is_final": False}
630
-
631
- # Chuyển đổi sang bytes
632
- audio_bytes = audio_chunk.tobytes()
633
-
634
- # Xử lý với VOSK
635
- if self.recognizer.AcceptWaveform(audio_bytes):
636
- # Kết quả cuối cùng
637
- result_json = self.recognizer.Result()
638
- result = json.loads(result_json)
639
- text = result.get('text', '').strip()
640
- print(f"✅ VOSK Final Result: '{text}'")
641
- if text:
642
- return {"text": text, "partial": "", "is_final": True}
643
- else:
644
- # Kết quả tạm thời
645
- partial_json = self.recognizer.PartialResult()
646
- partial_result = json.loads(partial_json)
647
- partial_text = partial_result.get('partial', '').strip()
648
- if partial_text:
649
- print(f"🎯 VOSK Partial: '{partial_text}'")
650
- return {"text": "", "partial": partial_text, "is_final": False}
651
-
652
- except Exception as e:
653
- print(f"❌ Lỗi VOSK processing: {e}")
654
- traceback.print_exc()
655
-
656
- return {"text": "", "partial": "", "is_final": False}
657
-
658
- def _boost_audio_volume(self, audio_chunk: np.ndarray, boost_factor: float = 5.0) -> np.ndarray:
659
- """Tăng cường âm lượng audio"""
660
- try:
661
- # Chuyển sang float để xử lý
662
- audio_float = audio_chunk.astype(np.float32) / 32768.0
663
-
664
- # Tăng âm lượng
665
- boosted_audio = audio_float * boost_factor
666
-
667
- # Ngăn chặn clipping
668
- boosted_audio = np.clip(boosted_audio, -1.0, 1.0)
669
-
670
- # Chuyển lại sang int16
671
- boosted_audio_int16 = (boosted_audio * 32767).astype(np.int16)
672
-
673
- print(f"🔊 Volume boosted: {boost_factor}x, New max: {np.max(boosted_audio_int16)}")
674
- return boosted_audio_int16
675
-
676
- except Exception as e:
677
- print(f"⚠️ Lỗi boost volume: {e}")
678
- return audio_chunk
679
- def stop_stream(self) -> str:
680
- """Kết thúc stream và lấy kết quả cuối"""
681
- if self.recognizer:
682
- try:
683
- result_json = self.recognizer.FinalResult()
684
- result = json.loads(result_json)
685
- text = result.get('text', '').strip()
686
- self.is_streaming = False
687
- print(f"🛑 VOSK Final: '{text}'")
688
- return text
689
- except Exception as e:
690
- print(f"❌ Lỗi khi dừng VOSK stream: {e}")
691
- return ""
692
-
693
- def _resample_audio(self, audio: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray:
694
- """Resample audio với chất lượng tốt hơn"""
695
- if orig_sr == target_sr:
696
- return audio
697
- try:
698
- from scipy import signal
699
- # Tính số sample mới
700
- num_samples = int(len(audio) * target_sr / orig_sr)
701
- resampled_audio = signal.resample(audio, num_samples)
702
- return resampled_audio.astype(np.int16)
703
- except Exception as e:
704
- print(f"❌ Lỗi resample audio: {e}")
705
- return audio
706
-
707
- class StreamingVoiceService:
708
- def __init__(self, groq_client: Groq, rag_system, tts_service):
709
- self.client = groq_client
710
- self.rag_system = rag_system
711
- self.tts_service = tts_service
712
-
713
- # Khởi tạo VOSK ASR
714
- print("🔄 Đang khởi tạo VOSK ASR...")
715
- self.vosk_asr = VoskStreamingASR()
716
-
717
- # Khởi tạo VAD
718
- self.vad_processor = SileroVAD()
719
- self.is_listening = False
720
- self.speech_callback = None
721
- self.is_processing = False
722
-
723
- # Conversation context
724
- self.conversation_history = []
725
- self.current_transcription = ""
726
- self.partial_transcription = ""
727
-
728
- # Multi-thread processing
729
- self.response_queue = queue.Queue()
730
- self.processing_threads = []
731
- self.max_workers = 2
732
-
733
- # Streaming state - FIXED: Thêm các biến state mới
734
- self.vosk_stream_active = False
735
- self.last_voice_time = 0
736
- self.silence_timeout = 3.0
737
-
738
- # Audio buffer để cải thiện nhận diện
739
- self.audio_buffer = []
740
- self.buffer_duration = 1.0
741
- self.max_buffer_samples = 16000
742
-
743
- # Real-time processing
744
- self.realtime_buffer = queue.Queue()
745
- self.processing_active = False
746
-
747
- # Latency tracking
748
- self.latency_metrics = {
749
- 'asr': [], 'rag': [], 'llm': [], 'tts': [], 'total': [],
750
- 'vad_detection': [], 'queue_waiting': [], 'vosk_processing': []
751
- }
752
-
753
- self.current_callback = None
754
-
755
- def start_listening(self, speech_callback: Callable) -> bool:
756
- """Bắt đầu lắng nghe với VOSK streaming - FIXED VERSION"""
757
- if self.is_listening:
758
- print("⚠️ Đã đang lắng nghe")
759
- return False
760
-
761
- self.current_callback = speech_callback
762
-
763
- # Kiểm tra VOSK model
764
- if self.vosk_asr.model is None:
765
- print("❌ VOSK model không khả dụng")
766
- if self.current_callback:
767
- self.current_callback({
768
- 'transcription': "Lỗi: VOSK model không khả dụng",
769
- 'response': "Không thể khởi động nhận diện giọng nói",
770
- 'tts_audio': None,
771
- 'status': 'error'
772
- })
773
- return False
774
-
775
- # Khởi động VOSK stream
776
- if not self.vosk_asr.start_stream():
777
- print("❌ Không thể khởi động VOSK stream")
778
- return False
779
-
780
- # Khởi động VAD
781
- success = self.vad_processor.start_stream(self._on_speech_detected)
782
-
783
- if success:
784
- self.is_listening = True
785
- self.is_processing = False
786
- self.vosk_stream_active = True
787
- self.last_voice_time = time.time()
788
- self.audio_buffer = []
789
- self.processing_active = True
790
-
791
- # Khởi động worker threads
792
- if not self.processing_threads:
793
- for i in range(self.max_workers):
794
- thread = threading.Thread(
795
- target=self._process_response_worker,
796
- daemon=True,
797
- name=f"ASR-Worker-{i}"
798
- )
799
- thread.start()
800
- self.processing_threads.append(thread)
801
-
802
- # Bắt đầu real-time processing thread
803
- threading.Thread(target=self._realtime_processing_worker, daemon=True).start()
804
-
805
- print("🎙️ Đã bắt đầu lắng nghe với VOSK ASR streaming")
806
-
807
- # Thông báo trạng thái
808
- if self.current_callback:
809
- self.current_callback({
810
- 'transcription': "Đã bắt đầu lắng nghe... Hãy nói gì đó",
811
- 'response': "",
812
- 'tts_audio': None,
813
- 'status': 'listening'
814
- })
815
-
816
- return True
817
-
818
- return False
819
-
820
- def stop_listening(self):
821
- """Dừng lắng nghe"""
822
- self.is_listening = False
823
- self.vosk_stream_active = False
824
- self.processing_active = False
825
- self.vad_processor.stop_stream()
826
- print("🛑 Đã dừng lắng nghe")
827
-
828
- def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
829
- """Callback khi VAD phát hiện speech - FIXED VERSION"""
830
- if not self.vosk_stream_active or not self.is_listening:
831
- return
832
-
833
- try:
834
- # Cập nhật thời gian có giọng nói
835
- self.last_voice_time = time.time()
836
-
837
- print(f"🎯 VAD detected: {len(speech_audio)} samples, {sample_rate}Hz")
838
-
839
- # Xử lý real-time với VOSK
840
- result = self.vosk_asr.process_audio_chunk(speech_audio, sample_rate)
841
- self._handle_vosk_result(result)
842
-
843
- except Exception as e:
844
- print(f"❌ Lỗi trong speech detection: {e}")
845
-
846
- def _handle_vosk_result(self, result: Dict[str, Any]):
847
- """Xử lý kết quả từ VOSK - FIXED VERSION"""
848
- try:
849
- # Xử lý kết quả partial (real-time)
850
- if result['partial'] and len(result['partial']) > 1:
851
- self.partial_transcription = result['partial']
852
- print(f"🎯 VOSK Partial: '{result['partial']}'")
853
-
854
- # Gửi partial result real-time
855
- if self.current_callback:
856
- self.current_callback({
857
- 'transcription': result['partial'],
858
- 'response': "",
859
- 'tts_audio': None,
860
- 'status': 'partial'
861
- })
862
-
863
- # Xử lý kết quả final
864
- if result['is_final'] and result['text'] and len(result['text']) > 1:
865
- print(f"✅ VOSK Final: '{result['text']}'")
866
-
867
- # Đưa vào queue để xử lý phản hồi AI
868
- try:
869
- self.response_queue.put({
870
- 'transcription': result['text'],
871
- 'timestamp': time.time()
872
- }, timeout=0.5)
873
- print(f"📦 Đã đưa vào queue: '{result['text']}'")
874
-
875
- # Cập nhật UI ngay lập tức
876
- if self.current_callback:
877
- self.current_callback({
878
- 'transcription': result['text'],
879
- 'response': "Đang xử lý...",
880
- 'tts_audio': None,
881
- 'status': 'processing'
882
- })
883
-
884
- except queue.Full:
885
- print("⚠️ Queue đầy, bỏ qua transcription")
886
-
887
- # Reset VOSK stream cho lần tiếp theo
888
- self.vosk_asr.start_stream()
889
-
890
- except Exception as e:
891
- print(f"❌ Lỗi xử lý VOSK result: {e}")
892
-
893
- def _process_response_worker(self):
894
- """Worker xử lý phản hồi AI từ queue"""
895
- while self.processing_active:
896
- try:
897
- # Lấy item từ queue với timeout
898
- item = self.response_queue.get(timeout=1.0)
899
- if item is None: # Tín hiệu dừng
900
- break
901
-
902
- transcription = item['transcription']
903
- start_time = item['timestamp']
904
-
905
- print(f"🤖 Processing AI response for: '{transcription}'")
906
-
907
- # Tạo phản hồi AI
908
- response = self._generate_ai_response_optimized(transcription)
909
- tts_audio_path = self._text_to_speech_optimized(response)
910
-
911
- # Gửi kết quả về callback
912
- if self.current_callback:
913
- self.current_callback({
914
- 'transcription': transcription,
915
- 'response': response,
916
- 'tts_audio': tts_audio_path,
917
- 'status': 'completed'
918
- })
919
-
920
- # Đánh dấu task hoàn thành
921
- self.response_queue.task_done()
922
-
923
- except queue.Empty:
924
- continue
925
- except Exception as e:
926
- print(f"❌ Lỗi trong response worker: {e}")
927
- if self.current_callback:
928
- self.current_callback({
929
- 'transcription': "Lỗi xử lý",
930
- 'response': f"Xin lỗi, có lỗi xảy ra: {str(e)}",
931
- 'tts_audio': None,
932
- 'status': 'error'
933
- })
934
-
935
- def _realtime_processing_worker(self):
936
- """Worker xử lý real-time để theo dõi timeout"""
937
- while self.processing_active:
938
- try:
939
- current_time = time.time()
940
- silence_duration = current_time - self.last_voice_time
941
-
942
- # Xử lý timeout nếu im lặng quá lâu và có partial text
943
- if (silence_duration > self.silence_timeout and
944
- self.partial_transcription and
945
- len(self.partial_transcription) > 2):
946
-
947
- print(f"⏰ Silence timeout, xử lý: '{self.partial_transcription}'")
948
-
949
- # Xử lý partial text như final
950
- try:
951
- self.response_queue.put({
952
- 'transcription': self.partial_transcription,
953
- 'timestamp': time.time()
954
- }, timeout=0.5)
955
- except queue.Full:
956
- print("⚠️ Queue đầy, bỏ qua timeout transcription")
957
-
958
- # Reset
959
- self.partial_transcription = ""
960
- self.vosk_asr.start_stream()
961
-
962
- time.sleep(0.1) # Giảm CPU usage
963
-
964
- except Exception as e:
965
- print(f"❌ Lỗi real-time worker: {e}")
966
- time.sleep(0.5)
967
-
968
- def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
969
- """Xử lý audio streaming manual mode với VOSK - FIXED VOLUME VERSION"""
970
- if not audio_data:
971
- return self._create_error_response("❌ Không có dữ liệu âm thanh")
972
-
973
- try:
974
- sample_rate, audio_array = audio_data
975
-
976
- print(f"🎤 Manual audio: {len(audio_array)} samples, {sample_rate}Hz, Max: {np.max(audio_array)}")
977
-
978
- # FIXED: Tăng cường âm lượng trước khi xử lý
979
- audio_array = self._boost_input_volume(audio_array)
980
-
981
- # Kiểm tra âm lượng với ngưỡng thấp hơn
982
- if isinstance(audio_array, np.ndarray):
983
- if audio_array.dtype in [np.float32, np.float64]:
984
- audio_rms = np.sqrt(np.mean(audio_array**2))
985
- else:
986
- audio_rms = np.sqrt(np.mean(audio_array.astype(np.float32)**2)) / 32768.0
987
-
988
- print(f"📊 Manual audio RMS: {audio_rms:.6f}, Max: {np.max(audio_array)}")
989
-
990
- # FIXED: Giảm ngưỡng âm lượng
991
- if audio_rms < 0.001: # Giảm từ 0.01 xuống 0.001
992
- return {
993
- 'transcription': f"Âm thanh quá nhỏ (RMS: {audio_rms:.6f}), hãy nói to hơn hoặc điều chỉnh microphone",
994
- 'response': "",
995
- 'tts_audio': None,
996
- 'status': 'listening'
997
- }
998
-
999
- # Khởi động VOSK stream tạm thời
1000
- if not self.vosk_asr.start_stream():
1001
- return self._create_error_response("❌ Không thể khởi động VOSK")
1002
-
1003
- # Xử lý audio với VOSK
1004
- result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
1005
-
1006
- if result['is_final'] and result['text'] and len(result['text']) > 1:
1007
- transcription = result['text']
1008
- print(f"📝 Manual Transcription: '{transcription}'")
1009
-
1010
- # Tạo phản hồi AI
1011
- response = self._generate_ai_response_optimized(transcription)
1012
- tts_audio_path = self._text_to_speech_optimized(response)
1013
-
1014
- return {
1015
- 'transcription': transcription,
1016
- 'response': response,
1017
- 'tts_audio': tts_audio_path,
1018
- 'status': 'completed'
1019
- }
1020
- elif result['partial']:
1021
- return {
1022
- 'transcription': result['partial'],
1023
- 'response': "",
1024
- 'tts_audio': None,
1025
- 'status': 'listening'
1026
- }
1027
- else:
1028
- return {
1029
- 'transcription': "Đang nghe... Hãy nói rõ hơn và gần microphone",
1030
- 'response': "",
1031
- 'tts_audio': None,
1032
- 'status': 'listening'
1033
- }
1034
-
1035
- except Exception as e:
1036
- print(f"❌ Lỗi xử lý streaming audio: {e}")
1037
- traceback.print_exc()
1038
- return self._create_error_response(f"❌ Lỗi: {str(e)}")
1039
-
1040
- def _boost_input_volume(self, audio_array: np.ndarray, boost_factor: float = 10.0) -> np.ndarray:
1041
- """Tăng cường âm lượng input audio"""
1042
- try:
1043
- if audio_array.dtype in [np.float32, np.float64]:
1044
- # Audio đã ở dạng float
1045
- boosted = audio_array * boost_factor
1046
- boosted = np.clip(boosted, -1.0, 1.0)
1047
- else:
1048
- # Audio ở dạng int
1049
- boosted = audio_array.astype(np.float32) * boost_factor
1050
- max_val = np.iinfo(audio_array.dtype).max
1051
- boosted = np.clip(boosted, -max_val, max_val).astype(audio_array.dtype)
1052
-
1053
- print(f"🔊 Input volume boosted: {boost_factor}x")
1054
- return boosted
1055
-
1056
- except Exception as e:
1057
- print(f"⚠️ Lỗi boost input volume: {e}")
1058
- return audio_array
1059
-
1060
- def _generate_ai_response_optimized(self, transcription: str) -> str:
1061
- """Tạo phản hồi AI tối ưu hóa"""
1062
- try:
1063
- # Thêm vào lịch sử hội thoại
1064
- self.conversation_history.append({"role": "user", "content": transcription})
1065
-
1066
- # Giới hạn lịch sử hội thoại
1067
- if len(self.conversation_history) > 10:
1068
- self.conversation_history = self.conversation_history[-10:]
1069
-
1070
- # Tạo prompt
1071
- messages = [
1072
- {"role": "system", "content": "Bạn là trợ lý AI hữu ích. Hãy trả lời ngắn gọn, tự nhiên bằng tiếng Việt."},
1073
- *self.conversation_history
1074
- ]
1075
-
1076
- # Gọi Groq API
1077
- response = self.client.chat.completions.create(
1078
- model="llama-3.1-8b-instant",
1079
- messages=messages,
1080
- max_tokens=150,
1081
- temperature=0.7
1082
- )
1083
-
1084
- ai_response = response.choices[0].message.content.strip()
1085
-
1086
- # Thêm vào lịch sử
1087
- self.conversation_history.append({"role": "assistant", "content": ai_response})
1088
-
1089
- return ai_response
1090
-
1091
- except Exception as e:
1092
- print(f"❌ Lỗi tạo phản hồi AI: {e}")
1093
- return "Xin lỗi, tôi không thể xử lý yêu cầu ngay lúc này."
1094
-
1095
- def _text_to_speech_optimized(self, text: str) -> Optional[str]:
1096
- """Chuyển văn bản thành giọng nói tối ưu hóa"""
1097
- try:
1098
- if not text or len(text.strip()) == 0:
1099
- return None
1100
-
1101
- # Sử dụng TTS service
1102
- audio_path = self.tts_service.text_to_speech(
1103
- text=text,
1104
- language='vi',
1105
- speed=1.0
1106
- )
1107
-
1108
- return audio_path
1109
-
1110
- except Exception as e:
1111
- print(f"❌ Lỗi TTS: {e}")
1112
- return None
1113
-
1114
- def _create_error_response(self, message: str) -> Dict[str, Any]:
1115
- """Tạo response lỗi chuẩn"""
1116
- return {
1117
- 'transcription': message,
1118
- 'response': "Vui lòng thử lại",
1119
- 'tts_audio': None,
1120
- 'status': 'error'
1121
- }
1122
-
1123
- def get_conversation_state(self) -> dict:
1124
- """Lấy trạng thái hội thoại"""
1125
- return {
1126
- 'is_listening': self.is_listening,
1127
- 'is_processing': self.is_processing,
1128
- 'history_length': len(self.conversation_history),
1129
- 'current_transcription': self.current_transcription,
1130
- 'partial_transcription': self.partial_transcription,
1131
- 'queue_size': self.response_queue.qsize(),
1132
- 'worker_threads': len([t for t in self.processing_threads if t.is_alive()]),
1133
- 'vosk_active': self.vosk_stream_active,
1134
- 'last_voice_time': time.strftime("%H:%M:%S", time.localtime(self.last_voice_time)),
1135
- 'last_update': time.strftime("%H:%M:%S")
1136
- }
1137
-
1138
- def clear_conversation(self):
1139
- """Xóa lịch sử hội thoại"""
1140
- self.conversation_history = []
1141
- self.current_transcription = ""
1142
- self.partial_transcription = ""
1143
- print("🗑️ Đã xóa lịch sử hội thoại")
1144
-
1145
- def get_latency_stats(self) -> dict:
1146
- """Lấy thống kê latency"""
1147
- stats = {}
1148
- for component, latencies in self.latency_metrics.items():
1149
- if latencies:
1150
- recent_latencies = latencies[-10:]
1151
- stats[component] = {
1152
- 'avg': sum(latencies) / len(latencies),
1153
- 'min': min(latencies),
1154
- 'max': max(latencies),
1155
- 'count': len(latencies),
1156
- 'recent_avg': sum(recent_latencies) / len(recent_latencies),
1157
- 'recent_min': min(recent_latencies),
1158
- 'recent_max': max(recent_latencies)
1159
- }
1160
- else:
1161
- stats[component] = {
1162
- 'avg': 0, 'min': 0, 'max': 0, 'count': 0,
1163
- 'recent_avg': 0, 'recent_min': 0, 'recent_max': 0
1164
- }
1165
- return stats
 
1
 
2
+ import io
3
+ import numpy as np
4
+ import soundfile as sf
5
+ import time
6
+ import traceback
7
+ import threading
8
+ import queue
9
+ import json
10
+ import os
11
+ from vosk import Model, KaldiRecognizer
12
+ from groq import Groq
13
+ from typing import Optional, Dict, Any, Callable
14
+ from config.settings import settings
15
+ from core.rag_system import EnhancedRAGSystem
16
+ from core.tts_service import EnhancedTTSService
17
+ from core.silero_vad import SileroVAD
18
+ class VoskStreamingASR:
19
+ def __init__(self, model_path: str = None):
20
+ self.model = None
21
+ self.recognizer = None
22
+ self.sample_rate = 16000
23
+ self.is_streaming = False
24
+
25
+ # Buffer để tích luỹ audio - QUAN TRỌNG
26
+ self.audio_buffer = []
27
+ self.buffer_duration = 2.0 # tích luỹ 2 giây audio
28
+ self.min_samples_for_recognition = 32000 # ít nhất 2 giây audio
29
+
30
+ if model_path is None:
31
+ model_path = self._download_vosk_model()
32
+
33
+ if model_path and os.path.exists(model_path):
34
+ print(f"🔄 Đang tải VOSK model từ: {model_path}")
35
+ try:
36
+ self.model = Model(model_path)
37
+ self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
38
+ self.recognizer.SetWords(True)
39
+ print("✅ Đã tải VOSK model thành công")
40
+ except Exception as e:
41
+ print(f"❌ Lỗi khởi tạo VOSK model: {e}")
42
+ else:
43
+ print(f"❌ Không tìm thấy VOSK model tại: {model_path}")
44
+
45
+ def start_stream(self):
46
+ """Bắt đầu stream mới"""
47
+ if self.model is None:
48
+ return False
49
+
50
+ try:
51
+ self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
52
+ self.recognizer.SetWords(True)
53
+ self.is_streaming = True
54
+ self.audio_buffer = [] # reset buffer
55
+ print("🎤 Đã khởi động VOSK stream")
56
+ return True
57
+ except Exception as e:
58
+ print(f"❌ Lỗi khởi động VOSK stream: {e}")
59
+ return False
60
+
61
+ def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
62
+ """Xử lý audio chunk - SIMPLIFIED VERSION"""
63
+ if self.recognizer is None or not self.is_streaming:
64
+ return {"text": "", "partial": "", "is_final": False}
65
+
66
+ try:
67
+ # Đơn giản hoá: luôn xử lý, không check âm lượng
68
+ if sample_rate and sample_rate != self.sample_rate:
69
+ audio_chunk = self._resample_audio(audio_chunk, sample_rate, self.sample_rate)
70
+
71
+ # Đảm bảo là int16
72
+ if audio_chunk.dtype != np.int16:
73
+ audio_chunk = audio_chunk.astype(np.int16)
74
+
75
+ # THÊM VÀO BUFFER - QUAN TRỌNG
76
+ self.audio_buffer.extend(audio_chunk)
77
+
78
+ # Chỉ xử lý khi có đủ audio
79
+ if len(self.audio_buffer) < 16000: # ít nhất 1 giây
80
+ return {"text": "", "partial": "Đang nghe...", "is_final": False}
81
+
82
+ # Lấy audio từ buffer để xử lý
83
+ process_audio = np.array(self.audio_buffer[-32000:], dtype=np.int16) # lấy 2 giây gần nhất
84
+
85
+ # Chuyển sang bytes
86
+ audio_bytes = process_audio.tobytes()
87
+
88
+ # Xử lý với VOSK - GỬI NHIỀU LẦN
89
+ for i in range(0, len(audio_bytes), 8000): # gửi từng chunk nhỏ
90
+ chunk = audio_bytes[i:i+8000]
91
+ if len(chunk) > 0:
92
+ if self.recognizer.AcceptWaveform(chunk):
93
+ result_json = self.recognizer.Result()
94
+ result = json.loads(result_json)
95
+ text = result.get('text', '').strip()
96
+ if text:
97
+ print(f"✅ VOSK Final: '{text}'")
98
+ # Reset buffer sau khi có kết quả
99
+ self.audio_buffer = []
100
+ return {"text": text, "partial": "", "is_final": True}
101
+
102
+ # Kiểm tra partial result
103
+ partial_json = self.recognizer.PartialResult()
104
+ partial_result = json.loads(partial_json)
105
+ partial_text = partial_result.get('partial', '').strip()
106
+
107
+ if partial_text:
108
+ print(f"🎯 VOSK Partial: '{partial_text}'")
109
+ return {"text": "", "partial": partial_text, "is_final": False}
110
+ else:
111
+ return {"text": "", "partial": "Đang xử lý âm thanh...", "is_final": False}
112
+
113
+ except Exception as e:
114
+ print(f"❌ Lỗi VOSK processing: {e}")
115
+
116
+ return {"text": "", "partial": "", "is_final": False}
117
+
118
+ class StreamingVoiceService:
119
+ def __init__(self, groq_client: Groq, rag_system, tts_service):
120
+ self.client = groq_client
121
+ self.rag_system = rag_system
122
+ self.tts_service = tts_service
123
+
124
+ # Khởi tạo VOSK ASR - ĐƠN GIẢN
125
+ print("🔄 Đang khởi tạo VOSK ASR...")
126
+ self.vosk_asr = VoskStreamingASR()
127
+ self.is_listening = False
128
+ self.current_callback = None
129
+
130
+ def start_listening(self, speech_callback: Callable) -> bool:
131
+ """Bắt đầu lắng nghe - ĐƠN GIẢN"""
132
+ if self.is_listening:
133
+ return False
134
+
135
+ self.current_callback = speech_callback
136
+
137
+ if self.vosk_asr.model is None:
138
+ return False
139
+
140
+ if not self.vosk_asr.start_stream():
141
+ return False
142
+
143
+ self.is_listening = True
144
+ print("🎙️ Đã bắt đầu lắng nghe")
145
+ return True
146
+
147
+ def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
148
+ """Xử lý audio streaming - ĐƠN GIẢN VÀ HIỆU QUẢ"""
149
+ if not audio_data:
150
+ return {"transcription": "Không có âm thanh", "response": "", "tts_audio": None, "status": "error"}
151
+
152
+ try:
153
+ sample_rate, audio_array = audio_data
154
+
155
+ # Đảm bảo VOSK stream đang chạy
156
+ if not self.vosk_asr.is_streaming:
157
+ self.vosk_asr.start_stream()
158
+
159
+ # Xử lý với VOSK
160
+ result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
161
+
162
+ # LUÔN trả về kết quả partial để hiển thị real-time
163
+ if result['partial']:
164
+ return {
165
+ 'transcription': result['partial'],
166
+ 'response': "",
167
+ 'tts_audio': None,
168
+ 'status': 'listening'
169
+ }
170
+ elif result['is_final'] and result['text']:
171
+ # Có kết quả cuối - tạo phản hồi AI
172
+ response = self._generate_ai_response(result['text'])
173
+ return {
174
+ 'transcription': result['text'],
175
+ 'response': response,
176
+ 'tts_audio': None, # có thể thêm TTS sau
177
+ 'status': 'completed'
178
+ }
179
+ else:
180
+ return {
181
+ 'transcription': "🎤 Đang nghe... nói tiếp đi",
182
+ 'response': "",
183
+ 'tts_audio': None,
184
+ 'status': 'listening'
185
+ }
186
+
187
+ except Exception as e:
188
+ print(f"❌ Lỗi: {e}")
189
+ return {"transcription": f"Lỗi: {e}", "response": "", "tts_audio": None, "status": "error"}
190
+
191
+ def _generate_ai_response(self, transcription: str) -> str:
192
+ """Tạo phản hồi AI đơn giản"""
193
+ try:
194
+ messages = [
195
+ {"role": "system", "content": "Bạn là trợ lý AI. Trả lời ngắn gọn bằng tiếng Việt."},
196
+ {"role": "user", "content": transcription}
197
+ ]
198
+
199
+ response = self.client.chat.completions.create(
200
+ model="llama-3.1-8b-instant",
201
+ messages=messages,
202
+ max_tokens=100,
203
+ temperature=0.7
204
+ )
205
+
206
+ return response.choices[0].message.content.strip()
207
+
208
+ except Exception as e:
209
+ return "Xin lỗi, tôi không thể trả lời ngay lúc này."
210
+
211
+ def stop_listening(self):
212
+ """Dừng lắng nghe"""
213
+ self.is_listening = False
214
+ if self.vosk_asr:
215
+ self.vosk_asr.stop_stream()
216
+ print("🛑 Đã dừng lắng nghe")
217
  # import io
218
  # import numpy as np
219
  # import soundfile as sf
 
306
  # return False
307
 
308
  # def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
309
+ # """Xử lý audio chunk và trả về kết quả - FIXED VOLUME VERSION"""
310
  # if self.recognizer is None or not self.is_streaming:
311
  # return {"text": "", "partial": "", "is_final": False}
312
 
 
326
  # else:
327
  # audio_chunk = audio_chunk.astype(np.int16)
328
 
329
+ # # FIXED: Tăng cường âm lượng trước khi kiểm tra
330
+ # audio_chunk = self._boost_audio_volume(audio_chunk)
331
+
332
+ # # Kiểm tra âm lượng - GIẢM ngưỡng xuống
333
  # audio_rms = np.sqrt(np.mean(audio_chunk.astype(np.float32)**2)) / 32767.0
334
+ # print(f"📊 Audio RMS: {audio_rms:.4f}, Max: {np.max(audio_chunk)}")
335
 
336
+ # # FIXED: Giảm ngưỡng âm lượng t�� 0.01 xuống 0.001
337
+ # if audio_rms < 0.001: # Giảm ngưỡng 10 lần
338
+ # print(f"⚠️ Âm lượng quá thấp (RMS: {audio_rms:.6f}), bỏ qua")
339
  # return {"text": "", "partial": "", "is_final": False}
340
 
341
  # # Chuyển đổi sang bytes
 
365
 
366
  # return {"text": "", "partial": "", "is_final": False}
367
 
368
+ # def _boost_audio_volume(self, audio_chunk: np.ndarray, boost_factor: float = 5.0) -> np.ndarray:
369
+ # """Tăng cường âm lượng audio"""
370
+ # try:
371
+ # # Chuyển sang float để xử lý
372
+ # audio_float = audio_chunk.astype(np.float32) / 32768.0
373
+
374
+ # # Tăng âm lượng
375
+ # boosted_audio = audio_float * boost_factor
376
+
377
+ # # Ngăn chặn clipping
378
+ # boosted_audio = np.clip(boosted_audio, -1.0, 1.0)
379
+
380
+ # # Chuyển lại sang int16
381
+ # boosted_audio_int16 = (boosted_audio * 32767).astype(np.int16)
382
+
383
+ # print(f"🔊 Volume boosted: {boost_factor}x, New max: {np.max(boosted_audio_int16)}")
384
+ # return boosted_audio_int16
385
+
386
+ # except Exception as e:
387
+ # print(f"⚠️ Lỗi boost volume: {e}")
388
+ # return audio_chunk
389
  # def stop_stream(self) -> str:
390
  # """Kết thúc stream và lấy kết quả cuối"""
391
  # if self.recognizer:
 
420
  # self.rag_system = rag_system
421
  # self.tts_service = tts_service
422
 
423
+ # # Khởi tạo VOSK ASR
424
  # print("🔄 Đang khởi tạo VOSK ASR...")
425
  # self.vosk_asr = VoskStreamingASR()
426
 
 
440
  # self.processing_threads = []
441
  # self.max_workers = 2
442
 
443
+ # # Streaming state - FIXED: Thêm các biến state mới
444
  # self.vosk_stream_active = False
445
  # self.last_voice_time = 0
446
+ # self.silence_timeout = 3.0
447
 
448
  # # Audio buffer để cải thiện nhận diện
449
  # self.audio_buffer = []
450
+ # self.buffer_duration = 1.0
451
+ # self.max_buffer_samples = 16000
452
+
453
+ # # Real-time processing
454
+ # self.realtime_buffer = queue.Queue()
455
+ # self.processing_active = False
456
 
457
  # # Latency tracking
458
  # self.latency_metrics = {
 
496
  # self.vosk_stream_active = True
497
  # self.last_voice_time = time.time()
498
  # self.audio_buffer = []
499
+ # self.processing_active = True
500
 
501
  # # Khởi động worker threads
502
  # if not self.processing_threads:
 
509
  # thread.start()
510
  # self.processing_threads.append(thread)
511
 
512
+ # # Bắt đầu real-time processing thread
513
+ # threading.Thread(target=self._realtime_processing_worker, daemon=True).start()
514
 
515
  # print("🎙️ Đã bắt đầu lắng nghe với VOSK ASR streaming")
516
 
 
527
 
528
  # return False
529
 
530
+ # def stop_listening(self):
531
+ # """Dừng lắng nghe"""
532
+ # self.is_listening = False
533
+ # self.vosk_stream_active = False
534
+ # self.processing_active = False
535
+ # self.vad_processor.stop_stream()
536
+ # print("🛑 Đã dừng lắng nghe")
537
+
538
+ # def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
539
+ # """Callback khi VAD phát hiện speech - FIXED VERSION"""
540
+ # if not self.vosk_stream_active or not self.is_listening:
541
+ # return
542
+
543
+ # try:
544
+ # # Cập nhật thời gian có giọng nói
545
+ # self.last_voice_time = time.time()
546
+
547
+ # print(f"🎯 VAD detected: {len(speech_audio)} samples, {sample_rate}Hz")
548
+
549
+ # # Xử lý real-time với VOSK
550
+ # result = self.vosk_asr.process_audio_chunk(speech_audio, sample_rate)
551
+ # self._handle_vosk_result(result)
552
+
553
+ # except Exception as e:
554
+ # print(f"❌ Lỗi trong speech detection: {e}")
555
+
556
+ # def _handle_vosk_result(self, result: Dict[str, Any]):
557
+ # """Xử lý kết quả từ VOSK - FIXED VERSION"""
558
+ # try:
559
+ # # Xử lý kết quả partial (real-time)
560
+ # if result['partial'] and len(result['partial']) > 1:
561
+ # self.partial_transcription = result['partial']
562
+ # print(f"🎯 VOSK Partial: '{result['partial']}'")
563
+
564
+ # # Gửi partial result real-time
565
+ # if self.current_callback:
566
+ # self.current_callback({
567
+ # 'transcription': result['partial'],
568
+ # 'response': "",
569
+ # 'tts_audio': None,
570
+ # 'status': 'partial'
571
+ # })
572
+
573
+ # # Xử lý kết quả final
574
+ # if result['is_final'] and result['text'] and len(result['text']) > 1:
575
+ # print(f"✅ VOSK Final: '{result['text']}'")
576
+
577
+ # # Đưa vào queue để xử lý phản hồi AI
578
+ # try:
579
+ # self.response_queue.put({
580
+ # 'transcription': result['text'],
581
+ # 'timestamp': time.time()
582
+ # }, timeout=0.5)
583
+ # print(f"📦 Đã đưa vào queue: '{result['text']}'")
584
+
585
+ # # Cập nhật UI ngay lập tức
586
+ # if self.current_callback:
587
+ # self.current_callback({
588
+ # 'transcription': result['text'],
589
+ # 'response': "Đang xử lý...",
590
+ # 'tts_audio': None,
591
+ # 'status': 'processing'
592
+ # })
593
+
594
+ # except queue.Full:
595
+ # print("⚠️ Queue đầy, bỏ qua transcription")
596
+
597
+ # # Reset VOSK stream cho lần tiếp theo
598
+ # self.vosk_asr.start_stream()
599
+
600
+ # except Exception as e:
601
+ # print(f"❌ Lỗi xử lý VOSK result: {e}")
602
+
603
+ # def _process_response_worker(self):
604
+ # """Worker xử lý phản hồi AI từ queue"""
605
+ # while self.processing_active:
606
+ # try:
607
+ # # Lấy item từ queue với timeout
608
+ # item = self.response_queue.get(timeout=1.0)
609
+ # if item is None: # Tín hiệu dừng
610
+ # break
611
+
612
+ # transcription = item['transcription']
613
+ # start_time = item['timestamp']
614
+
615
+ # print(f"🤖 Processing AI response for: '{transcription}'")
616
+
617
+ # # Tạo phản hồi AI
618
+ # response = self._generate_ai_response_optimized(transcription)
619
+ # tts_audio_path = self._text_to_speech_optimized(response)
620
+
621
+ # # Gửi kết quả về callback
622
+ # if self.current_callback:
623
+ # self.current_callback({
624
+ # 'transcription': transcription,
625
+ # 'response': response,
626
+ # 'tts_audio': tts_audio_path,
627
+ # 'status': 'completed'
628
+ # })
629
+
630
+ # # Đánh dấu task hoàn thành
631
+ # self.response_queue.task_done()
632
+
633
+ # except queue.Empty:
634
+ # continue
635
+ # except Exception as e:
636
+ # print(f"❌ Lỗi trong response worker: {e}")
637
+ # if self.current_callback:
638
+ # self.current_callback({
639
+ # 'transcription': "Lỗi xử lý",
640
+ # 'response': f"Xin lỗi, có lỗi xảy ra: {str(e)}",
641
+ # 'tts_audio': None,
642
+ # 'status': 'error'
643
+ # })
644
+
645
+ # def _realtime_processing_worker(self):
646
+ # """Worker xử lý real-time để theo dõi timeout"""
647
+ # while self.processing_active:
648
  # try:
649
  # current_time = time.time()
650
  # silence_duration = current_time - self.last_voice_time
651
 
652
+ # # Xử lý timeout nếu im lặng quá lâu và partial text
653
+ # if (silence_duration > self.silence_timeout and
654
+ # self.partial_transcription and
655
+ # len(self.partial_transcription) > 2):
656
+
 
 
 
 
 
657
  # print(f"⏰ Silence timeout, xử lý: '{self.partial_transcription}'")
658
+
659
+ # # Xử lý partial text như final
660
+ # try:
661
+ # self.response_queue.put({
662
+ # 'transcription': self.partial_transcription,
663
+ # 'timestamp': time.time()
664
+ # }, timeout=0.5)
665
+ # except queue.Full:
666
+ # print("⚠️ Queue đầy, bỏ qua timeout transcription")
667
+
668
+ # # Reset
669
  # self.partial_transcription = ""
670
  # self.vosk_asr.start_stream()
671
 
672
+ # time.sleep(0.1) # Giảm CPU usage
673
 
674
  # except Exception as e:
675
+ # print(f"❌ Lỗi real-time worker: {e}")
676
+ # time.sleep(0.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
 
678
  # def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
679
+ # """Xử lý audio streaming manual mode với VOSK - FIXED VOLUME VERSION"""
680
  # if not audio_data:
681
  # return self._create_error_response("❌ Không có dữ liệu âm thanh")
682
 
683
  # try:
684
  # sample_rate, audio_array = audio_data
685
 
686
+ # print(f"🎤 Manual audio: {len(audio_array)} samples, {sample_rate}Hz, Max: {np.max(audio_array)}")
687
+
688
+ # # FIXED: Tăng cường âm lượng trước khi xử lý
689
+ # audio_array = self._boost_input_volume(audio_array)
690
 
691
+ # # Kiểm tra âm lượng với ngưỡng thấp hơn
692
  # if isinstance(audio_array, np.ndarray):
693
  # if audio_array.dtype in [np.float32, np.float64]:
694
  # audio_rms = np.sqrt(np.mean(audio_array**2))
695
+ # else:
696
+ # audio_rms = np.sqrt(np.mean(audio_array.astype(np.float32)**2)) / 32768.0
697
 
698
+ # print(f"📊 Manual audio RMS: {audio_rms:.6f}, Max: {np.max(audio_array)}")
699
+
700
+ # # FIXED: Giảm ngưỡng âm lượng
701
+ # if audio_rms < 0.001: # Giảm từ 0.01 xuống 0.001
702
+ # return {
703
+ # 'transcription': f"Âm thanh quá nhỏ (RMS: {audio_rms:.6f}), hãy nói to hơn hoặc điều chỉnh microphone",
704
+ # 'response': "",
705
+ # 'tts_audio': None,
706
+ # 'status': 'listening'
707
+ # }
708
 
709
  # # Khởi động VOSK stream tạm thời
710
  # if not self.vosk_asr.start_stream():
 
736
  # }
737
  # else:
738
  # return {
739
+ # 'transcription': "Đang nghe... Hãy nói rõ hơn và gần microphone",
740
  # 'response': "",
741
  # 'tts_audio': None,
742
  # 'status': 'listening'
 
746
  # print(f"❌ Lỗi xử lý streaming audio: {e}")
747
  # traceback.print_exc()
748
  # return self._create_error_response(f"❌ Lỗi: {str(e)}")
749
+
750
+ # def _boost_input_volume(self, audio_array: np.ndarray, boost_factor: float = 10.0) -> np.ndarray:
751
+ # """Tăng cường âm lượng input audio"""
752
+ # try:
753
+ # if audio_array.dtype in [np.float32, np.float64]:
754
+ # # Audio đã ở dạng float
755
+ # boosted = audio_array * boost_factor
756
+ # boosted = np.clip(boosted, -1.0, 1.0)
757
+ # else:
758
+ # # Audio ở dạng int
759
+ # boosted = audio_array.astype(np.float32) * boost_factor
760
+ # max_val = np.iinfo(audio_array.dtype).max
761
+ # boosted = np.clip(boosted, -max_val, max_val).astype(audio_array.dtype)
762
+
763
+ # print(f"🔊 Input volume boosted: {boost_factor}x")
764
+ # return boosted
765
+
766
+ # except Exception as e:
767
+ # print(f"⚠️ Lỗi boost input volume: {e}")
768
+ # return audio_array
769
+
770
+ # def _generate_ai_response_optimized(self, transcription: str) -> str:
771
+ # """Tạo phản hồi AI tối ưu hóa"""
772
+ # try:
773
+ # # Thêm vào lịch sử hội thoại
774
+ # self.conversation_history.append({"role": "user", "content": transcription})
775
+
776
+ # # Giới hạn lịch sử hội thoại
777
+ # if len(self.conversation_history) > 10:
778
+ # self.conversation_history = self.conversation_history[-10:]
779
+
780
+ # # Tạo prompt
781
+ # messages = [
782
+ # {"role": "system", "content": "Bạn là trợ lý AI hữu ích. Hãy trả lời ngắn gọn, tự nhiên bằng tiếng Việt."},
783
+ # *self.conversation_history
784
+ # ]
785
+
786
+ # # Gọi Groq API
787
+ # response = self.client.chat.completions.create(
788
+ # model="llama-3.1-8b-instant",
789
+ # messages=messages,
790
+ # max_tokens=150,
791
+ # temperature=0.7
792
+ # )
793
+
794
+ # ai_response = response.choices[0].message.content.strip()
795
+
796
+ # # Thêm vào lịch sử
797
+ # self.conversation_history.append({"role": "assistant", "content": ai_response})
798
+
799
+ # return ai_response
800
+
801
+ # except Exception as e:
802
+ # print(f"❌ Lỗi tạo phản hồi AI: {e}")
803
+ # return "Xin lỗi, tôi không thể xử lý yêu cầu ngay lúc này."
804
+
805
+ # def _text_to_speech_optimized(self, text: str) -> Optional[str]:
806
+ # """Chuyển văn bản thành giọng nói tối ưu hóa"""
807
+ # try:
808
+ # if not text or len(text.strip()) == 0:
809
+ # return None
810
+
811
+ # # Sử dụng TTS service
812
+ # audio_path = self.tts_service.text_to_speech(
813
+ # text=text,
814
+ # language='vi',
815
+ # speed=1.0
816
+ # )
817
+
818
+ # return audio_path
819
+
820
+ # except Exception as e:
821
+ # print(f"❌ Lỗi TTS: {e}")
822
+ # return None
823
 
824
  # def _create_error_response(self, message: str) -> Dict[str, Any]:
825
  # """Tạo response lỗi chuẩn"""
 
830
  # 'status': 'error'
831
  # }
832
 
833
+ # def get_conversation_state(self) -> dict:
834
+ # """Lấy trạng thái hội thoại"""
835
+ # return {
836
+ # 'is_listening': self.is_listening,
837
+ # 'is_processing': self.is_processing,
838
+ # 'history_length': len(self.conversation_history),
839
+ # 'current_transcription': self.current_transcription,
840
+ # 'partial_transcription': self.partial_transcription,
841
+ # 'queue_size': self.response_queue.qsize(),
842
+ # 'worker_threads': len([t for t in self.processing_threads if t.is_alive()]),
843
+ # 'vosk_active': self.vosk_stream_active,
844
+ # 'last_voice_time': time.strftime("%H:%M:%S", time.localtime(self.last_voice_time)),
845
+ # 'last_update': time.strftime("%H:%M:%S")
846
+ # }
847
 
848
+ # def clear_conversation(self):
849
+ # """Xóa lịch sử hội thoại"""
850
+ # self.conversation_history = []
851
+ # self.current_transcription = ""
852
+ # self.partial_transcription = ""
853
+ # print("🗑️ Đã xóa lịch sử hội thoại")
 
 
854
 
855
  # def get_latency_stats(self) -> dict:
856
  # """Lấy thống kê latency"""
 
872
  # 'avg': 0, 'min': 0, 'max': 0, 'count': 0,
873
  # 'recent_avg': 0, 'recent_min': 0, 'recent_max': 0
874
  # }
875
+ # return stats