examples.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. """
  2. Examples and testing framework for Voice Recognition system.
  3. This module provides comprehensive examples, testing utilities, and mock
  4. components for developing and testing the voice recognition system.
  5. """
  6. import asyncio
  7. import logging
  8. import time
  9. import threading
  10. from typing import Dict, List, Optional, Any, Callable, Tuple
  11. from dataclasses import dataclass
  12. import numpy as np
  13. import torch
  14. from .config import VoiceRecognitionConfig, create_default_config, ModelArchitectureType
  15. from .detector import VoiceRecognitionDetector, SpeakerIdentificationResult, AudioChunk
  16. # Configure logging
  17. logger = logging.getLogger(__name__)
  18. class VRMockEventHandler:
  19. """Mock event handler for testing voice recognition system."""
  20. def __init__(self):
  21. """Initialize mock event handler."""
  22. self.events = []
  23. self.event_callbacks = {}
  24. self._lock = threading.Lock()
  25. def trigger(self, event_name: str, event_data: Dict[str, Any]):
  26. """
  27. Trigger an event.
  28. Args:
  29. event_name: Name of the event
  30. event_data: Event data dictionary
  31. """
  32. with self._lock:
  33. event_entry = {
  34. 'name': event_name,
  35. 'data': event_data,
  36. 'timestamp': time.time()
  37. }
  38. self.events.append(event_entry)
  39. # Call registered callbacks
  40. if event_name in self.event_callbacks:
  41. for callback in self.event_callbacks[event_name]:
  42. try:
  43. callback(event_name, event_data)
  44. except Exception as e:
  45. logger.error(f"Error in event callback for {event_name}: {e}")
  46. logger.debug(f"Event triggered: {event_name}")
  47. def register_callback(self, event_name: str, callback: Callable):
  48. """Register a callback for an event."""
  49. if event_name not in self.event_callbacks:
  50. self.event_callbacks[event_name] = []
  51. self.event_callbacks[event_name].append(callback)
  52. def get_events(self, event_name: Optional[str] = None) -> List[Dict[str, Any]]:
  53. """Get recorded events."""
  54. with self._lock:
  55. if event_name:
  56. return [e for e in self.events if e['name'] == event_name]
  57. return self.events.copy()
  58. def clear_events(self):
  59. """Clear recorded events."""
  60. with self._lock:
  61. self.events.clear()
  62. class VRAudioSimulator:
  63. """Audio simulator for testing voice recognition."""
  64. def __init__(self, sample_rate: int = 16000):
  65. """
  66. Initialize audio simulator.
  67. Args:
  68. sample_rate: Audio sample rate
  69. """
  70. self.sample_rate = sample_rate
  71. self._speaker_profiles = {}
  72. self._background_noise_level = 0.01
  73. def add_speaker_profile(self,
  74. speaker_id: str,
  75. fundamental_freq: float = 150.0,
  76. formant_freqs: List[float] = None):
  77. """
  78. Add a speaker profile for audio simulation.
  79. Args:
  80. speaker_id: Speaker identifier
  81. fundamental_freq: Fundamental frequency (F0)
  82. formant_freqs: Formant frequencies
  83. """
  84. if formant_freqs is None:
  85. formant_freqs = [800, 1200, 2500] # Default formants
  86. self._speaker_profiles[speaker_id] = {
  87. 'f0': fundamental_freq,
  88. 'formants': formant_freqs
  89. }
  90. def generate_speaker_audio(self,
  91. speaker_id: str,
  92. duration_seconds: float = 3.0,
  93. add_noise: bool = True) -> np.ndarray:
  94. """
  95. Generate synthetic audio for a speaker.
  96. Args:
  97. speaker_id: Speaker to generate audio for
  98. duration_seconds: Audio duration
  99. add_noise: Whether to add background noise
  100. Returns:
  101. Generated audio samples
  102. """
  103. if speaker_id not in self._speaker_profiles:
  104. raise ValueError(f"Speaker {speaker_id} not found in profiles")
  105. profile = self._speaker_profiles[speaker_id]
  106. n_samples = int(duration_seconds * self.sample_rate)
  107. t = np.linspace(0, duration_seconds, n_samples)
  108. # Generate fundamental frequency
  109. f0_signal = np.sin(2 * np.pi * profile['f0'] * t)
  110. # Add formants
  111. audio = f0_signal.copy()
  112. for formant_freq in profile['formants']:
  113. formant_signal = 0.3 * np.sin(2 * np.pi * formant_freq * t)
  114. audio += formant_signal
  115. # Add harmonics
  116. for harmonic in range(2, 6):
  117. harmonic_freq = profile['f0'] * harmonic
  118. harmonic_amp = 1.0 / harmonic
  119. audio += harmonic_amp * np.sin(2 * np.pi * harmonic_freq * t)
  120. # Normalize
  121. audio = audio / np.max(np.abs(audio)) * 0.8
  122. # Add envelope (speech-like amplitude modulation)
  123. envelope = self._generate_speech_envelope(duration_seconds, self.sample_rate)
  124. audio = audio * envelope
  125. # Add background noise
  126. if add_noise:
  127. noise = np.random.normal(0, self._background_noise_level, n_samples)
  128. audio += noise
  129. return audio.astype(np.float32)
  130. def generate_background_audio(self, duration_seconds: float = 3.0) -> np.ndarray:
  131. """Generate background/noise audio."""
  132. n_samples = int(duration_seconds * self.sample_rate)
  133. # Generate colored noise
  134. white_noise = np.random.normal(0, 1, n_samples)
  135. # Apply simple low-pass filter for more realistic noise
  136. b = np.ones(5) / 5 # Simple moving average
  137. colored_noise = np.convolve(white_noise, b, mode='same')
  138. # Normalize
  139. colored_noise = colored_noise / np.max(np.abs(colored_noise)) * 0.1
  140. return colored_noise.astype(np.float32)
  141. def _generate_speech_envelope(self, duration: float, sample_rate: int) -> np.ndarray:
  142. """Generate speech-like amplitude envelope."""
  143. n_samples = int(duration * sample_rate)
  144. t = np.linspace(0, duration, n_samples)
  145. # Create speech-like envelope with pauses
  146. envelope = np.ones(n_samples)
  147. # Add some speech pauses
  148. pause_starts = np.random.uniform(0.2, duration - 0.4, size=max(1, int(duration / 2)))
  149. for pause_start in pause_starts:
  150. pause_duration = np.random.uniform(0.1, 0.3)
  151. start_idx = int(pause_start * sample_rate)
  152. end_idx = int((pause_start + pause_duration) * sample_rate)
  153. end_idx = min(end_idx, n_samples)
  154. # Gradual fade in/out
  155. fade_samples = int(0.05 * sample_rate) # 50ms fade
  156. if end_idx - start_idx > 2 * fade_samples:
  157. envelope[start_idx:start_idx + fade_samples] *= np.linspace(1, 0.1, fade_samples)
  158. envelope[start_idx + fade_samples:end_idx - fade_samples] *= 0.1
  159. envelope[end_idx - fade_samples:end_idx] *= np.linspace(0.1, 1, fade_samples)
  160. return envelope
  161. class VoiceRecognitionTester:
  162. """Comprehensive testing framework for voice recognition system."""
  163. def __init__(self, config: VoiceRecognitionConfig):
  164. """
  165. Initialize voice recognition tester.
  166. Args:
  167. config: Voice recognition configuration
  168. """
  169. self.config = config
  170. self.event_handler = VRMockEventHandler()
  171. self.audio_simulator = VRAudioSimulator(config.audio_config.sample_rate)
  172. # Test results storage
  173. self.test_results = {}
  174. self._current_test = None
  175. def setup_test_speakers(self, speaker_configs: Dict[str, Dict[str, Any]]):
  176. """
  177. Setup test speakers for simulation.
  178. Args:
  179. speaker_configs: Dictionary of speaker configurations
  180. """
  181. for speaker_id, config in speaker_configs.items():
  182. self.audio_simulator.add_speaker_profile(
  183. speaker_id=speaker_id,
  184. fundamental_freq=config.get('f0', 150.0),
  185. formant_freqs=config.get('formants', [800, 1200, 2500])
  186. )
  187. def run_identification_test(self,
  188. test_speakers: List[str],
  189. samples_per_speaker: int = 10,
  190. add_noise: bool = True) -> Dict[str, Any]:
  191. """
  192. Run speaker identification accuracy test.
  193. Args:
  194. test_speakers: List of speaker IDs to test
  195. samples_per_speaker: Number of test samples per speaker
  196. add_noise: Whether to add noise to test samples
  197. Returns:
  198. Test results dictionary
  199. """
  200. logger.info(f"Running identification test with {len(test_speakers)} speakers")
  201. # Create detector (this would normally use a real model)
  202. detector = self._create_mock_detector()
  203. results = {
  204. 'test_type': 'identification',
  205. 'speakers': test_speakers,
  206. 'samples_per_speaker': samples_per_speaker,
  207. 'total_samples': len(test_speakers) * samples_per_speaker,
  208. 'correct_identifications': 0,
  209. 'total_attempts': 0,
  210. 'accuracy': 0.0,
  211. 'per_speaker_accuracy': {},
  212. 'confusion_matrix': {},
  213. 'avg_confidence': 0.0,
  214. 'avg_processing_time_ms': 0.0
  215. }
  216. processing_times = []
  217. confidences = []
  218. for speaker_id in test_speakers:
  219. correct_for_speaker = 0
  220. for sample_idx in range(samples_per_speaker):
  221. # Generate test audio
  222. audio = self.audio_simulator.generate_speaker_audio(
  223. speaker_id=speaker_id,
  224. duration_seconds=self.config.audio_config.audio_length,
  225. add_noise=add_noise
  226. )
  227. # Run identification
  228. start_time = time.time()
  229. result = detector.identify_speaker(audio)
  230. processing_time = (time.time() - start_time) * 1000
  231. processing_times.append(processing_time)
  232. confidences.append(result.confidence)
  233. # Check accuracy
  234. results['total_attempts'] += 1
  235. if result.speaker_id == speaker_id:
  236. results['correct_identifications'] += 1
  237. correct_for_speaker += 1
  238. # Update confusion matrix
  239. predicted = result.speaker_id or 'unknown'
  240. if speaker_id not in results['confusion_matrix']:
  241. results['confusion_matrix'][speaker_id] = {}
  242. if predicted not in results['confusion_matrix'][speaker_id]:
  243. results['confusion_matrix'][speaker_id][predicted] = 0
  244. results['confusion_matrix'][speaker_id][predicted] += 1
  245. # Calculate per-speaker accuracy
  246. results['per_speaker_accuracy'][speaker_id] = correct_for_speaker / samples_per_speaker
  247. # Calculate overall metrics
  248. results['accuracy'] = results['correct_identifications'] / results['total_attempts']
  249. results['avg_confidence'] = np.mean(confidences)
  250. results['avg_processing_time_ms'] = np.mean(processing_times)
  251. logger.info(f"Identification test completed: {results['accuracy']:.2%} accuracy")
  252. return results
  253. def run_verification_test(self,
  254. test_speakers: List[str],
  255. impostor_samples: int = 20,
  256. genuine_samples: int = 20) -> Dict[str, Any]:
  257. """
  258. Run speaker verification test (genuine vs impostor).
  259. Args:
  260. test_speakers: List of speaker IDs to test
  261. impostor_samples: Number of impostor samples per speaker
  262. genuine_samples: Number of genuine samples per speaker
  263. Returns:
  264. Test results dictionary
  265. """
  266. logger.info(f"Running verification test with {len(test_speakers)} speakers")
  267. detector = self._create_mock_detector()
  268. results = {
  269. 'test_type': 'verification',
  270. 'speakers': test_speakers,
  271. 'genuine_samples': genuine_samples,
  272. 'impostor_samples': impostor_samples,
  273. 'true_positives': 0,
  274. 'false_positives': 0,
  275. 'true_negatives': 0,
  276. 'false_negatives': 0,
  277. 'far': 0.0, # False Acceptance Rate
  278. 'frr': 0.0, # False Rejection Rate
  279. 'eer': 0.0, # Equal Error Rate
  280. 'accuracy': 0.0
  281. }
  282. for speaker_id in test_speakers:
  283. # Test genuine samples
  284. for _ in range(genuine_samples):
  285. audio = self.audio_simulator.generate_speaker_audio(speaker_id)
  286. result = detector.verify_speaker(audio, speaker_id)
  287. if result.confidence >= self.config.recognition_config.verification_threshold:
  288. results['true_positives'] += 1
  289. else:
  290. results['false_negatives'] += 1
  291. # Test impostor samples
  292. for _ in range(impostor_samples):
  293. # Generate audio from a different speaker or background
  294. if len(test_speakers) > 1:
  295. impostor_speakers = [s for s in test_speakers if s != speaker_id]
  296. impostor_id = np.random.choice(impostor_speakers)
  297. audio = self.audio_simulator.generate_speaker_audio(impostor_id)
  298. else:
  299. audio = self.audio_simulator.generate_background_audio()
  300. result = detector.verify_speaker(audio, speaker_id)
  301. if result.confidence >= self.config.recognition_config.verification_threshold:
  302. results['false_positives'] += 1
  303. else:
  304. results['true_negatives'] += 1
  305. # Calculate metrics
  306. total_genuine = len(test_speakers) * genuine_samples
  307. total_impostor = len(test_speakers) * impostor_samples
  308. results['far'] = results['false_positives'] / total_impostor if total_impostor > 0 else 0
  309. results['frr'] = results['false_negatives'] / total_genuine if total_genuine > 0 else 0
  310. results['accuracy'] = (results['true_positives'] + results['true_negatives']) / (total_genuine + total_impostor)
  311. # Estimate EER (simplified)
  312. results['eer'] = (results['far'] + results['frr']) / 2
  313. logger.info(f"Verification test completed: FAR={results['far']:.2%}, FRR={results['frr']:.2%}")
  314. return results
  315. def run_performance_test(self,
  316. duration_minutes: float = 5.0,
  317. target_fps: float = 33.3) -> Dict[str, Any]:
  318. """
  319. Run performance stress test.
  320. Args:
  321. duration_minutes: Test duration in minutes
  322. target_fps: Target processing rate (frames per second)
  323. Returns:
  324. Performance test results
  325. """
  326. logger.info(f"Running performance test for {duration_minutes} minutes")
  327. detector = self._create_mock_detector()
  328. results = {
  329. 'test_type': 'performance',
  330. 'duration_minutes': duration_minutes,
  331. 'target_fps': target_fps,
  332. 'frames_processed': 0,
  333. 'frames_dropped': 0,
  334. 'avg_processing_time_ms': 0.0,
  335. 'max_processing_time_ms': 0.0,
  336. 'achieved_fps': 0.0,
  337. 'memory_usage_mb': 0.0,
  338. 'errors': 0
  339. }
  340. processing_times = []
  341. start_time = time.time()
  342. end_time = start_time + (duration_minutes * 60)
  343. frame_interval = 1.0 / target_fps
  344. # Generate a test speaker
  345. test_speaker = 'performance_test_speaker'
  346. self.audio_simulator.add_speaker_profile(test_speaker)
  347. while time.time() < end_time:
  348. frame_start = time.time()
  349. try:
  350. # Generate test audio
  351. audio = self.audio_simulator.generate_speaker_audio(
  352. test_speaker,
  353. duration_seconds=self.config.audio_config.audio_length
  354. )
  355. # Process audio
  356. result = detector.identify_speaker(audio)
  357. processing_time = (time.time() - frame_start) * 1000
  358. processing_times.append(processing_time)
  359. results['frames_processed'] += 1
  360. # Simulate real-time constraint
  361. elapsed = time.time() - frame_start
  362. if elapsed < frame_interval:
  363. time.sleep(frame_interval - elapsed)
  364. else:
  365. results['frames_dropped'] += 1
  366. except Exception as e:
  367. logger.error(f"Error in performance test: {e}")
  368. results['errors'] += 1
  369. # Calculate results
  370. total_duration = time.time() - start_time
  371. results['achieved_fps'] = results['frames_processed'] / total_duration
  372. results['avg_processing_time_ms'] = np.mean(processing_times) if processing_times else 0
  373. results['max_processing_time_ms'] = np.max(processing_times) if processing_times else 0
  374. logger.info(f"Performance test completed: {results['achieved_fps']:.1f} FPS achieved")
  375. return results
  376. def _create_mock_detector(self) -> VoiceRecognitionDetector:
  377. """Create a mock detector for testing."""
  378. # This is a simplified mock - in real testing you'd use actual models
  379. # For now, we'll create a basic detector that simulates behavior
  380. class MockDetector:
  381. def __init__(self, config, event_handler):
  382. self.config = config
  383. self.event_handler = event_handler
  384. self._speakers = ['speaker_1', 'speaker_2', 'speaker_3', 'speaker_4']
  385. def identify_speaker(self, audio_data):
  386. # Simulate processing delay
  387. time.sleep(0.01) # 10ms processing time
  388. # Mock identification based on audio characteristics
  389. audio_energy = np.mean(audio_data ** 2)
  390. speaker_idx = int(audio_energy * 1000) % len(self._speakers)
  391. return SpeakerIdentificationResult(
  392. speaker_id=self._speakers[speaker_idx],
  393. speaker_name=f"Speaker {speaker_idx + 1}",
  394. confidence=0.7 + np.random.uniform(-0.2, 0.2),
  395. processing_time_ms=10.0 + np.random.uniform(-2, 2)
  396. )
  397. def verify_speaker(self, audio_data, claimed_speaker_id):
  398. result = self.identify_speaker(audio_data)
  399. # Mock verification logic
  400. if result.speaker_id == claimed_speaker_id:
  401. result.confidence = min(0.9, result.confidence + 0.1)
  402. else:
  403. result.confidence = max(0.1, result.confidence - 0.3)
  404. return result
  405. return MockDetector(self.config, self.event_handler)
  406. def example_speaker_identification():
  407. """Example of basic speaker identification."""
  408. logger.info("Running speaker identification example...")
  409. # Create configuration
  410. config = create_default_config(
  411. model_path="/mock/path/to/model.pth",
  412. model_password="test_password"
  413. )
  414. # Create mock event handler
  415. event_handler = VRMockEventHandler()
  416. # Create audio simulator
  417. audio_sim = VRAudioSimulator(config.audio_config.sample_rate)
  418. # Setup test speakers
  419. speakers = {
  420. 'alice': {'f0': 180, 'formants': [900, 1300, 2800]},
  421. 'bob': {'f0': 120, 'formants': [700, 1100, 2300]},
  422. 'charlie': {'f0': 150, 'formants': [800, 1200, 2500]}
  423. }
  424. for speaker_id, params in speakers.items():
  425. audio_sim.add_speaker_profile(speaker_id, **params)
  426. # Generate and process audio samples
  427. results = []
  428. for speaker_id in speakers:
  429. audio = audio_sim.generate_speaker_audio(speaker_id)
  430. # In a real scenario, you would use VoiceRecognitionDetector here
  431. # For the example, we'll simulate the result
  432. result = {
  433. 'speaker_id': speaker_id,
  434. 'confidence': 0.85,
  435. 'processing_time_ms': 25.3
  436. }
  437. results.append(result)
  438. logger.info(f"Identified {speaker_id} with confidence {result['confidence']:.2f}")
  439. logger.info("Speaker identification example completed")
  440. return results
  441. def example_speaker_verification():
  442. """Example of speaker verification."""
  443. logger.info("Running speaker verification example...")
  444. # Create configuration
  445. config = create_default_config("/mock/path/to/model.pth")
  446. config.recognition_config.verification_threshold = 0.6
  447. # Create audio simulator
  448. audio_sim = VRAudioSimulator()
  449. audio_sim.add_speaker_profile('target_speaker', f0=160)
  450. # Test genuine speaker
  451. genuine_audio = audio_sim.generate_speaker_audio('target_speaker')
  452. genuine_confidence = 0.82 # Mock result
  453. # Test impostor
  454. impostor_audio = audio_sim.generate_background_audio()
  455. impostor_confidence = 0.35 # Mock result
  456. results = {
  457. 'genuine_verification': {
  458. 'confidence': genuine_confidence,
  459. 'verified': genuine_confidence >= config.recognition_config.verification_threshold
  460. },
  461. 'impostor_verification': {
  462. 'confidence': impostor_confidence,
  463. 'verified': impostor_confidence >= config.recognition_config.verification_threshold
  464. }
  465. }
  466. logger.info(f"Genuine speaker verification: {results['genuine_verification']}")
  467. logger.info(f"Impostor verification: {results['impostor_verification']}")
  468. return results
  469. def example_dynamic_speaker_management():
  470. """Example of dynamic speaker management."""
  471. logger.info("Running dynamic speaker management example...")
  472. # Create configuration
  473. config = create_default_config("/mock/path/to/model.pth")
  474. # Create audio simulator
  475. audio_sim = VRAudioSimulator()
  476. # Simulate adding new speakers
  477. new_speakers = ['david', 'eva', 'frank']
  478. training_samples = []
  479. for speaker_id in new_speakers:
  480. # Add profile and generate training samples
  481. audio_sim.add_speaker_profile(speaker_id, f0=140 + len(training_samples) * 20)
  482. speaker_samples = []
  483. for _ in range(5): # 5 training samples per speaker
  484. audio = audio_sim.generate_speaker_audio(speaker_id)
  485. speaker_samples.append(audio)
  486. training_samples.append({
  487. 'speaker_id': speaker_id,
  488. 'samples': speaker_samples
  489. })
  490. # Simulate speaker addition process
  491. results = {
  492. 'speakers_added': [],
  493. 'training_samples_per_speaker': 5,
  494. 'success_rate': 1.0 # Mock 100% success
  495. }
  496. for speaker_data in training_samples:
  497. # In real implementation, this would call detector.add_speaker()
  498. results['speakers_added'].append(speaker_data['speaker_id'])
  499. logger.info(f"Added speaker: {speaker_data['speaker_id']}")
  500. logger.info("Dynamic speaker management example completed")
  501. return results
  502. def run_all_vr_examples() -> Dict[str, Any]:
  503. """Run all voice recognition examples."""
  504. logger.info("Running all voice recognition examples...")
  505. results = {
  506. 'speaker_identification': example_speaker_identification(),
  507. 'speaker_verification': example_speaker_verification(),
  508. 'dynamic_speaker_management': example_dynamic_speaker_management()
  509. }
  510. logger.info("All voice recognition examples completed")
  511. return results
  512. if __name__ == "__main__":
  513. # Configure logging
  514. logging.basicConfig(level=logging.INFO)
  515. # Run examples
  516. all_results = run_all_vr_examples()
  517. print("\nExample Results:")
  518. for example_name, result in all_results.items():
  519. print(f"{example_name}: {result}")