Jag köpte en, i mitt tycke egentligen för dyr, Hifime S2 Digi (Savitech SA9227 chip) men vill man inte köpa ett helt USB-ljudkort med Toslink in så finns det inte så många alternativ som klarar av 192 kHz och är USB Audio Class 2 (man slipper installera drivrutin i moderna OS).
Jag stängde av SA9227 USB-ljudkortet med pwvucontrol för PipeWire.
Och SA9227 syns inte längre bland ljudenheterna i PipeWire:
- Kod: Markera allt
$ pw-cli list-objects | grep device.name
Man kan även stänga av SA9227 permanent i WirePlumber för PipeWire. ~/.config/wireplumber/wireplumber.conf.d/hifime_s2_digi.conf:
- Kod: Markera allt
monitor.alsa.rules = [
{
matches = [
{
# This matches the value of the 'device.name' property of the device.
device.name = "~alsa_card.usb-HiFimeDIY_SA9227_USB_Audio.*"
}
]
actions = {
update-props = {
# Apply all the desired device settings here.
device.disabled = true
}
}
}
]
Starta om WirePumber med: systemctl --user restart wireplumber.service
I ALSA förekommer en massa ljudenheter för SA9227 och de intressanta är:
- Kod: Markera allt
$ aplay -L | grep -A 1 -B 1 SA9227
hw:CARD=Audio,DEV=0
SA9227 USB Audio, USB Audio
Direct hardware device without any conversions
iec958:CARD=Audio,DEV=0
SA9227 USB Audio, USB Audio
IEC958 (S/PDIF) Digital Audio Output
Jag tycker att iec958-enheten inte borde påverkas av volyminställningen på ljudenheten, till skillnad från hw, då den är till för att skicka AC3-ljudströmmar och sådant över S/PDIF men den påverkas likväl så det spelar ingen roll om jag väljer hw eller iec958 i praktiken (i det här fallet i alla fall) utan volymen måste sättas till 100% i alsamixer likväl (välj ljudenheten för SA9227 med F6).
- Kod: Markera allt
#!/bin/bash
# can be another card number
if amixer -c 1 info | grep -q "HiFimeDIY SA9227 USB Audio"; then
amixer -c 1 set PCM 100%
else
echo "Didn't find HiFimeDIY SA9227 USB Audio to maximize volume."
fi
sox --no-dither --channels 2 --null --bits 16 test-sine.wav synth 2 sine 50 pad 3 4
in="hw:CARD=Audio,DEV=0"
out="hw:CARD=Audio,DEV=0"
aplay -v -D $out test-sine.wav &
sleep 1
arecord -v -D $in -c 2 -f S16_LE -r 48000 --duration=6 sample.wav
wait
./zeroed_offset_compare.py test-sine.wav sample.wav
Jag upptäckte att den senast uppspelade samplen låg kvar i ljudbufferten och spelades in när arecord borde spelat in 0:or. Om uppspelning av testfil påbörjas och avslutas under inspelning så får man dessutom lite skräp i början och slutet av det uppspelade ljudet vilket omöjliggör en enkel jämförelse av ljudmaterialet för att bedöma om återgivningen är bittrogen. Lösningen är att vända på det och spela upp ljud med tystnad i början och slutet längre tid än man spelar in.
Jag skrev ett program i Python för att jämföra ljudfilerna och det ignorerar inledande och avslutande tystnad (nollor, därav --no-dither för testfilen).
- Kod: Markera allt
$ ./zeroed_offset_compare.py test-sine.wav sample.wav
Filtypen är inte tillåten att bifoga så här kommer programmet:
- Kod: Markera allt
#!/usr/bin/env python3
"""
https://python-soundfile.readthedocs.io/
https://numpy.org/doc/stable/reference/arrays.ndarray.html
"""
import sys
import argparse
import numpy as np
import soundfile as sf
# type must be one of ['float32', 'float64', 'int16', 'int32']
SAMPLE_TYPE = "float32"
def read_snd(filename: str) -> tuple[np.ndarray, int]:
"""Takes filename, returns 2D array with samples and number of channels."""
# 1D is just a list of samples, 2D is lists with a sample from each channel in a list.
try:
data, rate = sf.read(filename, dtype=SAMPLE_TYPE, always_2d=True)
except sf.LibsndfileError as e:
print(e)
sys.exit(-1)
# data.shape is a tuple with (samples, channels) when 2D
print(f"{filename}:")
print(f"\tsample rate = {rate}")
print(f"\tnumber of channels = {data.shape[1]}")
length = data.shape[0] / rate
print(f"\tlength = {length}s ({data.shape[0]} samples per channel)")
return data, data.data.shape[1]
def skip_zeroes(data: np.ndarray, index: int, channel: int) -> int:
"""Returns index for next non-zero sample."""
length = data.shape[0]
while index < length and data[index][channel] == 0:
index += 1
return index
def compare(frst: np.ndarray, scnd: np.ndarray, channel: int) -> int:
"""Compare a single audio channel in a 2D array of channels."""
fi = 0
si = 0
# skip leading zeroes
fi = skip_zeroes(frst, fi, channel)
si = skip_zeroes(scnd, si, channel)
flen = frst.shape[0]
slen = scnd.shape[0]
if fi == flen:
print("First file is empty or zeroed.")
return 1
if si == slen:
print("Second file is empty or zeroed.")
return 1
differences = 0
while fi < flen and si < slen:
if frst[fi][channel] != scnd[si][channel]:
differences += 1
fi += 1
si += 1
if differences > 0:
print(f"Channel {channel} differ by at least {differences} samples.")
return 1
# skip trailing zeroes
fi = skip_zeroes(frst, fi, channel)
si = skip_zeroes(scnd, si, channel)
if fi < flen or si < slen:
print(f"Channel {channel} have different non-zeroed length.")
return 1
print(f"Channel {channel} are the same, ignoring zeroes at the beginning and the end.")
return 0
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description="Comparing two sound files but ignoring leading and trailing zeroes.",
epilog="Returns 0 if equal or number of channels that differ, -1 for error.")
parser.add_argument("first", type=str, help="sound file")
parser.add_argument("second", type=str, help="sound file to compare with")
parser.add_argument("--channel", type=int, default=-1, help="single channel to compare, starting with 0")
args = parser.parse_args()
first, fch = read_snd(args.first)
second, sch = read_snd(args.second)
print("")
if args.channel == -1 and fch != sch:
print("Number of audio channels differ; select one with --channel.")
sys.exit(-1)
if args.channel != -1:
if fch > args.channel and sch > args.channel:
ret = compare(first, second, args.channel)
sys.exit(ret)
else:
print("Invalid --channel number to compare.")
sys.exit(-1)
ret = 0
for ch in range(fch):
ret += compare(first, second, ch)
if ret > 0:
print("\nChecked volume settings?")
sys.exit(ret)