Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
iOSDC2023:聴いて話すiOS 現実世界の「音」との連携
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
たまねぎ
September 02, 2023
Programming
450
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
iOSDC2023:聴いて話すiOS 現実世界の「音」との連携
たまねぎ
September 02, 2023
More Decks by たまねぎ
See All by たまねぎ
個人への依存を、AIで分散させる
chocoyama
0
19
AIのAIによるAIのための出力評価と改善
chocoyama
3
1k
[FlutterKaigi2024] Effective Form 〜Flutterによる複雑なフォーム開発の実践〜
chocoyama
1
13k
ハードウェア対応のリアル.pdf
chocoyama
0
140
20分でわかる!速習resultBuilder(iOSDC 2022)
chocoyama
7
4.1k
SwiftUIっぽくした話
chocoyama
1
760
SwiftUIとGraphQLでプロダクトの継続的な破壊に立ち向かう
chocoyama
6
2.9k
Other Decks in Programming
See All in Programming
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
230
海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム
atsuki_seo
0
850
速習iPhone Duo対応
yuukiw00w
1
710
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
5
3.9k
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
4
3.3k
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
360
更なる可用性を求めて、5年間運用したKotlinのアプリケーションをGoでリプレイスする話
ken_tunc
0
330
Workers Cache を知る
syumai
0
150
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
12
8.8k
Heart of Swift Concurrency
koher
0
950
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
500
[GoCon2026] When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go
takehaya
6
2.4k
Featured
See All Featured
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
410
Are puppies a ranking factor?
jonoalderson
2
3.9k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
250
The Mindset for Success: Future Career Progression
greggifford
PRO
0
500
Utilizing Notion as your number one productivity tool
mfonobong
4
590
Test your architecture with Archunit
thirion
2
2.4k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
The browser strikes back
jonoalderson
0
1.7k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
Building Adaptive Systems
keathley
44
3.2k
Transcript
ௌ͍ͯ͢iOS ݱ࣮ੈքͷʮԻʯͱͷ࿈ܞ iOSDC Japan 2023 ͨ·Ͷ͗ (@_chocoyama)
ࣗݾհ • LayerX ← STORES ← Ϡϑʔ • iOSͱFlutterΛΑ͘৮Δ •
స৬ͯ͠1ϲ݄ͷϐνϐν ͨ·Ͷ͗
ࣗݾհ ࣮3࿈ଓొஃ
ձࣾհ
ԻΛ׆༻ͨ͠ػೳ࣮ɺ ؆୯͔ͭڧྗʹͳ͍ͬͯΔ ͱ͍͏͜ͱΛ͠·͢
ΞδΣϯμ • දʹؔ͢ΔiOSඪ४ػೳͷ ಛͱ͍ํ • ࣮ͨ͠σϞͷհ ͢͜ͱ ͞ͳ͍͜ͱ • ཁૉٕज़ͷৄࡉͳղઆ
• Έͷਂ͍ཧղ
ΞδΣϯμ 1. ݱ࣮ੈքͷʮԻʯΛ׆༻ͨ͠ػೳ 2. ԻೝࣝͱτϥϯεΫϦϓτ 3. ൃʹΑΔϑΟʔυόοΫ 4. ԻϚονϯά 5.
Իྨ 6. ·ͱΊ
ݱ࣮ੈքͷ ʮԻʯΛ׆༻ͨ͠ػೳ
• Siri • FaceTime • Shazam • ϊΠζݕ • Voice
Control • VoiceOver • Իೖྗ • ԻಡΈ্͛ • ԻϝϞ ࣮ଟ༷ͳඪ४ػೳ • AVFoundation • CallKit • Core Audio • ShazamKit • SiriKit • SoundAnalysis • Speech ਐԽ͢ΔFramework
• ଟ͘ͷAppͰ׆༻͍ͯ͠ͳ͍ • ԻΛ׆༻ͨ͠ମݧઃܭك • ΩϟονΞοϓͷϞνϕʔγϣϯ্͕ΓͮΒ͍ ࣮ࡍͷͱ͜Ζ… ʮ࣮ݱͰ͖Δ͜ͱʯʮ׆༻Ͱ͖ͦ͏ͳ͜ͱʯΛΠϝʔδ͘͢͢͠Δ
ԻೝࣝͱτϥϯεΫϦϓτ
• ϚΠΫԻϑΝΠϧʹΑͬ ͯԻσʔλΛऔΓࠐΈ • γεςϜ͕ԻΛղੳ͠ɺς Ωετσʔλʹม • AppଆͰ݁ՌͷΛ׆༻ Իೝࣝ
• ԻೝࣝͷͨΊͷAPI • ԻσʔλΛςΩετʹม ͨ͠Γɺಛஅʹ׆༻Ͱ ͖Δ • iOS17Ҏ߱ɺݴޠϞσϧͷ ΧελϚΠζΛαϙʔτ SFSpeechRecognizer
ࣄલ४උ̍ɿAudioEngineͷϔϧύʔ struct AudioEngine { private let audioEngine = AVAudioEngine() func
start( bufferSize: AVAudioFrameCount, handler: @escaping (AVAudioPCMBuffer, AVAudioTime) -> Void ) throws { // Իೝࣝͷ߹ͷΦʔσΟΦઃఆ let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.record, mode: .measurement, options: []) try audioSession.setActive(true) // Իೖྗͷ४උ audioEngine.inputNode.installTap( onBus: 0, bufferSize: bufferSize, format: audioEngine.inputNode.outputFormat(forBus: 0), block: handler ) audioEngine.prepare() try audioEngine.start() } }
Իೝࣝؔ࿈ͷΠϯελϯεͱϦΫΤετΛ࡞ class Transcriptor: ObservableObject { // ԻೝࣝΛ࣮ߦ͢ΔΠϯελϯε private let speechRecognizer
= SFSpeechRecognizer(locale: Locale(identifier: “ja-JP"))! // ϦΞϧλΠϜೖྗͰͳ͘ϑΝΠϧೖྗͷ߹ɺSFSpeechURLRecognitionRequestΛ͏ private let request = SFSpeechAudioBufferRecognitionRequest() private let audioEngine = AudioEngine() // ೝࣝ݁ՌΛར༻ଆʹ௨ @Published private(set) var bestTranscription: SFTranscription? init() { // ϦΫΤετͷηοτΞοϓΛߦ͏ if speechRecognizer.supportsOnDeviceRecognition { // ΦϯσόΠε࣮ߦɿར༻੍ݶ͕ͳ͘ɺϓϥΠόγʔอͨΕΔ // αʔόʔ࣮ߦɿར༻੍ݶ͕͋ΓɺσόΠε֎ʹσʔλ͕ૹ৴͞ΕΔ͕ɺਫ਼͕ߴ͍ request.requiresOnDeviceRecognition = true } request.shouldReportPartialResults = true } }
ϨίʔσΟϯάͷ։࢝ func startRecording() async throws { // ར༻Մ൱ͷνΣοΫ guard case
.authorized = await withCheckedContinuation({ SFSpeechRecognizer.requestAuthorization($0.resume(returning:)) }), speechRecognizer.isAvailable else { throw TranscriptorError.unavailable } // ΦʔσΟΦΤϯδϯͷىಈ try audioEngine.start(bufferSize: 2048) { buffer, _ in // όοϑΝαΠζ͝ͱͷσʔλΛೝࣝϦΫΤετʹՃ͍ͯ͘͠ request.append(buffer) } // ೝࣝॲཧͷ։࢝ speechRecognizer.recognitionTask(with: request) { result, _ in DispatchQueue.main.async { self.bestTranscription = result?.bestTranscription } } }
DEMOɿೝࣝͨ͠ԻΛදࣔ struct ContentView: View { @StateObject private var transcriptor =
Transcriptor() private var recognizedText: String? { // Իೝࣝ݁Ռ͔ΒϑΥʔϚοτࡁΈจࣈྻΛऔಘ transcriptor.bestTranscription?.formattedString } var body: some View { VStack { if let recognizedText { Text(recognizedText) } RecordingButton { try await transcriptor.startRecording() } } } }
• LayerXϞόΠϧνʔϜ্ཱ͔ͪ͛ͨΓ • iOSϝΠϯͷΤϯδχΞ͕ࣗ1ਓ • ࣸਅΛࡱΓʹདྷͯ͘Ε͍ͯΔਓ͍ͳ͍ • ʮ͍ɺνʔζʂʯͰγϟολʔΛΔࣗࡱΓΞϓϦΛ࡞͖ͬͯͨ มΘΔ͕…
࠾༻ใ ࠾༻ϙʔλϧɿhttps://jobs.layerx.co.jp/
ൃʹΑΔϑΟʔυόοΫ
• จষΛԻʹม͢Δٕज़ • ΞΫηγϏϦςΟπʔϧ ϢʔβʔͷϑΟʔυόοΫ IFͱͯ͠׆༻͞ΕΔ ԻൃʢText to Speechʣ
• iOSͷςΩετԻมAPI • ϓϨʔϯςΩετSSMLܗࣜͷσʔλΛೖྗʹͱΔ ※ Speech Synthesis Markup LanguageʢԻ߹ϚʔΫΞοϓݴޠʣ
• ൃݴޠεϐʔυͳͲΛίϯτϩʔϧՄೳ • iOS17͔ΒύʔιφϧϘΠεʹରԠʢӳޠͷΈʣ AVSpeechSynthesizer
import AVFoundation // PlaneText͔ΒUtteranceͷ࡞ let utterance = AVSpeechUtterance(string: text) utterance.prefersAssistiveTechnologySettings
= true // ΞγετઃఆҾܧ utterance.rate = 0.5 // εϐʔυ (0 ~ 1) utterance.pitchMultiplier = 1 // ϐον (0.5 ~ 2) utterance.volume = 1 // Իྔ (0 ~ 1) // SSML͔ΒUtteranceͷ࡞ let ssml = """ <speak> <prosody rate="fast" pitch="+2st" volume="loud"> ͜Μʹͪɺͨ·Ͷ͗Ͱ͢ʂ </prosody> </speak> """ let utterance = AVSpeechUtterance(ssmlRepresentation: ssml) // Voiceͷઃఆ utterance.voice = .init(language: "ja-JP") utterance.voice = .init(identifier: AVSpeechSynthesisVoiceIdentifierAlex) utterance.voice = AVSpeechSynthesisVoice.speechVoices().randomElement() • AVSpeechUtteranceʹ ൃσʔλΛηοτ • SSMLΛΘͳͯ͘ ɺ֤छϓϩύςΟ ઃఆՄೳ • AVSpeechSynthesisVoi ceΛηοτͯ͠ɺϏϧ τΠϯԻͷར༻Ͱ ͖Δ AVSpeechSynthesizerͰͷൃ
// AVSpeechSynthesizerͷ࡞ let synthesizer = AVSpeechSynthesizer() // ࠶ੜ synthesizer.speak(utterance) //
Ұ࣌ఀࢭ synthesizer.pauseSpeaking(at: .immediate) synthesizer.pauseSpeaking(at: .word) // ࠶։ synthesizer.continueSpeaking() // ఀࢭ synthesizer.stopSpeaking(at: .immediate) synthesizer.stopSpeaking(at: .word) • AVSpeechSynthesizer ʹAVSpeechUtterance Λͯ͠ൃ͢Δ • ίϯτϩʔϧ༻ͷAPI Λ׆༻ͯ͠ࡉ੍͔͍ޚ Ͱ͖Δ AVSpeechSynthesizerͰͷൃ
DEMOɿೖྗͨ͠จࣈྻΛൃ class SpeechSynthesizer: ObservableObject { @Published var text = “͜Μʹͪɺͨ·Ͷ͗Ͱ͢ʂ”
@Published var selectedVoice = AVSpeechSynthesisVoice .speechVoices() .first { $0.language == "ja-JP" }! @Published var rate: Float = 0.5 @Published var pitchMultiplier: Float = 1 @Published var volume: Float = 1 private let synthesizer: AVSpeechSynthesizer = { let s = AVSpeechSynthesizer() s.usesApplicationAudioSession = false return s }() var voices: [AVSpeechSynthesisVoice] { AVSpeechSynthesisVoice.speechVoices() } func speak() { let utterance = AVSpeechUtterance(string: text) utterance.voice = selectedVoice utterance.rate = rate utterance.pitchMultiplier = pitchMultiplier utterance.volume = volume synthesizer.speak(utterance) } }
DEMOɿԻͰͷର 4'4QFFDI3FDPHOJ[FS "74QFFDI4ZOUIFTJ[FS 0QFO"*"1*
ԻϚονϯά
• ԻͱҰக͢ΔσʔλΛ୳͢ ٕज़ • ͷϚΠΫ͔ΒԻΛऔಘ • ରͷίϯςϯπࣗମͦͷ ࠶ੜҐஔΛಛఆ͢Δ ԻϚονϯά
• ԻݯΧλϩά୳ • Իڹγάωνϟ͔ΒྨࣅԻݯΛಛఆ • ΧελϜΧλϩάͷ࡞ • ಠࣗͷΦʔσΟΦDBΛ࡞͠ɺ ԻΛϚονϯά
• ϓϨΠϦετཧ • ೝָࣝͨ͠ۂͷϥΠϒϥϦಉظ ShazamKit
import ShazamKit class Matcher: ObservableObject { @Published private(set) var matchedItem:
SHMatchedMediaItem? private let audioEngine = AudioEngine() func startMatching() async throws { let session = SHSession() // ϦΞϧλΠϜͳΦʔσΟΦϚονϯάͷ४උ try audioEngine.start(bufferSize: 2048) { buffer, audioTime in session.matchStreamingBuffer(buffer, at: audioTime) } // ೝࣝͨ͠ϝσΟΞΞΠςϜΛड͚औΔ for await case .match(let match) in session.results { await MainActor.run { matchedItem = match.mediaItems.first } } } } • ʢࣄલʹʣDeveloper ϙʔλϧͰAppService ΛՃ • SHSessionΛ࡞ • ΦʔσΟΦσʔλΛ sessionʹྲྀ͠ࠐΉ • ΧλϩάσʔλͱϚο νͨ݁͠ՌΛऔಘ ָۂಛఆ
SHMatchedMediaItemͷϓϩύςΟ܈ func explore(_ mediaItem: SHMatchedMediaItem) { mediaItem.title // λΠτϧ mediaItem.subtitle
// αϒλΠτϧ mediaItem.artist // ΞʔςΟετ໊ mediaItem.artworkURL // ΞʔτϫʔΫURL mediaItem.genres // δϟϯϧͷྻ mediaItem.timeRanges // ࣌ؒൣғ mediaItem.matchOffset. // ϚονՕॴ mediaItem.predictedCurrentMatchOffset // ݱࡏͷϚονՕॴͷ༧ଌ mediaItem.webURL. // ShazamΧλϩάϖʔδͷϦϯΫ mediaItem.appleMusicID // AppleMusicID mediaItem.appleMusicURL // AppleMusicϖʔδͷϦϯΫ mediaItem.songs // MusicKitͷSongΦϒδΣΫτ // etc… }
SHMatchedMediaItemͷ׆༻ // ָۂͷίϯτϩʔϧʹɺMusikKitΛ͏ import MusicKit func play(_ mediaItem: SHMatchedMediaItem) async
throws { guard case .authorized = await MusicAuthorization.request() else { return } // SHMatchedMediaItemͷAppleMusicؔ࿈ͷϓϩύςΟΛࢀর SystemMusicPlayer.shared.queue = .init(for: mediaItem.songs) try await SystemMusicPlayer.shared.play() }
DEMOɿฉ͖औͬͨԻݯΛಛఆ struct ContentView: View { @StateObject private var matcher =
Matcher() var body: some View { VStack(spacing: 56) { if let mediaItem = matcher.matchedItem { MatchedMediaItemView(mediaItem) } else if matcher.isActive { MatchedMediaItemView.loading() } RecordingButton { try? await matcher.startMatching() } }.padding() } }
• Shazam CLIΛͬͯ࡞ΕΔ • Իݯ͔ΒSignature (.shazamsignature)Λ࡞ • ҙͷϝλσʔλ ΛؚΊͨϑΝΠϧ(.csv)Λ༻ҙ •
SignatureͱCSVΛඥ͚ͯΧλϩάʢ.shazamcatalogʣΛ࡞ • ԻϚονϯάΦϑηοτΛ༻͍ͨମݧߏஙʹ׆༻Մೳ ΧελϜΧλϩά
Իྨ
Իྨ • ԻσʔλͷύλʔϯΛࣝผ • ػցֶशϞσϧΛݩʹٕͨ͠ ज़ • ԻݯͷछྨΛಛఆͷΧςΰϦ ʹ͚Δ
• Իྨ༻ͷϑϨʔϜϫʔΫ • ΦϯσόΠεͰ࣮ߦՄೳ • iOS15Ҏ߱ϏϧτΠϯ͞Εͨ ϞσϧΛ͑Δ • औΓࠐΜͩԻͷಛύλʔϯ Λೝࣝ͠ɺ300छྨʹྨ
SoundAnalysis
SNAudioStreamAnalyzer class SoundAnalyzer: NSObject, ObservableObject { @Published private(set) var result:
SNClassificationResult? private let audioEngine = AudioEngine() func startAnalyze() throws { // SNClassifySoundRequestΛ࡞ let request = try SNClassifySoundRequest(classifierIdentifier: .version1) // ϑΝΠϧೖྗͷ߹ɺSNAudioFileAnalyzerΛ͏ let analyzer = SNAudioStreamAnalyzer(format: audioEngine.format) try analyzer.add(request, withObserver: self) try audioEngine.start(bufferSize: 2048) { buffer, time in // ԻσʔλΛྲྀ͜͠Ή analyzer.analyze(buffer, atAudioFramePosition: time.sampleTime) } } }
SNAudioStreamAnalyzer extension SoundAnalyzer: SNResultsObserving { // ೝࣝ݁Ռ͕௨͞Εͯ͘Δ func request(_ request:
SNRequest, didProduce result: SNResult) { DispatchQueue.main.async { self.result = result as? SNClassificationResult self.result?.classifications.first?.identifier // ೝࣝͨ͠Իͷϥϕϧ self.result?.classifications.first?.confidence // ೝࣝͨ͠Իͷ৴པ } } }
DEMOɿ ໐͍ͬͯΔָثΛಛఆ struct ContentView: View { // … @StateObject private
var soundAnalyzer = SoundAnalyzer() var body: some View { ZStack(alignment: .bottom) { ScrollView { LazyVStack(spacing: 0) { // … BandImage(soundAnalyzer.result) } } RecordingButton { try? soundAnalyzer.analyze() }.padding(.vertical) } } }
DEMOɿಈըΛʮস͍ʯʮٽ͖ʯͰߜΓࠐΈ ಈըϑΝΠϧͷ63- 4/"VEJP'JMF"OBMZ[FS con fi dence >= 0.9 identi fi
er == “laughter” $.5JNF3BOHF
·ͱΊ
·ͱΊ • SFSpeechRecognizerɿݴޠೝࣝ • τϥϯεΫϦϓτɺݴޠΛϑοΫʹͨ͠ΞΫγϣϯ • AVSpeechSynthesizerɿൃ • Իग़ྗʹΑΔϑΟʔυόοΫ •
ShazamKitɿϚονϯά • ࣄલʹΧλϩάͷ༻ҙ͕ඞཁ • ݴޠͰ͋Δඞཁ͕ແ͘ɺOffsetͷ׆༻Ͱ͖Δ • SoundAnalysisɿྨ • ϏϧτΠϯϞσϧͰ͋Ε͙͢ʹ׆༻Մೳ • ΧελϜͷϞσϧΛ༻ҙͯ͠ɺҙͷྨΈࠐΊΔ
·ͱΊ • ඪ४ػೳ͚ͩͰଟ༷ͳମݧΛ࣮ݱͰ͖Δ • հ͖͠Εͳ͔ͬͨػೳෳ • ͍ॴݶΒΕΔ͕ɺ༗ޮʹ͑ϢχʔΫͳମݧΛఏڙͰ͖Δ
͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ