MOYISNORE
ACOUSTIC PIPELINE

MoyiSnore 如何把八小時的聲音,變成一夜看得懂的紀錄

你睡著時只有兩件事在跑:一個 AAC 編碼器,和一個 20Hz 的特徵迴圈,後者只留下 10Hz 的音量包絡。就這樣。夜間不跑任何機器學習、不上傳任何東西,也不做任何偵測判斷——半夜對著不完整的資料做的決定,是不可逆的。早上 App 把錄音走過一次:包絡挑出有聲音的區段,Apple 的聲音分類器只讀這些區段,存活下來的視窗才成為事件,附上音檔、音量與嚴重度。這一頁的每個數字都來自出貨原始碼。

以下參數取自 App 原始碼(AudioPipeline、Screeners、EnvelopeGate、BatchProcessor、Theme、CLOUD_SYNC_SPEC)。模擬資料都有標示。

01 / 三級漏斗

夜間便宜,早晨徹底

設計鐵律是:夜間階段必須在電池上撐過 iOS 八小時的背景排程。所以夜間只做加減乘除——整流、濾波、累加——凡是「事後看得更清楚」的判斷,全部留到早上;那時整份檔案就在硬碟上,門檻設錯可以重跑一次。

夜間

錄製

AVAudioEngine tap,4800 frame 一批(48kHz 約 100ms)。工作階段用 .playAndRecord + .measurement,關掉系統 AGC 與 EQ,量測才乾淨。一個無聲 player node 讓工作階段在來電、鬧鐘之後仍然活著。

AAC m4a · 裝置原生取樣率 · 約 200MB/晚
夜間

分框特徵

50ms 一框,每秒 20 框。每框產出四個數字:整體 RMS、鼾聲帶 RMS、人聲帶 RMS、零交越率。兩條四階 Biquad 並行——鼾聲帶 40–400Hz,人聲帶 250–3000Hz。

4 個浮點數 × 20 fps
夜間

包絡

每兩框取較大值,得到 10Hz 的音量軌——峰值不能被平均洗掉。每 30 秒落地一次。十小時約 1.2MB。

10Hz · 每 2 框取 max · 約 1.2MB
早晨

靜音閘門

錄音時已算好的包絡被掃過一次,找出活躍區段:門檻 = 第 20 百分位底噪 × 2.5,每段前後外擴 2 秒,相距不到 5 秒就併成一段。採相對底噪,旅館的吵冷氣與安靜臥室才能同時成立。

P20 × 2.5 · 外擴 2s · 合併 5s
早晨

分類

SNAudioStreamAnalyzer 搭配 Apple 內建分類器,只餵活躍區段,並傳入真實檔案 frame 位置。鼾聲類 = {snoring, snort};說夢話類 = {speech, whispering, shout, singing, babbling}。

SNClassifySoundRequest .version1
早晨

事件

同類視窗間隔 ≤ 2 秒就合併,短於 0.5 秒丟棄。存活的每一筆裁出前後各 1 秒的音檔、正規化到 0.9 峰值,並算出峰值 dB、平均位準與嚴重度。

合併 2s · 最短 0.5s · 片段 ±1s
早晨

清理

整晚錄音的任務已完成,預設刪除——留下來的是事件片段。轉文字是按需的:你點哪一段才辨識那一段。

刪除整晚原始檔

閘門讓路的三種情況

沒有包絡,或底噪算出來不合理
退回整檔分析。因為門檻失效而跳過 ML,會安靜地弄丟一整夜。
活躍占比 ≥ 70%
退回整檔。已經省不到了,而單一 pass 的路徑更單純可靠。
全部低於門檻
真正的安靜夜:完全不跑 ML。
02 / 夜之織

一格一格掃過一整夜

一排是一小時,一格是十秒。方格亮度是固定刻度上的音量,所以不同夜晚的排可以並排比較——多數睡眠 App 把每晚各自正規化到當晚最大值,結果每個夜晚看起來都一樣。在織帶上拖動,讀出管線在那一刻看到什麼。夜晚是模擬的;參數、色票與映射都是 App 的。

在織帶上拖動 · 08:00 h · 10 s / cell
時間 —
整體 RMS —
鼾聲帶 40–400Hz —
人聲帶 250–3000Hz —
零交越率 —
閘門 —
分類器 —
App 主題 · 29

這裡的音量是 dBFS(數位滿刻度)。App 另外呈現推估的 dB SPL,那取決於裝置與手機擺放位置——夜與夜之間一致,但不是校準過的噪音計。

03 / 靜音閘門

分類器到底聽到整夜的多少

這是把五分鐘的分析變成幾秒鐘的那個判斷。包絡很便宜、而且已經在硬碟上;閘門用它決定哪些部分值得跑那個昂貴的 pass。拖動三個控制項,看活躍占比怎麼變。第 20 百分位底噪由資料自己決定、不由你決定——這正是相對門檻的意義。

2.5 × P20
2.0 s
5.0 s
活躍占比
—
— 段
P20 底噪: — 門檻: — 路徑: —

包絡以 10Hz 繪製,依畫布寬度做 max pooling。高亮區段就是 SNAudioStreamAnalyzer 實際收到的內容,並帶著真實 frame 位置,所以視窗時間戳不需要換算。

04 / ML 門檻

靈敏度,以及為什麼 top-1 不夠

靈敏度設定只是一個 1 到 10 的數字,它把最低信心門檻從 0.70 拉到 0.25——嚴到寬,預設 5 對應 0.50。更細緻的判斷是:要往分類器輸出的第幾名看。旅館的冷氣會產生一個恆定類別、整晚霸佔第一名,把鼾聲擠到第二名。只讀第一名會丟掉整夜;App 掃 top-5,取第一個達到門檻的目標類別。

5 / 10
最低信心門檻
0.50
偵測到的事件
—
—
若只讀 top-1
—
—
鼾聲 說夢話 未達門檻的視窗

模擬視窗,信心值落在真實分類器的輸出範圍內。合併與最短長度用的是出貨規則:同類 2 秒內合併,短於 0.5 秒丟棄。

一個視窗,分類器原樣回傳的樣子

1 white noise 0.71 —
2 mechanical fan 0.63 —
3 snoring 0.58 ← target ✓
4 breathing 0.31 —
5 speech 0.12 —

這是開著風扇的房間裡真實的 top-5。第一個達到門檻的目標類別勝出,不管它排第幾。

05 / 頻帶與特徵

夜間迴圈實際在算什麼

鼾聲是低頻主導,講話是中頻主導,所以夜間迴圈把兩條帶分開保留。它同時保留一個主導性判準:鼾聲要 low > mid × 2,人聲要 mid > low × 0.35。以真實 zh-TW 語音過管線量測,語音框只有約 23% 達到 low > mid × 2——這就是講話不會被記成鼾聲的原因。這些粗篩驅動工程模式的量測面板、也塑造了整個設計;但它們在夜間產出的事件是算完就丟的,因為真正的偵測器是早晨的分類器。

每 50ms 一組

rawRMS 未濾波 RMS 現場波形、織帶、包絡
lowRMS 40–400Hz RMS 鼾聲粗篩、主導性判準
midRMS 250–3000Hz RMS 人聲粗篩、主導性判準
zcr 零交越次數 ÷ 框長 像不像人聲,0.006–0.35

粗篩狀態機(量測面板所呈現的)

自適應底噪 EMA,α = 0.002,只在未觸發時更新——事件中更新會把門檻追高,事件被自己吃掉。
觸發門檻 max(底噪 × 倍率, 0.0009)。絕對下限約 −61 dBFS,死寂房間不會被自己的底噪抖動觸發。
遲滯 釋放門檻 = 觸發 × 0.6,且需維持 1.2 秒——鼾聲一陣一陣,中間有換氣空隙。
啟動 鼾聲需連續 4 框(200ms),人聲需 6 框(300ms)。
事件長度 鼾聲 0.5–30 秒,說夢話 0.4–20 秒。結尾取最後一個高於釋放門檻的框,不是等待結束的那一刻。
持續噪音防護 一次鼾聲事件不可能持續 8 秒——換氣會結束它。連續 8 秒沒結束就判定是風扇或冷氣:底噪學習到觀測峰值的 90%,該段丟棄。
靈敏度映射 1 → 5.0×,10 → 1.5×,線性內插;預設 5 得到高於底噪 3.4×。
06 / 規格

數字

平台 iPhone 與 iPad,iOS 17.6 或以上
音訊工作階段 .playAndRecord、mode .measurement——關閉 AGC 與 EQ
Tap buffer 4800 frame,48kHz 約 100ms
整晚錄音 AAC m4a,裝置原生取樣率,quality medium,約 200MB/晚
分框 50ms 一框,20 fps
濾波帶 鼾聲 40–400Hz、人聲 250–3000Hz,各為四階 Biquad
包絡 10Hz,每 2 框取 max,每 30 秒落地,10 小時約 1.2MB
原始環形緩衝 45 秒音訊,供抽取片段
靜音閘門 第 20 百分位底噪 × 2.5,外擴 2s,合併 5s,活躍 70% 即退回整檔
分類器 SNClassifySoundRequest,identifier .version1,裝置端
信心門檻範圍 0.70(靈敏度 1)到 0.25(靈敏度 10),預設 0.50
類別集合 snoring, snort / speech, whispering, shout, singing, babbling
事件規則 同類 2 秒內合併,短於 0.5 秒丟棄
音檔片段 事件前後各 1 秒,正規化到 0.9 峰值
嚴重度 0–100:0.6 × 響度(−50 → −10 dBFS)+ 0.4 × 持續時間(0 → 20s)
轉文字 zh-TW,requiresOnDeviceRecognition = true,30 秒超時,按需執行
新引擎 若已安裝則優先用 iOS 26 SpeechTranscriber,全程裝置端
同步 CloudKit 私有資料庫,自訂 SessionsZone,CKSyncEngine + CKOperation
網路呼叫 整個專案沒有任何一處
主題 29 套預設,每套含深色與淺色兩份配色
織帶方格 一格 10 秒,預設絕對刻度
時間軸縮放 第 95 百分位正規化,單一巨響不會把整夜壓扁
07 / 資料架構

什麼會移動,什麼不可能

沒有我們的伺服器,也沒有帳號系統。同步用的是你自己 Apple ID 之下的 CloudKit 私有資料庫:metadata 走 CKSyncEngine,資產走直接操作——這樣音檔片段才能做到「你按播放才抓」。整晚錄音永不上傳,它在分析後於本機刪除。離線不是錯誤狀態:iCloud 不可用時,App 就是它原本那個純本機 App,而網路狀態永遠不能阻擋開始錄音。

session.json — 事件、時間、狀態 數十 KB 雲端永遠有,本機永遠留
envelope.bin — 10Hz 整晚包絡 約 1.2MB 雲端永遠有,本機永遠留
clips/evt-* — 事件音檔 數 MB 雲端有;本機副本可卸載,播放時再抓
audio.m4a — 整晚原始錄音 約 200MB 永不上雲,分析完即刪
帳號、email、識別碼 — 不收集,沒有東西可送
分析工具、廣告、崩潰回報 SDK — 完全沒有

兩台裝置,一支麥克風

一筆 CloudKit 記錄當錄音鎖,用 compare-and-swap 搶,並以每 5 分鐘一次的心跳對抗 15 分鐘租約——裝置沒電或當機時,永遠沒機會執行「釋放」,所以沒有心跳的鎖一次意外就永久死鎖。而且這個鎖是禮貌、不是正確性閘門:檢查失敗或離線時照常開錄。兩段重疊的 session 就是兩個 UUID,兩筆都留。

08 / 侷限

這個 App 不能告訴你什麼

不是診斷

MoyiSnore 不是醫療器材。它記錄與量測聲音,不偵測呼吸中止或任何疾病。一份客觀、有時間戳的紀錄適合帶去給醫師參考——但不能取代醫師。

兩個人睡,一支麥克風

手機無法分辨聽到的是誰的鼾聲。同床時,請把報告讀成「這個房間」的紀錄。

擺放位置會改變數字

音量取決於距離,以及手機放在什麼東西上。每晚放在同一個位置,才讓夜與夜之間可以比較。

是通用分類器,不是鼾聲專模

App 用的是 Apple 內建的聲音分類器,不是專門用鼾聲訓練的模型。我們對此誠實:每個事件都保留它的類別、信心值與音檔,任何一筆你都能自己核對。

誤判會穿過漏斗

電風扇、路過的車、身旁的人翻身。持續噪音防護與信心門檻能濾掉大部分,但不是全部。

09 / 術語庫

這一頁用到的詞

RMS
均方根——一框音訊的能量。對一個在零上下擺盪的訊號來說,這是談「音量」最誠實的方式。
dBFS
相對於數位滿刻度的分貝:0 dBFS 是取樣值能達到的最大,所有真實訊號都是負值。與 dB SPL(房間裡的聲壓)不是同一回事。
ZCR
零交越率——一框內波形穿過零的次數。算起來極便宜,卻能相當好地暗示一個聲音是濁音、低頻隆隆還是高頻嘶聲。
Biquad
兩極兩零的數位濾波器級。四級串聯得到每八度 24dB 的裙擺,才把鼾聲帶與人聲帶分得開。
包絡
訊號音量的緩慢輪廓。這裡是:每秒 10 個值,每個值取兩個 50ms 框中較大的那個。
百分位底噪
用第 20 百分位的值當底噪,而不是一個固定數字——因為一整夜大部分時間是安靜的,底噪就住在那裡。
遲滯
開始與結束用不同門檻,讓在門檻線上徘徊的訊號不會反覆開開關關。
Somniloquy
說夢話。短暫、常常很小聲,而且——不像鼾聲——是中頻的。
SoundAnalysis
Apple 的裝置端音訊分類框架。內建的 .version1 分類器涵蓋數百種日常聲音類別,鼾聲是其中之一。
Neural Engine
Apple 晶片上專責推論的矽。這就是為什麼一整夜能在幾秒內分類完,而不必付出即時運算的電量代價。
CKAsset
附在 CloudKit 記錄上的檔案。片段與包絡以資產形式傳送,這才讓「按播放才抓」成立。
Compare-and-swap
只在記錄自你讀取後未被改動時才寫入。CloudKit 的樂觀鎖,也是錄音鎖的全部基礎。
10 / 參考文獻

聲學基礎

[1]
Perez-Padilla, R., et al. Characteristics of the sounds of snoring. Am Rev Respir Dis (1993)
— 1000Hz 以下頻率門檻與基頻估計的依據。
[2]
ITU-T Rec. G.729 Annex B A silence compression scheme for terminals. (1996)
— 短時能量(RMS)與零交越靜音預篩的數學模型。
[3]
Gemmeke, J. F., et al. Audio Set: An ontology and human-labeled dataset for audio events. IEEE ICASSP (2017)
— ML 鼾聲與人聲分類的分類體系對映。
[4]
Arkin, A. M. Sleeptalking: Psychology and Psychophysiology. Lawrence Erlbaum (1981)
— 短暫說夢話事件的時間分段模型。

文獻用於設計依據;App 不作任何臨床聲明。

← 回到總覽