iOS-WAV-Exporte prüfen: Frames, Kanalreihenfolge und Sampletreue
Die exportierte WAV-Aufnahme lässt sich abspielen, aber am Ende fehlt ein Stück. Beide Kanäle enthalten Ton, doch links und rechts sind vertauscht. Solche Fehler lassen sich durch Probehören über einen Remote-Desktop nur schlecht eingrenzen. Auf einem Cloud-Mac ist es zuverlässiger, zunächst ein fest definiertes Signal durch den vollständigen Ablauf „Datei schreiben, schließen, erneut lesen, vergleichen“ zu schicken und die Prüfungen anschließend an den iOS-Exporter anzubinden. So werden die Ergebnisse leichter reproduzierbar.
Zuerst den Dateivertrag festlegen
Dieses Beispiel prüft ausschließlich einen Fließkomma-PCM-Pfad ohne Resampling und ohne Pegeländerung: 48000 Hz Abtastrate, zwei Kanäle und eine Länge von 4800 Frames, also 0.1 Sekunden. Der linke Kanal enthält durchgehend den Wert 0.25, der rechte den Wert -0.5. Die unterschiedlichen Werte machen vertauschte oder zusammengemischte Kanäle ebenso erkennbar wie ein Monosignal, das auf beide Kanäle kopiert wurde.
Ein Audio-Frame enthält die Samples aller Kanäle für denselben Zeitpunkt. Hier hat also jeder Kanal 4800 Samples; die Zahl 4800 gilt nicht für beide Kanäle zusammen. Header, zusätzliche Blöcke und das Kodierungsformat beeinflussen die Dateigröße. Die Bytezahl ist deshalb kein direkter Ersatz für die Anzahl der Frames.
Dies ist eine Abnahmeprüfung auf Dateiebene. Sie benötigt keine Lautsprecher und bestätigt nicht, dass die Wiedergaberoute, Bluetooth-Geräte oder die Aufnahme auf einem physischen Gerät korrekt funktionieren. Das konstante Gleichsignal dient ausschließlich der numerischen Prüfung, nicht dem Probehören.
Den minimalen Ablauf auf dem Cloud-Mac ausführen
Führen Sie zunächst xcrun --find swift aus, um sicherzustellen, dass die aktuell ausgewählten Entwicklungswerkzeuge Swift finden. Speichern Sie den folgenden Code als wav-check.swift und starten Sie ihn in einem beschreibbaren Verzeichnis mit xcrun swift wav-check.swift. Das Skript legt ein eigenes temporäres Verzeichnis an und überschreibt keine vorhandenen Dateien. Schlägt eine Prüfung fehl, endet es mit einem Rückgabecode ungleich null.
import Foundation
import AVFoundation
let root = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(
at: root, withIntermediateDirectories: true, attributes: nil
)
defer { try? FileManager.default.removeItem(at: root) }
let url = root.appendingPathComponent("fixture.wav")
let frames: AVAudioFrameCount = 4800
let format = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: 48000,
channels: 2,
interleaved: false
)!
let source = AVAudioPCMBuffer(
pcmFormat: format, frameCapacity: frames
)!
source.frameLength = frames
let input = source.floatChannelData!
for i in 0..<Int(frames) {
input[0][i] = 0.25
input[1][i] = -0.5
}
func writeFixture() throws {
let settings: [String: Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVSampleRateKey: 48000.0,
AVNumberOfChannelsKey: 2,
AVLinearPCMBitDepthKey: 32,
AVLinearPCMIsFloatKey: true,
AVLinearPCMIsBigEndianKey: false,
AVLinearPCMIsNonInterleaved: false
]
let writer = try AVAudioFile(
forWriting: url,
settings: settings,
commonFormat: .pcmFormatFloat32,
interleaved: false
)
try writer.write(from: source)
}
try writeFixture()
let reader = try AVAudioFile(
forReading: url,
commonFormat: .pcmFormatFloat32,
interleaved: false
)
precondition(reader.fileFormat.sampleRate == 48000)
precondition(reader.fileFormat.channelCount == 2)
precondition(reader.length == AVAudioFramePosition(frames))
let output = AVAudioPCMBuffer(
pcmFormat: reader.processingFormat,
frameCapacity: frames + 1
)!
try reader.read(into: output)
precondition(output.frameLength == frames)
let channels = output.floatChannelData!
for channel in 0..<2 {
let expected: Float = channel == 0 ? 0.25 : -0.5
for i in 0..<Int(output.frameLength) {
let value = channels[channel][i]
precondition(value.isFinite)
precondition(abs(value - expected) <= 0.000001)
}
}
print("PASS: 4800 frames, 2 channels, samples verified")
Das Schreibobjekt liegt in einer eigenen Funktion, damit seine Lebensdauer vor dem erneuten Lesen endet und die Datei vollständig abgeschlossen wird. Lassen Sie ein noch nicht geschlossenes Schreibobjekt und ein Leseobjekt nicht gleichzeitig auf die Ausgabedatei zugreifen. Sonst kann eine zu kurz eingelesene Datei fälschlich als abgeschnittener Export gewertet werden.
Das Beispiel unterscheidet zwischen dem Dateiformat auf dem Datenträger und dem Format im Arbeitsspeicher: Im WAV sind die Kanalsamples verschachtelt gespeichert, während der Verarbeitungspuffer nicht verschachtelte Fließkommadaten enthält. Beim Zugriff auf die Kanalzeiger ist processingFormat maßgeblich. Aus der Dateiendung allein lässt sich die Speicheranordnung nicht ableiten.
Die Prüfungen an den tatsächlichen Exporter anbinden
Wenn das minimale Skript erfolgreich durchläuft, behalten Sie den Lese- und Prüfungsteil bei und ersetzen writeFixture() durch die tatsächliche Exportschnittstelle des Projekts. Arbeitet diese asynchron, muss der Test auf den Erfolgs-Callback warten. Erfolg muss dabei bedeuten, dass die Datei fertig geschrieben ist — nicht nur, dass der Auftrag in einer Warteschlange steht.
Blockgrenzen sind wichtiger als ein einziger Schreibvorgang
Decken Sie mindestens Längen von 1, 1023, 1024, 1025 und 4800 Frames ab. Teilen Sie anschließend dasselbe Signal auf mehrere unterschiedlich lange Puffer auf und prüfen Sie, ob die Anzahl der ausgegebenen Frames der Summe der gültigen Frames aller Blöcke entspricht. Der letzte Block ist meist nicht vollständig gefüllt. Beim Schreiben zählt deshalb frameLength, nicht frameCapacity.
Hilfreich ist außerdem ein Test mit Impulsen am Anfang und am Ende: Geben Sie dem ersten und dem letzten Frame jeweils einen anderen Amplitudenwert und setzen Sie alle übrigen auf null. Damit lassen sich vorangestellte Stille, ein verlorener letzter Frame und doppelte Abschnitte beim Zusammenfügen erkennen. Konstante Pegel eignen sich für die Prüfung der Kanalzuordnung, Impulse für die Prüfung der Positionen. Die beiden Testdatensätze ersetzen einander nicht.
Den Plattformpfad über ein Test-Target abnehmen
Das Skript läuft unter macOS und bestätigt lediglich, dass die Datei-Fixture und die Vergleichslogik funktionieren. Nehmen Sie den gemeinsam genutzten Exportcode in ein iOS-Test-Target auf und führen Sie dieselben Prüfungen im Simulator aus. Erst damit wird der tatsächliche Aufrufpfad der App abgedeckt. Für Teile, die eine Audiosession oder den Mikrofoneingang betreffen, sind zusätzlich separate Tests auf einem physischen Gerät einzuplanen.
Für Integer-PCM gelten eigene Fehlertoleranzen
Werden Fließkomma-Samples als Integer-PCM gespeichert, findet eine Quantisierung statt. Eine Prüfung auf exakte Gleichheit ist dann nicht mehr geeignet. Legen Sie zuerst den Normalisierungsbereich und die Rundungsregel fest und bestimmen Sie anschließend die Toleranz anhand des niederwertigsten Bits. Ergänzen Sie außerdem Samples nahe der Vollaussteuerung sowie negative Werte und Nullwerte, um zu prüfen, ob das Clipping dem vereinbarten Verhalten entspricht.
Resampling erfordert einen eigenen Vertrag. Filter können eine Verzögerung verursachen; außerdem hängt die Zahl der Ausgabe-Frames von der Rundung und vom Leeren interner Restpuffer am Ende ab. Die Prüfung auf gleiche Ein- und Ausgabelänge aus diesem Beispiel lässt sich daher nicht direkt übernehmen. Sichern Sie zunächst den unveränderten Exportpfad zuverlässig ab und testen Sie erst danach die Formatkonvertierung, damit nicht mehrere Einflussgrößen vermischt werden.
Bei Fehlern schichtweise Diagnosedaten sammeln
Stimmt die Framezahl nicht, prüfen Sie zuerst die gültige Länge des letzten Blocks und den Zeitpunkt des asynchronen Abschlusses. Ist die Framezahl korrekt, aber links und rechts sind vertauscht, kontrollieren Sie die Kanalzuordnung. Sind sämtliche Werte falsch, prüfen Sie die verschachtelte beziehungsweise nicht verschachtelte Anordnung und die Konvertierung zwischen Fließkomma- und Integer-Formaten. Erhöhen Sie bei einem Fehlschlag nicht einfach die Toleranz: Damit könnte ein Formatfehler unbemerkt als bestandener Test durchgehen.
Protokollieren Sie bei jedem Fehlschlag die Folge der Eingabeblocklängen, das Dateiformat, das Verarbeitungsformat, die tatsächliche Framezahl, die maximale Abweichung und die Position des ersten fehlerhaften Samples. Bewahren Sie als Testartefakte nur synthetisches Audio auf, damit keine echten Aufnahmen in die Pipeline gelangen. Die Abnahmekriterien sollten letztlich in diesen vergleichbaren Zahlen bestehen — nicht in der bloßen Aussage „Die Datei lässt sich abspielen“.
Häufig gestellte Fragen
Lassen sich WAV-Dateien ohne Audioausgabegerät prüfen?
Ja. AVAudioFile benötigt für das Lesen und Schreiben keine Lautsprecher. Framezahl, Kanalfolge und Samplefehler sind offline prüfbar; Wiedergaberouting und Höreindruck erfordern eigene Gerätetests.
Müssen die Samples jedes WAV-Exports exakt übereinstimmen?
Nein. Integer-PCM erzeugt Quantisierungsfehler. Resampling und Pegeländerungen verändern Samples ebenfalls. Toleranzen und Framezahlregeln müssen zum jeweiligen Verarbeitungspfad passen.
Den nächsten Build auf einer Cloud-Mac-Workstation ausführen
Ein ganzer Mac mini exklusiv für Sie – keine virtuelle Maschine. Wählen Sie die Laufzeit passend zu Ihrem Vorhaben. Ob die Workstation verfügbar ist, sehen Sie in Echtzeit in der Konsole.
Cloud-Mac jetzt mieten