大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 ShazamKit 批量创建自定义目录
了解如何借助 ShazamKit,直接在设备上构建自定义目录,并支持 App 中任何音频源的精确匹配。学习如何通过新的 ShazamKit CLI 轻松生成音频签名,并批量创建目录。我们还将向您介绍如何快速更新您的 App,以便和大量音频内容进行同步,如多季电视节目或多期播客等。此外,我们还将分享有关 ShazamKit API 和 SHMediaItems 的更新,帮助您的 App 利用时间范围对音频源中的关键时刻做出准确回应。要进一步了解 ShazamKit,我们建议您先观看 WWDC21 的“探索 ShazamKit”和“使用 ShazamKit 创建自定义音频体验”。
资源
相关视频
WWDC23
WWDC21
-
下载
大家好 我是Neil Foley ShazamKit 团队的工程师 2021 年 我们推出了 ShazamKit 它可以帮助您将音频 与 Shazam 庞大的录制音乐目录 进行匹配 我们还推出了 CustomCatalog 匹配 使开发者能够匹配自己的音频 并提供同步体验 现在我们进行了一些重要的更新 以简化使用 CustomCatalogs 的大量工作 在本次会议中 我将使用 一些现有的 ShazamKit 概念 如签名 目录和媒体项目 如需更深入的了解 请查看 2021 Apple 全球开发者大会讨论的 探索 ShazamKit 以及使用 ShazamKit 创建自定义音频体验 但作为快速概览 Shazamkit 能够使你 将音频转换为可匹配的特殊格式 我们称之为签名 签名可与包含元数据的 mediaitem 组合在一起 形成参考签名 参考签名可以一起存储在 我们称为自定义目录的文件中 现在我们都清楚了 我将带你建立大量的自定义目录 然后再谈论一些制作优秀目录的 要诀和技巧 在如今的 CustomCatalog workflow 中 如果你有少量需要匹配的内容 那么使用 CustomCatalogs 就是一项简单的任务 你只需要遵循以下步骤即可 以 ShazamKit 支持的格式录制音频
使用 SignatureGenerator 将其转换为签名
使用元数据对其进行注释 然后将其存储在自定义目录中 就是这样 你可以提供 Shazam 的使用体验 但其中一些步骤可能比较难 特别是如果你不擅长音频编程的话 即使对于经验丰富的开发人员来说 处理采样频率和缓冲文件 也会很棘手 当你想使用 Shazamable 制作大量的内容 例如10个季的电视节目时 会怎么样呢
这个 workflow 可能会变得很漫长
而且 如果你要处理的内容过多 则很快就会难以处理 如果你想为自己完善这一工作流程 则可能需要编写代码来 将音频转换为签名 写更多代码以加载和关联媒体项目 每修改一次内容 都必须重复这项工作 如果你只想匹配一些音频的话 这就是一项巨大的投资 然后 如果你想要与 ShazamKit 同步内容 则需要复杂的逻辑来确定 应该在何时显示什么内容 我会介绍 ShazamKit 的一些很棒的 增强功能 它们简化了此工作流程 但首先是一个简单的演示 这是 Alex 在 2021 年演示的 它可以将数学测试 与屏幕上的课程同步 我已经用最新的 ShazamKit 功能 对它进行了更新 我会回放 FoodMath 的演示视频 看看它是如何做到同步的
跳到 26 秒 两个 三个绿色的苹果 我总共有几个苹果 现在开始计时 好了 时间到 让我们看看你做得怎么样 跳到 56 秒 今天 为了更有趣些 我来到商店 从两个红色的苹果着手 我买了两个绿色的苹果 这次我总共有多少个苹果 现在开始计时
好了 时间到 看起来效果不错 有丰富的内容与视频同步 当我说“现在”时 菜单正好出现在正确的时间 同样 当内容不再相关 它就会立即消失 但它是如何工作的呢 我们来看一看代码 只有简单的循环 它在会话中使用 Async Sequence 而不是之前的委托回调 序列恢复至一个表示匹配 非匹配或错误的枚举类型 我只对匹配感兴趣 所以我将循环限制在这种情况下 为了搭建显示结果 我将媒体项目减少到 自己所需的内容
应用程序中实际上没有什么可看的 只有由我们创建的 matchResult 驱动的 SwiftUI 视图 没有复杂的逻辑或时间编码 并且完美同步 那么问题来了 它是如何同步的呢 FoodMaths 的秘诀在于丰富的 CustomCatalog 它提升了用户体验 我用我们构建的用于补充 ShazamKit 的简单工具创建了目录 你也可以使用它在自己的 应用程序中创建丰富的体验 Shazam CLI 是 macOS 13 的一部分 它提供了一种同步内容的简单方法 它有助于使一些与创建自定义目录 相关的重复性任务自动化 让我们来更新刚才展示的自定义目录 现在进行下一个演示
这是一个包含 FoodMath 视频文件的文件夹 这是我的终端 在同一个文件夹里 我将使用命令行界面 通过签名命令将视频转换为签名
我只是传递视频文件作为输入 并指定我们的签名输出
好 这就是我们的签名
现在我想把这个签名 和媒体项目结合起来 以制作一个自定义目录 命令行界面接受简单的 逗号分隔文件 用于描述我将在此处 复制的媒体项目
它描述了我同步内容所需的一切
这是我指定标题的地方 这是我为方程定义的自定义 json 字段 标题映射到媒体项目属性 有关映射的详细信息 请运行带有帮助标志的 自定义目录创建命令
它描述了 CSV 标题 与媒体项目属性之间的关系 现在我想将它们组合成 一个自定义目录 所以我将运行创建命令
我传入签名文件和 csv 文件 它输出一个目录
好的 现在我们有了目录 令人兴奋的是 我可以提前访问 最新一期的 FoodMath 所以我想把它添加到我们的 目录文件中 我把文件复制到这里
这是我们新一期的媒体项目
我将运行更新命令传入视频 新媒体和要更新的目录
好的 我们已经更新了目录 这是如何创建目录的简要概述 但如果你像我一样 你真的会想要编写这个脚本
FoodMath app 实际上有很多新内容 我想把它们都添加到这个目录中 我写了一个非常简单的脚本 依次通过所有的剧集文件夹 并将它们组合成一个自定义目录 我现在就运行
好了 我们现在有一个目录表示 FoodMath 的每个内容 脚本使用显示屏命令 来详细说明目录中的内容 我想我们已经做好了一切 foodmath 项目已经在引用 我们的新目录了 让我们搭建并运行它 这样我们 就可以享受做一些 Math 的乐趣
跳到 30 秒 我总共有多少个苹果 现在开始计时 好了 时间到 让我们看看你做得怎么样 我喜欢那个家伙 这一集很棒 新的一集怎么样呢 我们来试试
跳到 15 秒 多年来 我研究了墨西哥鳄梨酱 真正美味的原因 并写下了我最喜欢的鳄梨酱食谱 它需要 4 个鳄梨 你知道吗 我的朋友来了 所以对于两个人来说 我只需要做一半 我需要多少鳄梨呢 现在开始计时
对 你需要两个鳄梨 让我们一起来做鳄梨酱吧
我们来尝尝
嗯 事实证明这很棒 我希望你玩得开心 下次见
噢 他们换了新的主持人 很有趣 总之 我很快就创建了一个 丰富的同步体验 Shazam CLI 支持一组丰富的指令 我们来复习一下
你可以从任何带有音轨的 媒体文件创建签名 你可以通过组合签名和 媒体项目来创建自定义目录 可以 Display 目录的内容 添加 删除以及导出签名 和媒体项目 接下来 介绍 cli 如何 从 FoodMath 视频创建签名
SHSignatureGenerator 现在 有一个在所有平台上 都可用的方法 signatureFromAsset 使用这种方法 就无需手动从媒体中 提取音频缓冲区 只需传递带有音轨的 AVAsset 即可将其转换为签名 如果你的素材中有多个音轨 它们将混合在一起 以确保签名采集所有内容 好 现在我有了表示媒体的签名 那么我是如何精确同步内容的呢 我使用了 Timed MediaItem API 将时间范围附加到媒体项目可以 很容易地指定它的开始和结束时间 媒体项目还可以有多个时间范围 以针对签名的多个部分 想象一下 你有一个针对 一首歌副歌部分的媒体项目 你可以为每个位置 添加一个时间范围
只有在开始和结束时通知你 指定时间范围才有用 ShazamKit 将提供与时间范围 同步的匹配回调 一个在开始时 一个在结束时 签名可以包含许多媒体项目 所以此回调将只包含在 该特定时间点范围内的媒体项目 对于将在回调中恢复的 媒体项目及其顺序 有一些简单的规则 我们一起来看看
超出时间范围的媒体项目将不会恢复 时间范围内的 MediaItems 将被恢复 最近的事件将首先出现
最后 没有时间范围的媒体项目 总是最后被恢复 但它们将是无序的 没有时间范围的媒体项目 可以很好地存储 应用于整个参考签名的全局信息 在我的 FoodMath 示例中 我使用它来存储剧集的名称 当范围内没有其他媒体项目时出现
最后一点 如果所有媒体项目 都有时间范围 而且它们都在范围之外 那么 ShazamKit 将始终恢复一个 带有基本匹配信息的媒体项目 这样 你将始终获得重要的属性 例如 predictedCurrentMatch offset 和 frequencySkew 在代码中 这也很简单 定时媒体项目通过指定 timeRanges 媒体项目属性来创建 这是一个 Swift Ranges 的数组 还可以使用 timeRanges 属性将其读回 对于 Objective-C 程序员来说 有一个新的 SHRange 类别作为替代 现在你已经了解了如何搭建它们 让我们来探索一些制作 出色 ShazamCatalogs 的技巧 避免为一个媒体创建许多小型签名 签名是与其所代表媒体的一对一映射 所以对于你所有的每一段音频 不管是来自歌曲还是视频 都需要在整个过程中创造一个签名
签名越长 ShazamKit 匹配 音频峰值的机会就越大 准确度也就越高 它还避免了查询签名 与多个参考签名重叠的问题
使用新的定时 mediaitem API 你可以将同步内容定向到各个区间 没有必要将一段音频分成多个签名 我举了一个例子 我们有一个媒体 但有多个媒体项目 但如果我们想利用 Shazamable 制作大量的内容 应该怎么做呢 我们该如何拆分呢 在跨自定义目录拆分内容时 需要进行权衡 如果你为每个媒体素材 创建单独的目录 那么你需要知道 正在播放的是哪段音频 这样才能加载正确的目录 如果你把它们放在一个目录中 就会有更大的下载量 并使用更多的内存 但你可以匹配更多的音频片段 我们的建议是让你创建的 目录文件保持高度集中 例如 每首音乐或整张专辑的目录 而不是艺术家的整个音乐作品集 将内容分开意味着你可以 决定在运行时加载什么 你可以通过自定义目录 添加 API 来实现这一点
尝试一下 看看是否对你的用例有帮助 如果你有多个听起来相同的音频素材 比如一个节目总是以 相同的开场音乐开始 你想为每一集提供定制体验 或者在另一个音轨中采样的歌曲 可以考虑使用频率偏差作为区分器 偏差的音频提高 或降低录音中的频率 当你这么做时会影响音频的声音 但如果影响足够小 ShazamKit 会注意到 但一般人的耳朵听不出来 如果我们取一段音频 从中制作一个自定义目录 然后以略有偏差的频率播放 ShazamKit 仍会匹配音频 并且它还会通过 frequencySkew 属性 报告偏差值 以下是在代码中执行此操作的步骤
在不让人的听力察觉到 或 ShazamKit 无法识别的情况下 音频偏差的程度是有限的
保持 5% 以下的偏差应该是安全的 并提供足够的空间来区分 多个偏差的录音 要真正利用这一点 请使用频率偏差范围 只有当媒体项目处于指定的 偏差范围内时才会恢复
该范围以百分比形式 指定音频与原始音频的差异程度 值为 0 表示音频无偏差 值为 01 表示偏差 1% 你可以使用频率偏差范围属性 来访问媒体项目上的属性
我将介绍在你的App中 使用此功能的步骤 首先创建原始录音的参考签名 然后获取一个媒体项目 把它的频率偏差限制在 3% 到 4% 将其放在你的自定义目录中
现在播放偏差度为 3% 到 4% 的音频 你的媒体项目将被恢复 播放没有偏差或偏差至 范围之外的音频 将不会恢复媒体项目 这是频率偏差
既然你已经看到今年 ShazamKit 激动人心的更新 那么你就已经准备好 做出一些很棒的同步体验了 所以请记住这些最佳实践 首先 为每个媒体素材创建一个签名 你将从 ShazamKit 获得更好的准确性 和更简单的创建流程 使用 SHSignatureGenerators signatureFromAsset 来创建你的签名 它支持多种媒体 这意味着你不需要再处理 低水平的音频细节
使用新的定时媒体项目 API 将同步内容定位到感兴趣的区域 它结合了简单的 API 和出色的准确性 最后让 shazam cli 简化 你创建自定义目录的方式 它的设计消除了 处理大量媒体的麻烦 让你专注于 自己想要制作的精彩体验 我希望你喜欢 ShazamKit 最新的更新 我很高兴看到你把一切 都变成了 Shazamable 我们讨论的所有信息和文档链接 均附在了此次课程里面 感谢大家的收看 请欣赏 WWDC22 余下的部分
-
-
4:26 - Food Math Matcher
/* See LICENSE folder for this sample’s licensing information. Abstract: The model that is responsible for matching against the catalog and update the SwiftUI Views. */ import ShazamKit import AVFAudio struct MatchResult { var title: String? var equation: Equation? var episode: Episode? var answerRange: ClosedRange<Int>? var hasContent: Bool { equation != nil || title != nil || answerRange != nil } } class Matcher: NSObject, ObservableObject, SHSessionDelegate { @Published var matchResult: MatchResult? private var session: SHSession! private let audioEngine = AVAudioEngine() private var matchingTask: Task<Void, Never>? = nil func match(catalog: SHCustomCatalog) throws { session = SHSession(catalog: catalog) session.delegate = self let audioFormat = AVAudioFormat(standardFormatWithSampleRate: audioEngine.inputNode.outputFormat(forBus: 0).sampleRate, channels: 1) audioEngine.inputNode.installTap(onBus: 0, bufferSize: 2048, format: audioFormat) { [weak session] buffer, audioTime in session?.matchStreamingBuffer(buffer, at: audioTime) } try AVAudioSession.sharedInstance().setCategory(.record) AVAudioSession.sharedInstance().requestRecordPermission { [weak self] success in guard success, let self = self else { return } Task.detached { try? self.audioEngine.start() } } Task { @MainActor in for await case .match(let match) in session.results { self.matchResult = match.matchResult } } } } extension SHMatch { var matchResult: MatchResult { mediaItems.reduce(into: MatchResult()) { result, mediaItem in result.title = result.title ?? mediaItem.title result.episode = result.episode ?? mediaItem.episode result.equation = result.equation ?? mediaItem.equation result.answerRange = result.answerRange ?? mediaItem.answerRange } } }
-
13:51 - Timed Media Items
// Restrict this media item to only describe the first 5 seconds let mediaItem = SHMediaItem(properties: [ .title: "Title", .timeRanges:[0.0..<5.0] ]) let timeRanges: [Range<TimeInterval>] = mediaItem.timeRanges
-
16:02 - Combine Catalogs
let parentCatalog = SHCustomCatalog() parentCatalog.add(from: URL(fileURLWithPath: "/path/to/Episode1.shazamcatalog")) parentCatalog.add(from: URL(fileURLWithPath: "/path/to/Episode2.shazamcatalog")) parentCatalog.add(from: URL(fileURLWithPath: "/path/to/Episode3.shazamcatalog"))
-
16:58 - Frequency Skew
func within(range: Range<Float>, for matchedMediaItem: SHMatchedMediaItem) -> Bool { range.contains(matchedMediaItem.frequencySkew) }
-
17:21 - Frequency Skew Ranges
// Restrict this media item to only describe the first 5 seconds let mediaItem = SHMediaItem(properties: [ .title: “Frequency Skewed Audio”, .frequencySkewRanges:[0.01..<0.02] ]) let frequencySkewRanges: [Range<Float>] = mediaItem.frequencySkewRanges
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。