Pour vous permettre de faire vos premiers pas dans l'optimisation de Diretta en mode DDS, voici un programme python qui calcule une proposition de fichier de paramètres adaptés. N'oubliez pas de démarrer à très bas volume pour ne pas griller vos enceintes. Je dégage toute responsabilité...
Code :
#!/usr/bin/env python3
"""
Diretta DDS Configuration Calculator
Based on real working configuration with adaptive CycleTime
Optimized settings: periodMin=4, periodMax=8, syncBufferCount=6
Usage:
python3 diretta_calc.py # Default optimized settings
python3 diretta_calc.py --period-max 6 # Lower latency
python3 diretta_calc.py --pcm # PCM instead of DSD
python3 diretta_calc.py --help # Show all options
"""
import argparse
from typing import Dict, List, Tuple
class DirettaCalculator:
"""Calculate Diretta configuration parameters"""
# Ethernet frame overhead
ETH_HEADER = 14
DIRETTA_HEADER = 2
VLAN_OVERHEAD = 4
FCS = 4
def __init__(self, mtu: int = 9024):
self.mtu = mtu
self.overhead = self.ETH_HEADER + self.DIRETTA_HEADER + self.VLAN_OVERHEAD
self.available_payload = mtu - self.overhead
def calculate_frame_params(self, sample_rate: int, is_dsd: bool = True) -> Dict:
"""
Calculate frame parameters for a given sample rate
Args:
sample_rate: Sample rate in Hz (e.g., 12288000 for DSD256×48k)
is_dsd: True for DSD, False for PCM
Returns:
Dictionary with frame parameters
"""
# Bytes per sample (stereo)
if is_dsd:
bytes_per_sample = 0.25 # 1 bit/sample × 2 channels = 2 bits = 0.25 bytes
else:
bytes_per_sample = 6 # 24 bit/sample × 2 channels = 48 bits = 6 bytes
# Calculate samples that fit in available payload
# Align to 64-bit (8 byte) boundary
audio_bytes_max = (int(self.available_payload / 8) * 8)
samples_per_frame = int(audio_bytes_max / bytes_per_sample)
# Actual audio bytes after alignment
audio_bytes = int(samples_per_frame * bytes_per_sample)
# Calculate cycle time (time to transmit one frame)
cycle_time_us = (samples_per_frame / sample_rate) * 1_000_000
# Packet rate (packets per second)
packet_rate_hz = sample_rate / samples_per_frame
# Total frame size
total_frame_size = self.ETH_HEADER + self.DIRETTA_HEADER + audio_bytes + self.FCS
return {
'sample_rate': sample_rate,
'sample_rate_mhz': sample_rate / 1_000_000,
'samples_per_frame': samples_per_frame,
'audio_bytes': audio_bytes,
'total_frame_size': total_frame_size,
'cycle_time_us': cycle_time_us,
'cycle_time_ms': cycle_time_us / 1000,
'packet_rate_hz': packet_rate_hz,
'bytes_per_sample': bytes_per_sample,
'is_dsd': is_dsd,
}
def find_target_cycle_time(self, base_rates: List[int], is_dsd: bool = True) -> Tuple[int, Dict]:
"""
Find a target CycleTime that works well across multiple base rates
Args:
base_rates: List of base sample rates (e.g., [44100, 48000])
is_dsd: True for DSD, False for PCM
Returns:
Tuple of (recommended_cycle_time_us, analysis_dict)
"""
results = {}
for base_rate in base_rates:
# For DSD256, multiply by 256; for PCM 8fs, multiply by 8
multiplier = 256 if is_dsd else 8
sample_rate = base_rate * multiplier
params = self.calculate_frame_params(sample_rate, is_dsd)
results[base_rate] = params
# Find the cycle time that's closest to average
cycle_times = [p['cycle_time_us'] for p in results.values()]
avg_cycle = sum(cycle_times) / len(cycle_times)
# Round to nearest 5 microseconds for cleaner config
target_cycle = int(round(avg_cycle / 5) * 5)
return target_cycle, results
def print_header():
"""Print program header"""
print("""
╔═══════════════════════════════════════════════════════════════════════════════╗
║ DIRETTA DDS CONFIGURATION CALCULATOR ║
║ Low Latency Optimizer ║
╚═══════════════════════════════════════════════════════════════════════════════╝
""")
def print_analysis_table(calculator: DirettaCalculator, configs: List[Dict]):
"""Print analysis table for different sample rates"""
print(f"\n{'='*100}")
print(f"SAMPLE RATE ANALYSIS (MTU = {calculator.mtu} bytes)")
print(f"{'='*100}")
print(f"\n{'Format':<20} {'Sample Rate':<15} {'Samples/':<12} {'Cycle':<12} {'Packet':<12} {'Audio':<10}")
print(f"{'':20} {'(MHz)':<15} {'Frame':<12} {'(μs)':<12} {'Rate (Hz)':<12} {'Bytes':<10}")
print("-" * 100)
for config in configs:
params = calculator.calculate_frame_params(config['sample_rate'], config['is_dsd'])
print(f"{config['name']:<20} "
f"{params['sample_rate_mhz']:>8.6f} "
f"{params['samples_per_frame']:>8} "
f"{params['cycle_time_us']:>8.1f} "
f"{params['packet_rate_hz']:>8.2f} "
f"{params['audio_bytes']:>6}")
def print_latency_table(calculator: DirettaCalculator, configs: List[Dict],
period_min: int, period_max: int, sync_buffer: int):
"""Print detailed latency table for all configurations"""
print(f"\n{'='*100}")
print(f"LATENCY COMPARISON TABLE")
print(f"{'='*100}")
buffer_configs = [
("Ultra Conservative", 6, 12, 10),
("Conservative", 5, 10, 8),
("Optimized (recommended)", 4, 8, 6),
("Lower Latency", 3, 6, 5),
("Aggressive", 3, 6, 4),
]
# Highlight the current settings
current_config = (f"Your Settings", period_min, period_max, sync_buffer)
print(f"\n{'Configuration':<25} {'Periods':<12} {'Sync':<8} {'48kHz':<12} {'44.1kHz':<12} {'Notes'}")
print(f"{'':25} {'(min/max)':<12} {'Buf':<8} {'Latency':<12} {'Latency':<12}")
print("-" * 100)
# Find DSD256 configs for latency calculation
dsd256_48k = None
dsd256_441k = None
for config in configs:
if config['sample_rate'] == 12288000:
dsd256_48k = calculator.calculate_frame_params(config['sample_rate'], True)
elif config['sample_rate'] == 11289600:
dsd256_441k = calculator.calculate_frame_params(config['sample_rate'], True)
for name, pmin, pmax, sync in buffer_configs:
lat_48k = dsd256_48k['cycle_time_ms'] * pmax if dsd256_48k else 0
lat_441k = dsd256_441k['cycle_time_ms'] * pmax if dsd256_441k else 0
is_current = (pmin == period_min and pmax == period_max and sync == sync_buffer)
marker = ">>> " if is_current else " "
notes = ""
if pmax >= 10:
notes = "Very stable"
elif pmax == 8:
notes = "Good balance"
elif pmax == 6:
notes = "Monitor closely"
else:
notes = "Risky!"
print(f"{marker}{name:<21} "
f"{pmin:>3}/{pmax:<7} "
f"{sync:>4} "
f"{lat_48k:>7.2f} ms "
f"{lat_441k:>7.2f} ms "
f"{notes}")
# Add your current settings if they're custom
if not any(pmin == period_min and pmax == period_max and sync == sync_buffer
for _, pmin, pmax, sync in buffer_configs):
lat_48k = dsd256_48k['cycle_time_ms'] * period_max if dsd256_48k else 0
lat_441k = dsd256_441k['cycle_time_ms'] * period_max if dsd256_441k else 0
print(f">>> Your Settings "
f"{period_min:>3}/{period_max:<7} "
f"{sync_buffer:>4} "
f"{lat_48k:>7.2f} ms "
f"{lat_441k:>7.2f} ms "
f"Custom")
def print_improvement_comparison():
"""Show improvement from conservative to optimized settings"""
print(f"\n{'='*100}")
print("LATENCY IMPROVEMENT: Conservative → Optimized")
print(f"{'='*100}")
print("\nConservative Settings (periodMin=5, periodMax=10, syncBufferCount=8):")
print(" 48kHz family: ~29.4 ms")
print(" 44.1kHz family: ~32.0 ms")
print("\nOptimized Settings (periodMin=4, periodMax=8, syncBufferCount=6):")
print(" 48kHz family: ~23.5 ms (↓ 5.9 ms, 20% faster)")
print(" 44.1kHz family: ~25.6 ms (↓ 6.4 ms, 20% faster)")
print("\nWhat Changed:")
print(" • periodMax: 10 → 8 (2 fewer periods)")
print(" • periodMin: 5 → 4 (tighter ratio)")
print(" • syncBufferCount: 8 → 6 (2 fewer buffers)")
print(" • CycleTime: 2935 μs (unchanged - proven stable)")
print("\nStability Analysis:")
print(" ✓ periodMin/Max ratio: 4:8 = 1:2 (good balance)")
print(" ✓ Total buffering: 8 periods (reasonable)")
print(" ✓ Sync buffer: 6 (adequate for clock drift)")
print(" ⚠ Monitor for underruns on congested networks")
def generate_config(cycle_time: int, period_min: int = 4, period_max: int = 8,
period_size_min: int = 2048, period_size_max: int = 8192,
sync_buffer: int = 6, latency_buffer: int = 0,
thred_mode: int = 257, interface: str = 'enp5s0',
cpu_send: int = 1, cpu_other: int = 2,
margin_us: int = 20) -> str:
"""Generate Diretta configuration file content"""
cycle_min = cycle_time - margin_us
config = f"""[global]
Interface={interface}
TargetProfileLimitTime=0
ThredMode={thred_mode}
InfoCycle=100000
FlexCycle=max
CycleTime={cycle_time}
CycleMinTime={cycle_min}
Debug=stdout
periodMax={period_max}
periodMin={period_min}
periodSizeMax={period_size_max}
periodSizeMin={period_size_min}
syncBufferCount={sync_buffer}
alsaUnderrun=enable
unInitMemDet=disable
CpuSend={cpu_send}
CpuOther={cpu_other}
LatencyBuffer={latency_buffer}
"""
return config
def print_usage_guide():
"""Print usage and monitoring guide"""
print(f"\n{'='*100}")
print("USAGE GUIDE")
print(f"{'='*100}")
print("""
1. SAVE CONFIGURATION
sudo nano ~/DirettaAlsaHost/setting.inf
(paste the configuration shown above)
2. RESTART DIRETTA
sudo systemctl restart diretta_sync_host.service
3. MONITOR OPERATION
journalctl -u diretta_sync_host -f
Look for these indicators:
✓ "start cycle=XXXXusec" - shows actual cycle time
✓ "info rcv" - timing statistics (should be stable)
✗ "Buffer underrun" - indicates buffers too small
✗ Timing jitter >100μs - network congestion
4. VERIFY EXPECTED BEHAVIOR
• CycleTime will auto-adjust per sample rate
• 48kHz: ~2938 μs (340 packets/sec)
• 44.1kHz: ~3198 μs (313 packets/sec)
• This is normal and expected!
5. TROUBLESHOOTING
Buffer underruns occur:
→ Increase periodMax: --period-max 10
→ Increase syncBuffer: --sync-buffer 8
Want lower latency:
→ Decrease periodMax: --period-max 6
→ Decrease syncBuffer: --sync-buffer 5
→ Monitor closely for underruns!
6. NETWORK OPTIMIZATION
• Ensure jumbo frames enabled: ip link set enp5s0 mtu 9024
• Disable offloading: ethtool -K enp5s0 gro off lro off
• Check no packet loss: ethtool -S enp5s0 | grep errors
• Isolate Diretta traffic on dedicated network
""")
def print_key_concepts():
"""Explain key concepts"""
print(f"\n{'='*100}")
print("KEY CONCEPTS EXPLAINED")
print(f"{'='*100}")
print("""
CYCLE TIME (Adaptive)
• You configure: CycleTime=2935 (target value)
• Diretta adjusts: Actual cycle varies by sample rate
• Purpose: Maintains constant packet size (9024 bytes)
• Why adaptive: Different sample rates need different timing
PERIOD BUFFERS
• periodMin/Max: Range of buffering depth
• Each period = one CycleTime
• periodMax determines total latency
• Example: 8 periods × 2.938 ms = 23.5 ms latency
SYNC BUFFER
• syncBufferCount: Additional buffer for clock sync
• Compensates for clock drift between sender/receiver
• Too small: Sync issues, clicks/pops
• Too large: Added latency
PACKET STRUCTURE
• Total frame: 9024 bytes (jumbo frame)
• Overhead: 20 bytes (ethernet + diretta headers)
• Audio payload: ~9004 bytes
• Samples per packet: Varies by sample rate
LATENCY CALCULATION
• Total latency = CycleTime × periodMax
• Does NOT include:
- DAC processing time
- Network propagation delay (usually <1ms)
- USB/SPDIF converter latency
• Actual end-to-end: Add ~2-5ms for complete chain
""")
def main():
parser = argparse.ArgumentParser(
description='Diretta DDS Configuration Calculator - Optimized Settings',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Default optimized config
%(prog)s --period-max 6 --sync-buffer 5 # Lower latency (experimental)
%(prog)s --period-max 10 --sync-buffer 8 # More conservative
%(prog)s --pcm # PCM instead of DSD
%(prog)s --mtu 1500 # Standard ethernet MTU
%(prog)s --config-only # Just show config file
"""
)
parser.add_argument('--mtu', type=int, default=9024,
help='MTU size (default: 9024 for jumbo frames)')
parser.add_argument('--pcm', action='store_true',
help='Calculate for PCM instead of DSD')
parser.add_argument('--period-min', type=int, default=4,
help='periodMin value (default: 4)')
parser.add_argument('--period-max', type=int, default=8,
help='periodMax value (default: 8)')
parser.add_argument('--sync-buffer', type=int, default=6,
help='syncBufferCount value (default: 6)')
parser.add_argument('--latency-buffer', type=int, default=0,
help='LatencyBuffer value (default: 0)')
parser.add_argument('--period-size-min', type=int, default=2048,
help='periodSizeMin value (default: 2048)')
parser.add_argument('--period-size-max', type=int, default=8192,
help='periodSizeMax value (default: 8192)')
parser.add_argument('--config-only', action='store_true',
help='Only generate config, skip analysis')
parser.add_argument('--thred-mode', type=int, default=257,
help='ThredMode value (default: 257)')
parser.add_argument('--interface', type=str, default='enp5s0',
help='Network interface (default: enp5s0)')
parser.add_argument('--cpu-send', type=int, default=1,
help='CPU core for send thread (default: 1)')
parser.add_argument('--cpu-other', type=int, default=2,
help='CPU core for other threads (default: 2)')
args = parser.parse_args()
calculator = DirettaCalculator(mtu=args.mtu)
is_dsd = not args.pcm
format_type = "DSD" if is_dsd else "PCM"
# Define sample rate configurations
if is_dsd:
configs = [
{'name': 'DSD256 × 48kHz', 'sample_rate': 48000 * 256, 'is_dsd': True},
{'name': 'DSD256 × 44.1kHz', 'sample_rate': 44100 * 256, 'is_dsd': True},
{'name': 'DSD128 × 48kHz', 'sample_rate': 48000 * 128, 'is_dsd': True},
{'name': 'DSD128 × 44.1kHz', 'sample_rate': 44100 * 128, 'is_dsd': True},
{'name': 'DSD64 × 48kHz', 'sample_rate': 48000 * 64, 'is_dsd': True},
{'name': 'DSD64 × 44.1kHz', 'sample_rate': 44100 * 64, 'is_dsd': True},
]
else:
configs = [
{'name': 'PCM 384kHz (8×48k)', 'sample_rate': 48000 * 8, 'is_dsd': False},
{'name': 'PCM 352.8kHz (8×44.1k)', 'sample_rate': 44100 * 8, 'is_dsd': False},
{'name': 'PCM 192kHz (4×48k)', 'sample_rate': 48000 * 4, 'is_dsd': False},
{'name': 'PCM 176.4kHz (4×44.1k)', 'sample_rate': 44100 * 4, 'is_dsd': False},
{'name': 'PCM 96kHz (2×48k)', 'sample_rate': 48000 * 2, 'is_dsd': False},
{'name': 'PCM 88.2kHz (2×44.1k)', 'sample_rate': 44100 * 2, 'is_dsd': False},
]
# Find target cycle time
base_rates = [44100, 48000]
target_cycle_us, analysis = calculator.find_target_cycle_time(base_rates, is_dsd)
# Print analysis unless config-only
if not args.config_only:
print_header()
print(f"Format Type: {format_type}")
print(f"MTU: {args.mtu} bytes")
print(f"Target CycleTime: {target_cycle_us} μs")
print(f"\nBuffer Configuration:")
print(f" periodMin: {args.period_min}")
print(f" periodMax: {args.period_max}")
print(f" syncBufferCount: {args.sync_buffer}")
print_analysis_table(calculator, configs)
print_latency_table(calculator, configs, args.period_min, args.period_max, args.sync_buffer)
if args.period_min == 4 and args.period_max == 8 and args.sync_buffer == 6:
print_improvement_comparison()
print_key_concepts()
# Generate and display configuration
print(f"\n{'='*100}")
print(f"CONFIGURATION FILE ({format_type} mode)")
print(f"{'='*100}")
# Calculate expected latencies
dsd256_48k = calculator.calculate_frame_params(12288000 if is_dsd else 384000, is_dsd)
dsd256_441k = calculator.calculate_frame_params(11289600 if is_dsd else 352800, is_dsd)
lat_48k = dsd256_48k['cycle_time_ms'] * args.period_max
lat_441k = dsd256_441k['cycle_time_ms'] * args.period_max
print(f"\nExpected Latency:")
print(f" 48kHz family: ~{lat_48k:.1f} ms")
print(f" 44.1kHz family: ~{lat_441k:.1f} ms")
config = generate_config(
cycle_time=target_cycle_us,
period_min=args.period_min,
period_max=args.period_max,
period_size_min=args.period_size_min,
period_size_max=args.period_size_max,
sync_buffer=args.sync_buffer,
latency_buffer=args.latency_buffer,
thred_mode=args.thred_mode,
interface=args.interface,
cpu_send=args.cpu_send,
cpu_other=args.cpu_other,
)
print(config)
if not args.config_only:
print_usage_guide()
print(f"\n{'='*100}")
print("NEXT STEPS")
print(f"{'='*100}")
print(f"""
1. Copy the configuration above to ~/DirettaAlsaHost/setting.inf
2. Restart Diretta: sudo systemctl restart diretta_sync_host.service
3. Monitor: journalctl -u diretta_sync_host -f
4. Verify ~4ms latency achieved
For different configurations:
• Lower latency: python3 {__file__} --period-max 6 --sync-buffer 5
• More conservative: python3 {__file__} --period-max 10 --sync-buffer 8
• Standard MTU: python3 {__file__} --mtu 1500
• PCM mode: python3 {__file__} --pcm
""")
if __name__ == "__main__":
main()
![[Image: banniereforumhifi.jpg]](https://i.postimg.cc/wxwWFvzj/banniereforumhifi.jpg)

