Cleaning a bit
All checks were successful
/ mirror (push) Successful in 5s

This commit is contained in:
stcb 2025-07-06 23:36:41 +02:00
parent 6b517f6a46
commit a6cd9632ee
11 changed files with 0 additions and 1080 deletions

View File

@ -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

View File

@ -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=<class 'numpy.ndarray'>, 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.

View File

@ -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)!

View File

@ -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.

View File

@ -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!

View File

@ -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)

View File

@ -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!

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()