雲端 Mac 的 iOS WAV 匯出驗收:音訊影格數、聲道順序與取樣保真

雲端 Mac 的 iOS WAV 匯出驗收:音訊影格數、聲道順序與取樣保真

錄音匯出的 WAV 能正常播放,尾端卻少了一段;左右聲道都有聲音,順序卻顛倒了。這類問題不適合靠遠端桌面試聽來定位。在雲端 Mac 上,先用固定訊號跑完「寫入檔案、關閉、讀回、比較」的完整流程,再把斷言接到 iOS 匯出器,結果會更容易重現。

先定義檔案契約

本例只驗證不重新取樣、不調整增益的浮點 PCM 路徑:取樣率為 48000Hz,雙聲道,長度為 4800 個音訊影格,也就是 0.1 秒。左聲道固定為 0.25,右聲道固定為 -0.5。兩側的值不同,才能辨識聲道互換、混音或單聲道複製。

一個音訊影格包含同一時間點各聲道的取樣值,因此這裡每個聲道各有 4800 個取樣值,不是兩個聲道合計 4800 個。檔案標頭、附加區塊和編碼格式都會影響檔案大小,不能直接用位元組數代替影格數。

這是檔案層級的驗收流程,不依賴喇叭,也無法證明播放路由、藍牙裝置或實機錄音運作正常。固定直流訊號只用於數值檢查,不用於試聽。

在雲端 Mac 跑通最小驗證流程

先執行 xcrun --find swift,確認目前的開發工具能找到 Swift。將下列程式碼儲存為 wav-check.swift,並在可寫入的目錄中執行 xcrun swift wav-check.swift。指令稿會建立自己的暫存目錄,不會覆寫既有檔案;斷言失敗時,會以非零狀態碼結束。

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

將寫入器放在函式內,是為了讓它在讀回前結束生命週期,完成檔案收尾。不要讓尚未關閉的寫入器與讀取器同時操作輸出檔案,再把讀到的不完整檔案誤判為匯出器截斷了內容。

這裡區分了磁碟格式與記憶體格式:WAV 採用交錯儲存,處理緩衝區則使用非交錯的浮點資料。讀取聲道指標時,必須以 processingFormat 為依據,不能只憑副檔名推定記憶體中的排列方式。

把檢查接到實際匯出器

最小指令稿通過後,保留讀取與斷言部分,將 writeFixture() 換成專案真正使用的匯出介面。如果介面採非同步執行,測試就必須等待成功回呼,而且成功應代表檔案已完成寫入,而不只是工作剛排入佇列。

分塊輸入比一次寫完更重要

至少涵蓋 1、1023、1024、1025 和 4800 個影格的輸入。接著將同一訊號拆成多個長度不一的緩衝區,斷言輸出影格數等於各區塊有效影格數的總和。最後一塊通常不會填滿容量,寫入時應使用 frameLength,而不是 frameCapacity。

加入首尾脈衝取樣也很有用:在第一個與最後一個影格各放入一個振幅不同的取樣值,其餘設為零。這樣就能定位開頭多出的靜音、尾端影格遺失及重複拼接。固定電平適合檢查聲道對應,脈衝適合檢查位置,這兩組測試資料不能互相取代。

透過測試目標驗收平台路徑

指令稿在 macOS 上執行,只能證明檔案測試資料與比較邏輯成立。將共用的匯出程式碼放進 iOS 測試目標,並在模擬器中執行同一套檢查,才能涵蓋應用程式實際呼叫的路徑。涉及音訊工作階段或麥克風輸入的部分,再另外安排實機測試。

整數 PCM 要另訂誤差規則

將浮點取樣值寫成整數 PCM 時會發生量化,不能沿用完全相等的斷言。先固定正規化範圍與捨入策略,再依最低有效位元設定容許誤差。也要加入接近滿刻度、負值及零值的取樣,檢查削波行為是否符合約定。

重新取樣則屬於另一份契約。濾波器可能產生延遲,輸出影格數也涉及取整與尾端資料排空,不能直接套用本例輸入與輸出等長的斷言。先讓原樣匯出路徑的驗收穩定通過,再測試格式轉換,避免把多個變因混在一起。

失敗時分層蒐集證據

影格數不符時,先查最後一塊的有效長度,以及非同步作業完成的時機;影格數正確但左右互換時,查聲道對應;所有數值都異常時,查交錯排列方式,以及浮點與整數格式之間的轉換。不要一遇到失敗就放寬容許誤差,否則會把格式錯誤掩蓋成測試通過。

每次失敗都記錄輸入區塊的長度序列、檔案格式、處理格式、實際影格數、最大誤差,以及第一個錯誤取樣的位置。測試產物只保留合成音訊,避免把真實錄音帶進流水線。驗收標準最終應落實為這些可比較的數值,而不是一句「檔案能夠播放」。

常見問題

沒有音訊輸出裝置的雲端 Mac,也能驗收 WAV 嗎?

可以。AVAudioFile 讀寫檔案不需要喇叭,音訊影格數、聲道順序及取樣誤差都能離線驗證;實際播放路由與聽感仍要另外以裝置測試。

所有 WAV 都應要求取樣值完全一致嗎?

不應該。整數 PCM 有量化誤差,重新取樣及增益處理也會改變數值。必須依輸出格式與處理流程,分別訂定誤差容許值及影格數規則。

把下一次建置工作放到雲端工作站

整台 Mac mini 專屬使用,非虛擬機。依工作需求選擇租用期間,實際可用狀態以控制台即時回報為準。

立即租用雲端 Mac

先確認規格,再安排工作

查看 Desk M4 的計費週期與加購項目,或參考入門指南,準備首次連線。