Highlight
Apple 推出 Music Understanding 框架,让 App 无需机器学习或信号处理知识,即可在设备端离线分析音频的调性、节奏、结构、速度、乐器活动和响度六个维度,且所有结果数据均可编码为 JSON 导出。
核心内容
以前做音乐分析有多麻烦
做视频剪辑 App 时,一个常见需求是「让画面切换踩上音乐节拍」。以前要实现这个功能,开发者得自己写 FFT(快速傅里叶变换)算法提取音频特征,或者集成 librosa 等 Python 库把音频传到服务器分析。这两种方案都有硬伤:前者需要深厚的信号处理知识,后者依赖网络且存在隐私风险。
Final Cut Pro 团队也面临同样的问题。他们在 macOS 和 iPad 版 FCP 中实现了节拍检测(Beat Detection)和自动蒙太奇(Montage)功能,但这些能力被封装在 FCP 内部,第三方开发者无法复用。
Apple 做了什么
WWDC26 发布的 Music Understanding 框架把 FCP 团队积累的音乐分析能力开放给了所有开发者。框架内部封装了信号处理和模型推理,开发者只需要几行代码就能调用。
(01:16)框架提供六个分析维度:
- Key(调性):歌曲的主音和调式,如 D flat major
- Rhythm(节奏):每个节拍和小节的精确时间戳,以及全局 BPM
- Structure(结构):歌曲的段落层级——sections(如副歌、主歌)、segments、phrases
- Pace(速度感):音乐在不同段落的能量密度,数值越高感觉越快
- Instrument Activity(乐器活动):各乐器在何时出现、强度如何
- Loudness(响度):按 LUFS 标准计算的整体响度、瞬时响度和峰值
(00:34)所有分析都在设备端完成,音频不会离开本地。这意味着离线可用、零网络延迟、隐私安全。
这个新能力如何解决问题
以视频卡点功能为例。以前需要写几百行 DSP 代码,现在只需要:
- 把音频文件传给
MusicUnderstandingSession - 调用
analyze()获取节奏和结构数据 - 用返回的
CMTime数组直接驱动AVPlayer的seek(to:)
(15:28)FCP 的 Montage 功能就是这样做的:先识别歌曲段落,再根据每个段落的 pace 值计算剪辑时长,让视频节奏与音乐能量匹配。高能量段落用短快剪辑,低能量段落用长慢镜头。
详细内容
初始化 Session
(04:47)框架通过 MusicUnderstandingSession 与音频交互。最简单的初始化方式是从 AVAsset 创建:
import MusicUnderstanding
import AVFoundation
.fileImporter(isPresented: $isPresented, allowedContentTypes: [.audio]) { result in
switch result {
case .success(let url):
let asset = AVURLAsset(
url: url,
options: [AVURLAssetPreferPreciseDurationAndTimingKey: true]
)
let session = try await MusicUnderstandingSession(asset: asset)
let results = try await session.analyze()
case .failure(let error):
print("导入失败: \(error)")
}
}
关键点:
AVURLAssetPreferPreciseDurationAndTimingKey必须设为true,否则节拍对齐会有时间偏差analyze()默认分析全部六个维度,计算量较大- 整个流程使用
async/await,建议在后台任务中执行
按需分析提升性能
(03:45)如果只需要部分分析结果,使用 analyze(for:) 避免不必要的计算:
let results = try await session.analyze(for: [.rhythm, .pace])
if let rhythm = results.rhythm {
let bpm = rhythm.beatsPerMinute ?? 120.0
let beatTimes = rhythm.beats
print("BPM: \(bpm), 节拍数: \(beatTimes.count)")
}
关键点:
analyze(for:)只返回请求的分析类型,其余字段为nilbeatsPerMinute是Float?,如果音频没有明显节奏(如纯人声朗读),返回nil- 建议始终对可选结果做安全解包
时间数据类型
(05:53)框架用两个泛型结构体把时间与数据绑定:
public struct TimedValue<Value>: Codable, Equatable, Sendable
where Value: Codable & Equatable & Sendable {
public let time: CMTime
public let value: Value
}
public struct RangedValue<Value>: Codable, Equatable, Sendable
where Value: Codable & Equatable & Sendable {
public let range: CMTimeRange
public let value: Value
}
关键点:
TimedValue把一个值绑定到某个时间点RangedValue把一个值绑定到某个时间范围- 两者都遵循
Codable和Sendable,可直接序列化或跨任务传递 - 使用
CMTime而非Double避免了浮点精度问题,与AVFoundation无缝衔接
调性分析
(06:27)调性分析返回歌曲的主音和调式:
public struct KeyResult: Codable, Sendable {
public let ranges: [MusicUnderstandingSession.RangedValue<KeySignature>]
}
public struct KeySignature: Codable, Hashable, Sendable {
public let tonic: Tonic
public let mode: Mode
}
@frozen public enum Tonic: String, Codable, Hashable, Sendable {
case aFlat, aSharp, a, bFlat, b, c, cSharp,
d, dFlat, dSharp, eFlat, e, f, fSharp,
g, gFlat, gSharp
}
public enum Mode: String, Codable, Hashable, Sendable {
case major, minor
}
关键点:
KeyResult.ranges是数组,因为歌曲可能在不同段落转调- 每个
RangedValue<KeySignature>包含调性生效的时间范围 Tonic覆盖全部 17 个半音音级,包括升降调
节奏分析
(07:16)节奏分析返回每个节拍和小节的精确时间戳:
public struct RhythmResult: Codable, Sendable {
public let beats: [CMTime]
public let bars: [CMTime]
public let beatsPerMinute: Float?
}
关键点:
beats数组包含歌曲中每个节拍的CMTimebars数组包含每个小节的起始时间beatsPerMinute是全局平均 BPM,如果音频节奏不明显则为nil
结构分析
(08:42)结构分析把歌曲拆成三个层级:
public struct StructureResult: Codable, Sendable {
public let sections: [CMTimeRange]
public let segments: [CMTimeRange]
public let phrases: [CMTimeRange]
}
关键点:
sections对应副歌、主歌、前奏、桥段等宏观段落segments是 sections 的子划分phrases是最细粒度,相当于音乐中的「句子」- 三者层级关系:section > segment > phrase
速度感分析
(09:26)Pace 描述音乐给听众的速度感:
public struct PaceResult: Codable, Sendable {
public let ranges: [MusicUnderstandingSession.RangedValue<Double>]
}
关键点:
- Pace 和 BPM 不同,它衡量的是「能量密度」这种主观感受
- 数值越高,音乐感觉越快、越有活力
- 返回的是带时间范围的数组,不同段落可能有不同的 pace 值
(14:47)用 pace 计算视频剪辑时长:
let timePerClip = 60 / paceValue
乐器活动分析
(10:13)乐器活动提供两种粒度:
public struct InstrumentActivityResult: Codable, Sendable {
public let ranges: [Instrument: [CMTimeRange]]
public let activity: [Instrument: [MusicUnderstandingSession.TimedValue<Float>]]
}
关键点:
ranges告诉你某种乐器在哪些时间段出现(布尔判断)activity告诉你该乐器在每个时间点的强度(0 到 1 的浮点数)activity数据适合驱动音频可视化动画
响度分析
(11:45)响度按 LUFS(Loudness Units Full Scale)标准计算:
public struct LoudnessResult: Codable, Sendable {
public let integrated: MusicUnderstandingSession.TimedValue<Float>
public let momentary: [MusicUnderstandingSession.TimedValue<Float>]
public let shortTerm: [MusicUnderstandingSession.TimedValue<Float>]
public let peak: MusicUnderstandingSession.TimedValue<Float>
}
关键点:
integrated:整首歌的平均响度,单个值momentary:每 100ms 的响度,计算窗口 400ms,适合检测突发音量变化shortTerm:每 100ms 的响度,计算窗口 3 秒,曲线更平滑peak:整首歌的绝对峰值音量,单位是分贝
流式响度 API
(12:48)框架提供 AsyncSequence 风格的流式响度数据,适合实时场景:
let audioProvider = AudioProvider()
let session = MusicUnderstandingSession(audioProvider: audioProvider)
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
for try await result in await session.loudnessResults {
updateAudioLevel(result.momentary.value)
}
}
group.addTask {
try await session.analyze(for: [.loudness])
}
}
关键点:
loudnessResults是AsyncSequence,每 100ms 推送一次结果- 需要配合自定义
AudioProvider使用 - 两个任务并发执行:一个消费结果,一个驱动分析
自定义音频提供者
(13:19)除了 AVAsset,还可以用实时音频流初始化:
struct AudioProvider: AsyncSequence, AsyncIteratorProtocol {
func makeAsyncIterator() -> Self {
return self
}
mutating func next() async -> AVReadOnlyAudioPCMBuffer? {
// 返回下一个音频缓冲区,传 nil 表示结束
}
}
关键点:
AudioProvider需遵循AsyncSequence和AsyncIteratorProtocol- 每次
next()返回一个AVReadOnlyAudioPCMBuffer - 分析结束时必须返回
nil通知框架停止 - 适合麦克风实时输入、网络音频流等场景
导出分析结果
(13:55)所有结果都遵循 Codable,可直接编码为 JSON:
let session = try await MusicUnderstandingSession(asset: asset)
let results = try await session.analyze()
let encoder = JSONEncoder()
let data = try encoder.encode(results)
关键点:
- 无需手动序列化,一行代码导出全部数据
- 适合预计算分析结果并随 App 打包,或上传到服务器共享
- JSON 中的时间戳使用
CMTime的编码格式
核心启发
1. 做一款「自动踩点」的视频剪辑 App
用户导入音乐后,App 自动分析节拍和结构,在 beat 和 bar 位置插入转场标记。开发者只需要读取 RhythmResult.beats 数组,把每个 CMTime 转换成时间轴上的标记点。入口 API 是 MusicUnderstandingSession.analyze(for: [.rhythm, .structure])。
2. 做一款「能量可视化」的音乐播放器
播放歌曲时,用 PaceResult 和 InstrumentActivityResult.activity 驱动粒子动画或波形图。高 pace 段落粒子运动加快,鼓点出现时触发闪光效果。TimedValue<Float> 的时间戳与 AVPlayer 的当前时间直接对比即可同步。
3. 做一款「智能 DJ」App
用 KeyResult 分析用户曲库的调性,自动推荐调性兼容的歌曲做串烧。大调转大调、小调转小调,或者五度圈相邻调性都可以无缝衔接。入口是遍历曲库调用 analyze(for: [.key]),把结果存入本地数据库。
4. 做一款「节拍游戏」
预先用 analyze() 计算好歌曲的节拍数据,打包进游戏资源。游戏运行时读取 beats 数组,在对应时间点生成点击目标。这比运行时实时分析更省性能,也避免了游戏过程中分析失败的风险。
5. 做一款「响度均衡」工具
用 LoudnessResult.integrated 批量分析用户曲库,自动调整每首歌的播放音量,让不同来源的音乐听起来响度一致。这在播客 App 和音乐播放器中都很实用,解决「切歌时被吓一跳」的痛点。
关联 Session
- Meet MusicKit for Swift — MusicKit 提供音乐播放和曲库管理能力,与 Music Understanding 分析能力结合可构建完整的音乐应用
- Create 3D models for your spatial app — 用 3D 模型构建空间音频可视化场景,把 Music Understanding 的分析结果映射到三维空间
- What’s new in SwiftUI — SwiftUI 的 Canvas 和动画 API 适合绘制 Music Understanding Lab 风格的实时可视化界面
- Explore machine learning on Apple platforms — 了解 Apple 设备端 ML 的底层机制,理解 Music Understanding 框架的推理优化策略
评论
GitHub Issues · utterances