diff --git a/protocol_prototype/DryBox/AUDIO_SETUP.md b/protocol_prototype/DryBox/AUDIO_SETUP.md deleted file mode 100644 index 05c38db..0000000 --- a/protocol_prototype/DryBox/AUDIO_SETUP.md +++ /dev/null @@ -1,52 +0,0 @@ -# Audio Setup Guide for DryBox - -## Installing PyAudio on Fedora - -PyAudio requires system dependencies before installation: - -```bash -# Install required system packages -sudo dnf install python3-devel portaudio-devel - -# Then install PyAudio -pip install pyaudio -``` - -## Alternative: Run Without PyAudio - -If you prefer not to install PyAudio, the application will still work but without real-time playback. You can still: -- Record audio to files -- Process and export audio -- Use all other features - -To run without PyAudio, the audio_player.py module will gracefully handle the missing dependency. - -## Ubuntu/Debian Installation - -```bash -sudo apt-get install python3-dev portaudio19-dev -pip install pyaudio -``` - -## macOS Installation - -```bash -brew install portaudio -pip install pyaudio -``` - -## Troubleshooting - -If you see "No module named 'pyaudio'" errors: -1. The app will continue to work without playback -2. Recording and processing features remain available -3. Install PyAudio later when convenient - -## Testing Audio Features - -1. Run the application: `python UI/main.py` -2. Start a call between phones -3. Test features: - - Recording: Works without PyAudio - - Playback: Requires PyAudio - - Processing: Works without PyAudio \ No newline at end of file diff --git a/protocol_prototype/DryBox/AUDIO_TESTING_GUIDE.md b/protocol_prototype/DryBox/AUDIO_TESTING_GUIDE.md deleted file mode 100644 index 7b9501c..0000000 --- a/protocol_prototype/DryBox/AUDIO_TESTING_GUIDE.md +++ /dev/null @@ -1,118 +0,0 @@ -# Audio Testing Guide for DryBox - -## Setup Verification - -1. **Start the server first**: - ```bash - python server.py - ``` - -2. **Run the UI**: - ```bash - python UI/main.py - ``` - -## Testing Audio Playback - -### Step 1: Test PyAudio is Working -When you enable playback (Ctrl+1 or Ctrl+2), you should hear a short beep (100ms, 1kHz tone). This confirms: -- PyAudio is properly installed -- Audio output device is working -- Stream format is correct - -### Step 2: Test During Call -1. Click "Run Automatic Test" or press Space -2. **Immediately** enable playback on Phone 2 (Ctrl+2) - - You should hear the test beep -3. Watch the debug console for: - - "Phone 2 playback started" - - "Phone 2 sent test beep to verify audio" -4. Wait for handshake to complete (steps 4-5 in test) -5. Once voice session starts, you should see: - - "Phone 2 received audio data: XXX bytes" - - "Phone 2 forwarding audio to player" - - "Client 1 playback thread got XXX bytes" - -### What to Look For in Debug Console - -**Good signs:** -``` -[AudioPlayer] Client 1 add_audio_data called with 640 bytes -[AudioPlayer] Client 1 added to buffer, queue size: 1 -[AudioPlayer] Client 1 playback thread got 640 bytes -``` - -**Problem signs:** -``` -[AudioPlayer] Client 1 has no buffer (playback not started?) -Low confidence demodulation: 0.XX -Codec decode returned None or empty -``` - -## Troubleshooting - -### No Test Beep -- Check system volume -- Verify PyAudio: `python test_audio_setup.py` -- Check audio device: `python -c "import pyaudio; p=pyaudio.PyAudio(); print(p.get_default_output_device_info())"` - -### Test Beep Works but No Voice Audio -1. **Check if audio is being transmitted:** - - Phone 1 should show: "sent N voice frames" - - Phone 2 should show: "Received voice data frame #N" - -2. **Check if audio is being decoded:** - - Look for: "Decoded PCM samples: type=, len=320" - - Look for: "Emitting PCM bytes: 640 bytes" - -3. **Check if audio reaches the player:** - - Look for: "Phone 2 received audio data: 640 bytes" - - Look for: "Client 1 add_audio_data called with 640 bytes" - -### Audio Sounds Distorted -This is normal! The system uses: -- Codec2 at 1200bps (very low bitrate) -- 4FSK modulation -- This creates robotic/vocoder-like sound - -### Manual Testing Commands - -Test just the codec: -```python -python test_audio_pipeline.py -``` - -Play the test outputs: -```bash -# Original -aplay wav/input.wav - -# Codec only (should sound robotic) -aplay wav/test_codec_only.wav - -# Full pipeline (codec + FSK) -aplay wav/test_full_pipeline.wav -``` - -## Expected Audio Flow - -1. Phone 1 reads `wav/input.wav` (8kHz mono) -2. Encodes 320 samples (40ms) with Codec2 → 6 bytes -3. Modulates with 4FSK → ~1112 float samples -4. Encrypts with Noise XK -5. Sends to server -6. Server routes to Phone 2 -7. Phone 2 decrypts with Noise XK -8. Demodulates FSK → 6 bytes -9. Decodes with Codec2 → 320 samples (640 bytes PCM) -10. Sends to PyAudio for playback - -## Recording Feature - -To save received audio: -1. Press Alt+1 or Alt+2 to start recording -2. Let it run during the call -3. Press again to stop and save -4. Check `wav/` directory for saved files - -This helps verify if audio is being received even if playback isn't working. \ No newline at end of file diff --git a/protocol_prototype/DryBox/PHONE2_PLAYBACK.md b/protocol_prototype/DryBox/PHONE2_PLAYBACK.md deleted file mode 100644 index e0735b8..0000000 --- a/protocol_prototype/DryBox/PHONE2_PLAYBACK.md +++ /dev/null @@ -1,58 +0,0 @@ -# Phone 2 Playback - What It Actually Plays - -## The Complete Audio Flow for Phone 2 - -When Phone 2 receives audio, it goes through this exact process: - -### 1. Network Reception -- Encrypted data arrives from server -- Data includes Noise XK encrypted voice frames - -### 2. Decryption (Noise XK) -- `protocol_phone_client.py` line 156-165: Noise wrapper decrypts the data -- Result: Decrypted voice message containing FSK modulated signal - -### 3. Demodulation (4FSK) -- `_handle_voice_data()` line 223: FSK demodulation -- Converts modulated signal back to 6 bytes of compressed data -- Only processes if confidence > 0.5 - -### 4. Decompression (Codec2 Decode) -- Line 236: `pcm_samples = self.codec.decode(frame)` -- Converts 6 bytes → 320 samples (640 bytes PCM) -- This is the final audio ready for playback - -### 5. Playback -- Line 264: `self.data_received.emit(pcm_bytes, self.client_id)` -- PCM audio sent to audio player -- PyAudio plays the 16-bit, 8kHz mono audio - -## What You Hear on Phone 2 - -Phone 2 plays audio that has been: -- ✅ Encrypted → Decrypted (Noise XK) -- ✅ Modulated → Demodulated (4FSK) -- ✅ Compressed → Decompressed (Codec2) - -The audio will sound: -- **Robotic/Vocoder-like** due to 1200bps Codec2 compression -- **Slightly delayed** due to processing pipeline -- **But intelligible** - you can understand speech - -## Fixed Issues - -1. **Silent beginning**: Now skips first second of silence in input.wav -2. **Control messages**: No longer sent to audio player -3. **Debug spam**: Reduced to show only important frames - -## Testing Phone 2 Playback - -1. Run automatic test (Space) -2. Enable Phone 2 playback (Ctrl+2) -3. Wait for handshake to complete -4. You should hear: - - Audio starting from 1 second into input.wav - - Processed through full protocol stack - - Robotic but understandable audio - -The key point: Phone 2 IS playing fully processed audio (decrypted + demodulated + decompressed)! \ No newline at end of file diff --git a/protocol_prototype/DryBox/PLAYBACK_FIXED.md b/protocol_prototype/DryBox/PLAYBACK_FIXED.md deleted file mode 100644 index 2710bd3..0000000 --- a/protocol_prototype/DryBox/PLAYBACK_FIXED.md +++ /dev/null @@ -1,67 +0,0 @@ -# Fixed Audio Playback Guide - -## How Playback Now Works - -### Phone 1 (Sender) Playback -- **What it plays**: Original audio from `input.wav` BEFORE encoding -- **When to enable**: During a call to hear what you're sending -- **Audio quality**: Clear, unprocessed 8kHz mono audio - -### Phone 2 (Receiver) Playback -- **What it plays**: Decoded audio AFTER the full pipeline (Codec2 → FSK → Noise XK → transmission → decryption → demodulation → decoding) -- **When to enable**: During a call to hear what's being received -- **Audio quality**: Robotic/vocoder sound due to 1200bps Codec2 compression - -## Changes Made - -1. **Fixed control message routing** - 8-byte control messages no longer sent to audio player -2. **Phone 1 now plays original audio** when sending (before encoding) -3. **Removed test beep** - you'll hear actual audio immediately -4. **Added size filter** - only audio data (≥320 bytes) is processed - -## Testing Steps - -1. Start server: `python server.py` -2. Start UI: `python UI/main.py` -3. Run automatic test (Space key) -4. **For Phone 1 playback**: Press Ctrl+1 to hear the original `input.wav` being sent -5. **For Phone 2 playback**: Press Ctrl+2 to hear the decoded audio after transmission - -## Expected Debug Output - -**Good signs for Phone 1 (sender):** -``` -Phone 1 playing original audio (sender playback) -[AudioPlayer] Client 0 add_audio_data called with 640 bytes -``` - -**Good signs for Phone 2 (receiver):** -``` -Phone 2 received audio data: 640 bytes -Phone 2 forwarding audio to player (playback enabled) -[AudioPlayer] Client 1 add_audio_data called with 640 bytes -``` - -**Fixed issues:** -``` -Phone 2 received non-audio data: 8 bytes (ignoring) # Control messages now filtered out -``` - -## Audio Quality Expectations - -- **Phone 1**: Should sound identical to `input.wav` -- **Phone 2**: Will sound robotic/compressed due to: - - Codec2 compression at 1200bps (very low bitrate) - - 4FSK modulation/demodulation - - This is normal and proves the protocol is working! - -## Troubleshooting - -If you still don't hear audio: - -1. **Check debug console** for the messages above -2. **Verify handshake completes** before expecting audio -3. **Try recording** (Alt+1/2) to save audio for offline playback -4. **Check system volume** and audio device - -The most important fix: control messages are no longer sent to the audio player, so you should only receive actual 640-byte audio frames. \ No newline at end of file diff --git a/protocol_prototype/DryBox/PLAYBACK_SUMMARY.md b/protocol_prototype/DryBox/PLAYBACK_SUMMARY.md deleted file mode 100644 index b8c0ee6..0000000 --- a/protocol_prototype/DryBox/PLAYBACK_SUMMARY.md +++ /dev/null @@ -1,83 +0,0 @@ -# Audio Playback Implementation Summary - -## Key Fixes Applied - -### 1. Separated Sender vs Receiver Playback -- **Phone 1 (Sender)**: Now plays the original `input.wav` audio when transmitting -- **Phone 2 (Receiver)**: Plays the decoded audio after full protocol processing - -### 2. Fixed Control Message Routing -- Control messages (like "CALL_END" - 8 bytes) no longer sent to audio player -- Added size filter: only data ≥320 bytes is considered audio -- Removed problematic `data_received.emit()` for non-audio messages - -### 3. Improved Debug Logging -- Reduced verbosity: logs only first frame and every 25th frame -- Clear indication of what's happening at each stage -- Separate logging for sender vs receiver playback - -### 4. Code Changes Made - -**phone_manager.py**: -- Added original audio playback for sender -- Added size filter for received data -- Improved debug logging with frame counters - -**protocol_phone_client.py**: -- Removed control message emission to data_received -- Added confidence logging for demodulation -- Reduced debug verbosity - -**audio_player.py**: -- Added frame counting for debug -- Reduced playback thread logging -- Better buffer status reporting - -**main.py**: -- Fixed lambda signal connection issue -- Improved UI scaling with flexible layouts - -## How to Test - -1. Start server and UI -2. Run automatic test (Space) -3. Enable playback: - - **Ctrl+1**: Hear original audio from Phone 1 - - **Ctrl+2**: Hear decoded audio on Phone 2 - -## Expected Behavior - -**Phone 1 with playback enabled:** -- Clear audio matching `input.wav` -- Shows "playing original audio (sender playback)" - -**Phone 2 with playback enabled:** -- Robotic/compressed audio (normal for 1200bps) -- Shows "received audio frame #N: 640 bytes" -- No more "8 bytes" messages - -## Audio Flow -``` -Phone 1: Phone 2: -input.wav (8kHz) - ↓ -[Playback here if enabled] - ↓ -Codec2 encode (1200bps) - ↓ -4FSK modulate - ↓ -Noise XK encrypt - ↓ -→ Network transmission → - ↓ - Noise XK decrypt - ↓ - 4FSK demodulate - ↓ - Codec2 decode - ↓ - [Playback here if enabled] -``` - -The playback now correctly plays audio at the right points in the pipeline! \ No newline at end of file diff --git a/protocol_prototype/DryBox/README.md b/protocol_prototype/DryBox/README.md deleted file mode 100644 index 0731eb5..0000000 --- a/protocol_prototype/DryBox/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# DryBox - Secure Voice Over GSM Protocol - -A secure voice communication protocol that transmits encrypted voice data over standard GSM voice channels. - -## Architecture - -- **Noise XK Protocol**: Provides authenticated key exchange and secure channel -- **Codec2**: Voice compression (1200 bps mode) -- **4FSK Modulation**: Converts digital data to audio tones -- **Encryption**: ChaCha20-Poly1305 for secure communication - -## Project Structure - -``` -DryBox/ -├── UI/ # User interface components -│ ├── main.py # Main PyQt5 application -│ ├── phone_manager.py # Phone state management -│ ├── protocol_phone_client.py # Protocol client implementation -│ ├── noise_wrapper.py # Noise XK wrapper -│ └── ... -├── simulator/ # GSM channel simulator -│ └── gsm_simulator.py # Simulates GSM voice channel -├── voice_codec.py # Codec2 and FSK modem implementation -├── encryption.py # Encryption utilities -└── wav/ # Audio test files - -``` - -## Running the Protocol - -1. Start the GSM simulator: -```bash -cd simulator -python3 gsm_simulator.py -``` - -2. Run the UI application: -```bash -./run_ui.sh -# or -python3 UI/main.py -``` - -## Usage - -1. Click "Call" on Phone 1 to initiate -2. Click "Answer" on Phone 2 to accept -3. The protocol will automatically: - - Establish secure connection via Noise XK - - Start voice session - - Compress and encrypt voice data - - Transmit over simulated GSM channel - -## Requirements - -- Python 3.6+ -- PyQt5 -- dissononce (Noise protocol) -- numpy (optional, for optimized audio processing) \ No newline at end of file diff --git a/protocol_prototype/DryBox/UI_FEATURES_GUIDE.md b/protocol_prototype/DryBox/UI_FEATURES_GUIDE.md deleted file mode 100644 index 0dc16af..0000000 --- a/protocol_prototype/DryBox/UI_FEATURES_GUIDE.md +++ /dev/null @@ -1,105 +0,0 @@ -# DryBox UI Features Guide - -## UI Improvements -The UI has been updated with responsive layouts that scale better: -- Phone displays now use flexible sizing (min/max constraints) -- Waveform widgets adapt to available space -- Buttons have flexible widths that scale with window size -- Better margins and padding for improved visual appearance - -## Audio Playback Feature - -The DryBox UI includes real-time audio playback capabilities that allow you to hear the decoded audio as it's received. - -### How to Use Playback - -#### Manual Control -1. **During a Call**: Once a secure voice session is established, click the "🔊 Playback" button under either phone -2. **Button States**: - - Gray (unchecked): Playback disabled - - Green (checked): Playback active -3. **Toggle Anytime**: You can enable/disable playback at any time during a call - -#### Keyboard Shortcuts -- `Ctrl+1`: Toggle playback for Phone 1 -- `Ctrl+2`: Toggle playback for Phone 2 - -### Using Playback with Automatic Test - -The automatic test feature demonstrates the complete protocol flow. Here's how to use it with playback: - -1. **Start the Test**: Click "🧪 Run Automatic Test" or press `Space` -2. **Enable Playback Early**: - - As soon as the test starts, enable playback on Phone 2 (Ctrl+2) - - This ensures you'll hear audio as soon as the secure channel is established -3. **What You'll Hear**: - - Once handshake completes (step 4-5), Phone 1 starts transmitting test audio - - Phone 2 will play the received, decoded audio through your speakers - - The audio goes through: Codec2 encoding → 4FSK modulation → Noise XK encryption → transmission → decryption → demodulation → Codec2 decoding - -### Audio Recording Feature - -You can also record received audio for later analysis: - -1. **Start Recording**: Click "⏺ Record" button (or press Alt+1/Alt+2) -2. **Stop Recording**: Click the button again -3. **Files Saved**: Recordings are saved to `wav/` directory with timestamps - -### Audio Processing Options - -Access advanced audio features via "Audio Options" button (Ctrl+A): -- **Export Buffer**: Save current audio buffer to file -- **Clear Buffer**: Clear accumulated audio data -- **Processing Options**: - - Normalize Audio - - Apply Gain (adjustable dB) - - Noise Gate - - Low/High Pass Filters - - Remove Silence - -### Requirements - -For playback to work, you need PyAudio installed: -```bash -# Fedora/RHEL -sudo dnf install python3-devel portaudio-devel -pip install pyaudio - -# Ubuntu/Debian -sudo apt-get install python3-dev portaudio19-dev -pip install pyaudio -``` - -If PyAudio isn't installed, recording will still work but playback will be disabled. - -### Troubleshooting - -1. **No Sound**: - - Check PyAudio is installed - - Ensure system volume is up - - Verify audio device is working - -2. **Choppy Audio**: - - Normal for low-bitrate codec (1200bps) - - Represents actual protocol performance - -3. **Delayed Start**: - - Audio only flows after secure handshake - - Wait for "🔒 Secure Channel Established" status - -### Test Sequence Overview - -The automatic test goes through these steps: -1. Initial state check -2. Phone 1 calls Phone 2 -3. Phone 2 answers -4. Noise XK handshake begins -5. Handshake completes, secure channel established -6. Voice session starts (Codec2 + 4FSK) -7. Audio transmission begins -8. Protocol details logged -9. Transmission continues for observation -10. Final statistics -11. Call ends, cleanup - -Enable playback on the receiving phone to hear the transmitted audio in real-time! \ No newline at end of file diff --git a/protocol_prototype/DryBox/test_audio_features.py b/protocol_prototype/DryBox/test_audio_features.py deleted file mode 100755 index 36636e7..0000000 --- a/protocol_prototype/DryBox/test_audio_features.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -"""Test script for audio features in DryBox""" - -import sys -import os -import wave -import struct -import time - -# Add parent directory to path -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from UI.audio_player import AudioPlayer, PYAUDIO_AVAILABLE -from UI.audio_processor import AudioProcessor - -def create_test_audio(filename="test_tone.wav", duration=2, frequency=440): - """Create a test audio file with a sine wave""" - sample_rate = 8000 - num_samples = int(sample_rate * duration) - - # Generate sine wave - import math - samples = [] - for i in range(num_samples): - t = float(i) / sample_rate - value = int(32767 * 0.5 * math.sin(2 * math.pi * frequency * t)) - samples.append(value) - - # Save to WAV file - with wave.open(filename, 'wb') as wav_file: - wav_file.setnchannels(1) - wav_file.setsampwidth(2) - wav_file.setframerate(sample_rate) - wav_file.writeframes(struct.pack(f'{len(samples)}h', *samples)) - - print(f"Created test audio file: {filename}") - return filename - -def test_audio_player(): - """Test audio player functionality""" - print("\n=== Testing Audio Player ===") - - player = AudioPlayer() - player.set_debug_callback(print) - - if PYAUDIO_AVAILABLE: - print("PyAudio is available - testing playback") - - # Test playback - client_id = 0 - if player.start_playback(client_id): - print(f"Started playback for client {client_id}") - - # Create and play test audio - test_file = create_test_audio() - with wave.open(test_file, 'rb') as wav: - data = wav.readframes(wav.getnframes()) - - # Add audio data - chunk_size = 640 # 320 samples * 2 bytes - for i in range(0, len(data), chunk_size): - chunk = data[i:i+chunk_size] - player.add_audio_data(client_id, chunk) - time.sleep(0.04) # 40ms per chunk - - time.sleep(0.5) # Let playback finish - player.stop_playback(client_id) - print(f"Stopped playback for client {client_id}") - - # Clean up - os.remove(test_file) - else: - print("PyAudio not available - skipping playback test") - - # Test recording (works without PyAudio) - print("\n=== Testing Recording ===") - client_id = 1 - player.start_recording(client_id) - - # Add some test data - test_data = b'\x00\x01' * 320 # Simple test pattern - for i in range(10): - player.add_audio_data(client_id, test_data) - - save_path = player.stop_recording(client_id, "test_recording.wav") - if save_path and os.path.exists(save_path): - print(f"Recording saved successfully: {save_path}") - os.remove(save_path) - else: - print("Recording failed") - - player.cleanup() - print("Audio player test complete") - -def test_audio_processor(): - """Test audio processor functionality""" - print("\n=== Testing Audio Processor ===") - - processor = AudioProcessor() - processor.set_debug_callback(print) - - # Create test audio - test_file = create_test_audio("test_input.wav", duration=1, frequency=1000) - - # Read test audio - with wave.open(test_file, 'rb') as wav: - test_data = wav.readframes(wav.getnframes()) - - # Test various processing functions - print("\nTesting normalize:") - normalized = processor.normalize_audio(test_data, target_db=-6) - save_path = processor.save_processed_audio(normalized, test_file, "normalized") - if save_path: - print(f"Saved: {save_path}") - os.remove(save_path) - - print("\nTesting gain:") - gained = processor.apply_gain(test_data, gain_db=6) - save_path = processor.save_processed_audio(gained, test_file, "gained") - if save_path: - print(f"Saved: {save_path}") - os.remove(save_path) - - print("\nTesting filters:") - filtered = processor.apply_low_pass_filter(test_data) - save_path = processor.save_processed_audio(filtered, test_file, "lowpass") - if save_path: - print(f"Saved: {save_path}") - os.remove(save_path) - - # Clean up - os.remove(test_file) - print("\nAudio processor test complete") - -def main(): - """Run all tests""" - print("DryBox Audio Features Test") - print("==========================") - - if not PYAUDIO_AVAILABLE: - print("\nNOTE: PyAudio not installed. Playback tests will be skipped.") - print("To install: sudo dnf install python3-devel portaudio-devel && pip install pyaudio") - - test_audio_player() - test_audio_processor() - - print("\nAll tests complete!") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/protocol_prototype/DryBox/test_audio_flow.py b/protocol_prototype/DryBox/test_audio_flow.py deleted file mode 100644 index cb7c7bf..0000000 --- a/protocol_prototype/DryBox/test_audio_flow.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -""" -Test to verify audio is flowing through the system -""" - -import os -import wave -import struct - -def check_audio_file(): - """Verify input.wav has actual audio content""" - print("Checking input.wav content...") - - with wave.open("wav/input.wav", 'rb') as wf: - # Read multiple frames to check for silence - total_frames = wf.getnframes() - print(f"Total frames: {total_frames}") - - # Check beginning - wf.setpos(0) - frames = wf.readframes(320) - samples = struct.unpack('320h', frames) - max_val = max(abs(s) for s in samples) - print(f"Frame 0 (beginning): max amplitude = {max_val}") - - # Check middle - wf.setpos(total_frames // 2) - frames = wf.readframes(320) - samples = struct.unpack('320h', frames) - max_val = max(abs(s) for s in samples) - print(f"Frame {total_frames//2} (middle): max amplitude = {max_val}") - - # Check near end - wf.setpos(total_frames - 640) - frames = wf.readframes(320) - samples = struct.unpack('320h', frames) - max_val = max(abs(s) for s in samples) - print(f"Frame {total_frames-640} (near end): max amplitude = {max_val}") - - # Find first non-silent frame - wf.setpos(0) - for i in range(0, total_frames, 320): - frames = wf.readframes(320) - if len(frames) < 640: - break - samples = struct.unpack('320h', frames) - max_val = max(abs(s) for s in samples) - if max_val > 100: # Not silence - print(f"\nFirst non-silent frame at position {i}") - print(f"First 10 samples: {samples[:10]}") - break - -def main(): - # Change to DryBox directory if needed - if os.path.basename(os.getcwd()) != 'DryBox': - if os.path.exists('DryBox'): - os.chdir('DryBox') - - check_audio_file() - - print("\nTo fix silence at beginning of file:") - print("1. Skip initial silence in phone_manager.py") - print("2. Or use a different test file") - print("3. Or trim the silence from input.wav") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/protocol_prototype/DryBox/test_audio_pipeline.py b/protocol_prototype/DryBox/test_audio_pipeline.py deleted file mode 100644 index ff9dbdf..0000000 --- a/protocol_prototype/DryBox/test_audio_pipeline.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 -""" -Test the audio pipeline (Codec2 + FSK) independently -""" - -import sys -import os -import wave -import struct -import numpy as np - -# Add parent directory to path -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from voice_codec import Codec2Wrapper, FSKModem, Codec2Mode, Codec2Frame - -def test_codec_only(): - """Test just the codec2 encode/decode""" - print("\n1. Testing Codec2 only...") - - codec = Codec2Wrapper(mode=Codec2Mode.MODE_1200) - - # Read test audio - with wave.open("wav/input.wav", 'rb') as wf: - # Read 320 samples (40ms at 8kHz) - frames = wf.readframes(320) - if len(frames) < 640: # 320 samples * 2 bytes - print("Not enough audio data") - return False - - # Convert to samples - samples = struct.unpack(f'{len(frames)//2}h', frames) - print(f"Input: {len(samples)} samples, first 10: {samples[:10]}") - - # Encode - encoded = codec.encode(frames) - if encoded: - print(f"Encoded: {len(encoded.bits)} bytes") - print(f"First 10 bytes: {encoded.bits[:10].hex()}") - else: - print("Encoding failed!") - return False - - # Decode - decoded = codec.decode(encoded) - if decoded is not None: - print(f"Decoded: type={type(decoded)}, len={len(decoded)}") - if hasattr(decoded, '__getitem__'): - print(f"First 10 samples: {list(decoded[:10])}") - - # Save decoded audio - with wave.open("wav/test_codec_only.wav", 'wb') as out: - out.setnchannels(1) - out.setsampwidth(2) - out.setframerate(8000) - if hasattr(decoded, 'tobytes'): - out.writeframes(decoded.tobytes()) - else: - # Convert to bytes - import array - arr = array.array('h', decoded) - out.writeframes(arr.tobytes()) - print("Saved decoded audio to wav/test_codec_only.wav") - return True - else: - print("Decoding failed!") - return False - -def test_full_pipeline(): - """Test the full Codec2 + FSK pipeline""" - print("\n2. Testing full pipeline (Codec2 + FSK)...") - - codec = Codec2Wrapper(mode=Codec2Mode.MODE_1200) - modem = FSKModem() - - # Read test audio - with wave.open("wav/input.wav", 'rb') as wf: - frames = wf.readframes(320) - if len(frames) < 640: - print("Not enough audio data") - return False - - # Encode with Codec2 - encoded = codec.encode(frames) - if not encoded: - print("Codec encoding failed!") - return False - print(f"Codec2 encoded: {len(encoded.bits)} bytes") - - # Modulate with FSK - modulated = modem.modulate(encoded.bits) - print(f"FSK modulated: {len(modulated)} float samples") - - # Demodulate - demodulated, confidence = modem.demodulate(modulated) - print(f"FSK demodulated: {len(demodulated)} bytes, confidence: {confidence:.2f}") - - if confidence < 0.5: - print("Low confidence demodulation!") - return False - - # Create frame for decoding - frame = Codec2Frame( - mode=Codec2Mode.MODE_1200, - bits=demodulated, - timestamp=0, - frame_number=0 - ) - - # Decode with Codec2 - decoded = codec.decode(frame) - if decoded is not None: - print(f"Decoded: type={type(decoded)}, len={len(decoded)}") - - # Save decoded audio - with wave.open("wav/test_full_pipeline.wav", 'wb') as out: - out.setnchannels(1) - out.setsampwidth(2) - out.setframerate(8000) - if hasattr(decoded, 'tobytes'): - out.writeframes(decoded.tobytes()) - else: - # Convert to bytes - import array - arr = array.array('h', decoded) - out.writeframes(arr.tobytes()) - print("Saved decoded audio to wav/test_full_pipeline.wav") - return True - else: - print("Codec decoding failed!") - return False - -def test_byte_conversion(): - """Test the byte conversion that happens in the protocol""" - print("\n3. Testing byte conversion...") - - # Create test PCM data - test_samples = [100, -100, 200, -200, 300, -300, 0, 0, 1000, -1000] - - # Method 1: array.tobytes() - import array - arr = array.array('h', test_samples) - bytes1 = arr.tobytes() - print(f"array.tobytes(): {len(bytes1)} bytes, hex: {bytes1.hex()}") - - # Method 2: struct.pack - bytes2 = struct.pack(f'{len(test_samples)}h', *test_samples) - print(f"struct.pack(): {len(bytes2)} bytes, hex: {bytes2.hex()}") - - # They should be the same - print(f"Bytes match: {bytes1 == bytes2}") - - # Test unpacking - unpacked = struct.unpack(f'{len(bytes1)//2}h', bytes1) - print(f"Unpacked: {unpacked}") - print(f"Matches original: {list(unpacked) == test_samples}") - - return True - -def main(): - print("Audio Pipeline Test") - print("=" * 50) - - # Change to DryBox directory if needed - if os.path.basename(os.getcwd()) != 'DryBox': - if os.path.exists('DryBox'): - os.chdir('DryBox') - - # Ensure wav directory exists - os.makedirs("wav", exist_ok=True) - - # Run tests - codec_ok = test_codec_only() - pipeline_ok = test_full_pipeline() - bytes_ok = test_byte_conversion() - - print("\n" + "=" * 50) - print("Test Results:") - print(f" Codec2 only: {'✅ PASS' if codec_ok else '❌ FAIL'}") - print(f" Full pipeline: {'✅ PASS' if pipeline_ok else '❌ FAIL'}") - print(f" Byte conversion: {'✅ PASS' if bytes_ok else '❌ FAIL'}") - - if codec_ok and pipeline_ok and bytes_ok: - print("\n✅ All tests passed!") - print("\nIf playback still doesn't work, check:") - print("1. Is the audio data actually being sent? (check debug logs)") - print("2. Is PyAudio stream format correct? (16-bit, 8kHz, mono)") - print("3. Is the volume turned up?") - else: - print("\n❌ Some tests failed - this explains the playback issue") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/protocol_prototype/DryBox/test_audio_setup.py b/protocol_prototype/DryBox/test_audio_setup.py deleted file mode 100755 index 9e37cde..0000000 --- a/protocol_prototype/DryBox/test_audio_setup.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify audio setup for DryBox -""" - -import os -import sys -import wave - -def check_audio_file(): - """Check if input.wav exists and has correct format""" - wav_path = "wav/input.wav" - - if not os.path.exists(wav_path): - print(f"❌ {wav_path} not found!") - return False - - try: - with wave.open(wav_path, 'rb') as wf: - channels = wf.getnchannels() - framerate = wf.getframerate() - sampwidth = wf.getsampwidth() - nframes = wf.getnframes() - duration = nframes / framerate - - print(f"✅ Audio file: {wav_path}") - print(f" Channels: {channels} {'✅' if channels == 1 else '❌ (should be 1)'}") - print(f" Sample rate: {framerate}Hz {'✅' if framerate == 8000 else '❌ (should be 8000)'}") - print(f" Sample width: {sampwidth * 8} bits {'✅' if sampwidth == 2 else '❌'}") - print(f" Duration: {duration:.2f} seconds") - print(f" Size: {os.path.getsize(wav_path) / 1024:.1f} KB") - - return channels == 1 and framerate == 8000 - - except Exception as e: - print(f"❌ Error reading {wav_path}: {e}") - return False - -def check_pyaudio(): - """Check if PyAudio is installed and working""" - try: - import pyaudio - p = pyaudio.PyAudio() - - # Check for output devices - output_devices = 0 - for i in range(p.get_device_count()): - info = p.get_device_info_by_index(i) - if info['maxOutputChannels'] > 0: - output_devices += 1 - - p.terminate() - - print(f"✅ PyAudio installed") - print(f" Output devices available: {output_devices}") - return True - - except ImportError: - print("❌ PyAudio not installed") - print(" To enable playback, run:") - print(" sudo dnf install python3-devel portaudio-devel") - print(" pip install pyaudio") - return False - except Exception as e: - print(f"❌ PyAudio error: {e}") - return False - -def check_dependencies(): - """Check all required dependencies""" - deps = { - 'PyQt5': 'PyQt5', - 'numpy': 'numpy', - 'struct': None, # Built-in - 'wave': None, # Built-in - } - - print("\nDependency check:") - all_good = True - - for module_name, pip_name in deps.items(): - try: - __import__(module_name) - print(f"✅ {module_name}") - except ImportError: - print(f"❌ {module_name} not found") - if pip_name: - print(f" Install with: pip install {pip_name}") - all_good = False - - return all_good - -def main(): - print("DryBox Audio Setup Test") - print("=" * 40) - - # Change to DryBox directory if needed - if os.path.basename(os.getcwd()) != 'DryBox': - if os.path.exists('DryBox'): - os.chdir('DryBox') - print(f"Changed to DryBox directory: {os.getcwd()}") - - print("\nChecking audio file...") - audio_ok = check_audio_file() - - print("\nChecking PyAudio...") - pyaudio_ok = check_pyaudio() - - print("\nChecking dependencies...") - deps_ok = check_dependencies() - - print("\n" + "=" * 40) - if audio_ok and deps_ok: - print("✅ Audio setup is ready!") - if not pyaudio_ok: - print("⚠️ Playback disabled (PyAudio not available)") - print(" Recording will still work") - else: - print("❌ Audio setup needs attention") - - print("\nUsage tips:") - print("1. Run the UI: python UI/main.py") - print("2. Click 'Run Automatic Test' or press Space") - print("3. Enable playback on Phone 2 with Ctrl+2") - print("4. You'll hear the decoded audio after handshake completes") - -if __name__ == "__main__": - main() \ No newline at end of file