KKBOX WWDC17 Airplay 2 - Dolphin

146
#WWDC17 © 2017 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple. David Saracino, AirPlay Engineer Introducing AirPlay 2 Unlocking multi-room audio Session 509 Media

Transcript of KKBOX WWDC17 Airplay 2 - Dolphin

Page 1: KKBOX WWDC17 Airplay 2 - Dolphin

#WWDC17

© 2017 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.

David Saracino, AirPlay Engineer

•Introducing AirPlay 2 •Unlocking multi-room audio • Session 509

Media

Page 2: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Audio

Wireless audio

Page 3: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Audio

Wireless audio

Multi-room playback

NEWNEW

Page 4: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Audio

Wireless audio

Multi-room playback

Enhanced buffering

NEW

Page 5: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Audio

Wireless audio

Multi-room playback

Enhanced buffering

Multi-device control

NEW

Page 6: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Supported platforms

tvOS mac OSiOS

Page 7: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Supported speakers

HomePod Apple TV* 3rd Party

* Requires Apple TV (4th generation)

Page 8: KKBOX WWDC17 Airplay 2 - Dolphin

•AirPlay 2 Adoption

Page 9: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Adoption

Identify as long-form audio

Page 10: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Adoption

Identify as long-form audio

Add an AirPlay picker

Page 11: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Adoption

Identify as long-form audio

Add an AirPlay picker

Integrate with MediaPlayer framework

Page 12: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Adoption

Identify as long-form audio

Add an AirPlay picker

Integrate with MediaPlayer framework

Adopt an AirPlay 2 playback API

Page 13: KKBOX WWDC17 Airplay 2 - Dolphin

Identify as Long-Form Audio NEW

Page 14: KKBOX WWDC17 Airplay 2 - Dolphin

Long-form Audio Routing (iOS) Music and phone call coexistence

Long-form Audio (AirPlay 2) System Audio

NEW

Page 15: KKBOX WWDC17 Airplay 2 - Dolphin

Long-form Audio Routing (iOS) Music and phone call coexistence

Long-form Audio (AirPlay 2) System Audio

Long-form audio route

Session Arbitration

NEW

Page 16: KKBOX WWDC17 Airplay 2 - Dolphin

Long-form Audio Routing (iOS) Music and phone call coexistence

Long-form Audio (AirPlay 2) System Audio

Long-form audio route

Session Arbitration

NEW

Page 17: KKBOX WWDC17 Airplay 2 - Dolphin

Long-form Audio Routing (iOS) Music and phone call coexistence

Long-form Audio (AirPlay 2) System Audio

Long-form audio route

Session Arbitration

System audio route

Session Arbitration and Mixing

NEW

Page 18: KKBOX WWDC17 Airplay 2 - Dolphin

NEWLong-form Audio Routing (iOS and tvOS)

Long-form Audio (AirPlay 2)

Session Arbitration

Applications that use the long-form audio route

System Audio

Session Arbitration and Mixing

Applications that use the system audio route

Page 19: KKBOX WWDC17 Airplay 2 - Dolphin

//Long-form Audio Routing (iOS and tvOS), code example

let mySession = AVAudioSession.sharedInstance() do { try mySession.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault, routeSharingPolicy: .longForm) } catch { // handle errors }

NEW

Page 20: KKBOX WWDC17 Airplay 2 - Dolphin

//Long-form Audio Routing (iOS and tvOS), code example

let mySession = AVAudioSession.sharedInstance() do { try mySession.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault, routeSharingPolicy: .longForm) } catch { // handle errors }

NEW

Page 21: KKBOX WWDC17 Airplay 2 - Dolphin

//Long-form Audio Routing (iOS and tvOS), code example

let mySession = AVAudioSession.sharedInstance() do { try mySession.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault, routeSharingPolicy: .longForm) } catch { // handle errors }

NEW

Page 22: KKBOX WWDC17 Airplay 2 - Dolphin

Long-form Audio Routing (macOS)

Long-form Audio (AirPlay 2)

Arbitration

Applications that use the long-form audio route

NEW

Default Device

Mixing

Applications that use the system audio route

Page 23: KKBOX WWDC17 Airplay 2 - Dolphin

//Long-form Audio Routing (macOS), code example

let mySession = AVAudioSession.sharedInstance() do { try mySession.setRouteSharingPolicy(.longForm) } catch { // handle errors }

NEW

Page 24: KKBOX WWDC17 Airplay 2 - Dolphin

//Long-form Audio Routing (macOS), code example

let mySession = AVAudioSession.sharedInstance() do { try mySession.setRouteSharingPolicy(.longForm) } catch { // handle errors }

NEW

Page 25: KKBOX WWDC17 Airplay 2 - Dolphin

Add an AirPlay Picker

Page 26: KKBOX WWDC17 Airplay 2 - Dolphin

Add an AirPlay Picker

Adopt

Page 27: KKBOX WWDC17 Airplay 2 - Dolphin

Add an AirPlay Picker

Adopt• AVRoutePickerView

NEW

Page 28: KKBOX WWDC17 Airplay 2 - Dolphin

Add an AirPlay Picker

Adopt• AVRoutePickerView• AVRouteDetector

NEW

Page 29: KKBOX WWDC17 Airplay 2 - Dolphin

Integrate with Media Player Framework

Page 30: KKBOX WWDC17 Airplay 2 - Dolphin

Handle remote media commands • MPRemoteCommandCenter

Integrate with Media Player Framework

Page 31: KKBOX WWDC17 Airplay 2 - Dolphin

Handle remote media commands • MPRemoteCommandCenter

Display current track info • MPNowPlayingInfoCenter

Integrate with Media Player Framework

Page 32: KKBOX WWDC17 Airplay 2 - Dolphin

•AirPlay 2 Playback APIs

Page 33: KKBOX WWDC17 Airplay 2 - Dolphin

Audio Buffering Existing AirPlay

Real-time audio stream

Speaker adds a small buffer before output

Works fine for streaming to single speaker

Page 34: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AirPlay 2

Large audio buffering capacity on speakers

Faster-than-real-time streaming to speakers

Benefits• Adds robustness• More responsive playback

NEW

Page 35: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering Adoption

Supported with specific playback APIs

Page 36: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering Adoption

Supported with specific playback APIs• AVPlayer / AVQueuePlayer

Page 37: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering Adoption

Supported with specific playback APIs• AVPlayer / AVQueuePlayer• AVSampleBufferAudioRenderer

- AVSampleBufferRenderSynchronizer

NEW

Page 38: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

NEW

Page 39: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Your app has additional responsibilities

NEW

Page 40: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Your app has additional responsibilities• Sourcing and parsing the content• Providing raw audio buffers for rendering

NEW

Page 41: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Wants more data

Client App

SynchronizerAudioRenderer

Page 42: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Client App

SynchronizerAudioRenderer

Audio Data

Page 43: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Client App

SynchronizerAudioRendererAudio DataAudio Data

Page 44: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Client App

SynchronizerAudioRendererAudio Data

SetRate 1

Audio Data

Page 45: KKBOX WWDC17 Airplay 2 - Dolphin

Enhanced Audio Buffering AVSampleBufferAudioRenderer / AVSampleBufferRenderSynchronizer

Client App

SynchronizerAudioRendererAudio Data

Audio Data

Page 46: KKBOX WWDC17 Airplay 2 - Dolphin

Requested data amount varies by audio route

Audio Buffer Levels AVSampleBufferAudioRenderer

Local Seconds

Bluetooth Seconds

AirPlay Seconds

AirPlay 2 Minutes

Page 47: KKBOX WWDC17 Airplay 2 - Dolphin

Manually changing Playhead location

Seek AVSampleBufferAudioRenderer

Page 48: KKBOX WWDC17 Airplay 2 - Dolphin

Seek AVSampleBufferAudioRenderer

Renderer

Source Audio Data to Play

PlayheadPlayhead

Page 49: KKBOX WWDC17 Airplay 2 - Dolphin

Seek AVSampleBufferAudioRenderer

Renderer

Source Audio Data to Play

Playhead Playhead

Page 50: KKBOX WWDC17 Airplay 2 - Dolphin

Seek AVSampleBufferAudioRenderer

Renderer

Source Audio Data to Play

Flush Playhead

Page 51: KKBOX WWDC17 Airplay 2 - Dolphin

Seek AVSampleBufferAudioRenderer

Renderer

Source Audio Data to Play

Playhead

Page 52: KKBOX WWDC17 Airplay 2 - Dolphin

// Seek - AVSampleBufferAudioRenderer

func seek(toMediaTime mediaTime: CMTime) {

renderSynchronizer.setRate(0.0, time: kCMTimeZero)

audioRenderer.stopRequestingMediaData()

audioRenderer.flush()

myPrepareSampleGenerationForMediaTime(mediaTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) {

// ... }

renderSynchronizer.setRate(1.0, time: mediaTime)

}

Page 53: KKBOX WWDC17 Airplay 2 - Dolphin

// Seek - AVSampleBufferAudioRenderer

func seek(toMediaTime mediaTime: CMTime) {

renderSynchronizer.setRate(0.0, time: kCMTimeZero)

audioRenderer.stopRequestingMediaData()

audioRenderer.flush()

myPrepareSampleGenerationForMediaTime(mediaTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) {

// ... }

renderSynchronizer.setRate(1.0, time: mediaTime)

}

Page 54: KKBOX WWDC17 Airplay 2 - Dolphin

// Seek - AVSampleBufferAudioRenderer

func seek(toMediaTime mediaTime: CMTime) {

renderSynchronizer.setRate(0.0, time: kCMTimeZero)

audioRenderer.stopRequestingMediaData()

audioRenderer.flush()

myPrepareSampleGenerationForMediaTime(mediaTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) {

// ... }

renderSynchronizer.setRate(1.0, time: mediaTime)

}

Page 55: KKBOX WWDC17 Airplay 2 - Dolphin

// Seek - AVSampleBufferAudioRenderer

func seek(toMediaTime mediaTime: CMTime) {

renderSynchronizer.setRate(0.0, time: kCMTimeZero)

audioRenderer.stopRequestingMediaData()

audioRenderer.flush()

myPrepareSampleGenerationForMediaTime(mediaTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) {

// ... }

renderSynchronizer.setRate(1.0, time: mediaTime)

}

Page 56: KKBOX WWDC17 Airplay 2 - Dolphin

// Seek - AVSampleBufferAudioRenderer

func seek(toMediaTime mediaTime: CMTime) {

renderSynchronizer.setRate(0.0, time: kCMTimeZero)

audioRenderer.stopRequestingMediaData()

audioRenderer.flush()

myPrepareSampleGenerationForMediaTime(mediaTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) {

// ... }

renderSynchronizer.setRate(1.0, time: mediaTime)

}

Page 57: KKBOX WWDC17 Airplay 2 - Dolphin

// Seek - AVSampleBufferAudioRenderer

func seek(toMediaTime mediaTime: CMTime) {

renderSynchronizer.setRate(0.0, time: kCMTimeZero)

audioRenderer.stopRequestingMediaData()

audioRenderer.flush()

myPrepareSampleGenerationForMediaTime(mediaTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) {

// ... }

renderSynchronizer.setRate(1.0, time: mediaTime)

}

Page 58: KKBOX WWDC17 Airplay 2 - Dolphin

Play Queues Timelines

Queue

Renderer

0 100 200

Continuous Timeline

0 100 1000 0

Item 1 Item 2 Item 3

Page 59: KKBOX WWDC17 Airplay 2 - Dolphin

Play Queues Editing during playback

Queue

Last Enqueued Sample

Item 1 Item 2 Item 3

Renderer

Playhead

Page 60: KKBOX WWDC17 Airplay 2 - Dolphin

Item 1

Play Queues Editing during playback

Queue

Renderer

Playhead

Item 3

Last Enqueued Sample

Page 61: KKBOX WWDC17 Airplay 2 - Dolphin

Item 3

Play Queues Editing during playback

Queue Item 1 Item 4

Renderer Item 2’s Data!

Playhead Last Enqueued Sample

Page 62: KKBOX WWDC17 Airplay 2 - Dolphin

Flushing from a Source Time

1. Stop enqueueing audio data

2. flush(fromSourceTime:)

3. Wait for the callback

Page 63: KKBOX WWDC17 Airplay 2 - Dolphin

Item 3

Play Queues Replacing incorrect renderer data

Queue Item 1 Item 4

Renderer

Flush from Source Time

Page 64: KKBOX WWDC17 Airplay 2 - Dolphin

Item 3

Play Queues Replacing incorrect renderer data

Queue Item 1 Item 4

Renderer

Last Enqueued SamplePlayhead

Page 65: KKBOX WWDC17 Airplay 2 - Dolphin

Flushing from a Source Time Gotchas

Page 66: KKBOX WWDC17 Airplay 2 - Dolphin

Flushing from a Source Time Gotchas

Flush may fail!

Page 67: KKBOX WWDC17 Airplay 2 - Dolphin

Flushing from a Source Time Gotchas

Flush may fail!• Source time is too close to playhead

Page 68: KKBOX WWDC17 Airplay 2 - Dolphin

Flushing from a Source Time Gotchas

Flush may fail!• Source time is too close to playhead

Wait for the callback!

Page 69: KKBOX WWDC17 Airplay 2 - Dolphin

// FlushFromSourceTime - AVSampleBufferAudioRenderer

func performFlush(fromSourceTime sourceTime: CMTime) {

audioRenderer.stopRequestingMediaData()

// App-specific logic to ensure no more media data is enqueued

audioRenderer.flush(fromSourceTime: sourceTime) { (flushSucceeded) in

if flushSucceeded {

self.myPrepareSampleGenerationForMediaTime(sourceTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) { /*…*/ } }

else {

// Flush and interrupt playback }

}

}

Page 70: KKBOX WWDC17 Airplay 2 - Dolphin

// FlushFromSourceTime - AVSampleBufferAudioRenderer

func performFlush(fromSourceTime sourceTime: CMTime) {

audioRenderer.stopRequestingMediaData()

// App-specific logic to ensure no more media data is enqueued

audioRenderer.flush(fromSourceTime: sourceTime) { (flushSucceeded) in

if flushSucceeded {

self.myPrepareSampleGenerationForMediaTime(sourceTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) { /*…*/ } }

else {

// Flush and interrupt playback }

}

}

Page 71: KKBOX WWDC17 Airplay 2 - Dolphin

// FlushFromSourceTime - AVSampleBufferAudioRenderer

func performFlush(fromSourceTime sourceTime: CMTime) {

audioRenderer.stopRequestingMediaData()

// App-specific logic to ensure no more media data is enqueued

audioRenderer.flush(fromSourceTime: sourceTime) { (flushSucceeded) in

if flushSucceeded {

self.myPrepareSampleGenerationForMediaTime(sourceTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) { /*…*/ } }

else {

// Flush and interrupt playback }

}

}

Page 72: KKBOX WWDC17 Airplay 2 - Dolphin

// FlushFromSourceTime - AVSampleBufferAudioRenderer

func performFlush(fromSourceTime sourceTime: CMTime) {

audioRenderer.stopRequestingMediaData()

// App-specific logic to ensure no more media data is enqueued

audioRenderer.flush(fromSourceTime: sourceTime) { (flushSucceeded) in

if flushSucceeded {

self.myPrepareSampleGenerationForMediaTime(sourceTime)

audioRenderer.requestMediaDataWhenReady(on: mySerializationQueue) { /*…*/ } }

else {

// Flush and interrupt playback }

}

}

Page 73: KKBOX WWDC17 Airplay 2 - Dolphin

•Audio Format Support •AVSampleBufferAudioRenderer

Page 74: KKBOX WWDC17 Airplay 2 - Dolphin

Supported Audio Formats AVSampleBufferAudioRenderer

All platform-supported audio formats• e.g. LPCM, AAC, mp3, or ALAC• e.g. 44.1 kHZ or 48 kHZ

Page 75: KKBOX WWDC17 Airplay 2 - Dolphin

Supported Audio Formats AVSampleBufferAudioRenderer

All platform-supported audio formats• e.g. LPCM, AAC, mp3, or ALAC• e.g. 44.1 kHZ or 48 kHZ• various bit depths

Mixed formats may be enqueued

AAC @ 44.1kHZ MP3 @ 48kHz 16bit ALAC @ 48kHzRenderer

Page 76: KKBOX WWDC17 Airplay 2 - Dolphin

•Video Synchronization •AVSampleBufferDisplayLayer

Page 77: KKBOX WWDC17 Airplay 2 - Dolphin

AudioRenderer Synchronizer

Video Synchronization AVSampleBufferDisplayLayer

Client App

Page 78: KKBOX WWDC17 Airplay 2 - Dolphin

AudioRenderer Synchronizer

Video Synchronization AVSampleBufferDisplayLayer

Client App

DisplayLayer

NEW

Page 79: KKBOX WWDC17 Airplay 2 - Dolphin

Client App

Video Synchronization AVSampleBufferDisplayLayer

AudioRenderer

Audio Data

Synchronizer DisplayLayer

Video Data

NEW

Page 80: KKBOX WWDC17 Airplay 2 - Dolphin

Client App

Video Synchronization AVSampleBufferDisplayLayer

SetRate 1

AudioRenderer

Audio Data

Synchronizer DisplayLayer

Video Data

NEW

Page 81: KKBOX WWDC17 Airplay 2 - Dolphin

Client App

Video Synchronization AVSampleBufferDisplayLayer

AudioRenderer

Audio Data

Synchronizer DisplayLayer

Video Data

NEW

Page 82: KKBOX WWDC17 Airplay 2 - Dolphin

#WWDC17

© 2017 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.

Akshatha Nagesh, AudioEngine-eer Béla Balázs, Audio Artisan Torrey Holbrook Walker, Audio/MIDI Black Ops

•What’s New in Audio • Session 501

Media

Page 83: KKBOX WWDC17 Airplay 2 - Dolphin

•AVAudioEngine •AVAudioSession •watchOS •AUAudioUnit •Other Enhancements •Inter-Device Audio Mode (IDAM)

Page 84: KKBOX WWDC17 Airplay 2 - Dolphin

•AVAudioEngine

Page 85: KKBOX WWDC17 Airplay 2 - Dolphin

Recap

Powerful, feature-rich, Objective-C / Swift API set

Simplifies realtime audio, easier to use

Supports • Playback, recording, processing, mixing • 3D spatialization

AVAudioEngine in Practice WWDC14

What’s New in Core Audio WWDC15

Page 86: KKBOX WWDC17 Airplay 2 - Dolphin

What’s New

AVAudioEngine • Manual rendering • Auto shutdown

AVAudioPlayerNode • Completion callbacks

NEW

Page 87: KKBOX WWDC17 Airplay 2 - Dolphin

Sample Engine Setup Karaoke

NodeTapBlock (Analyze)

InputNode EffectNode (EQ)

PlayerNode (Backing Track)

PlayerNode (Sound Effects)

MixerNode OutputNode

Page 88: KKBOX WWDC17 Airplay 2 - Dolphin

Sample Engine Setup

InputNode

OutputNode

NodeTapBlock (Analyze)

EffectNode

PlayerNode

PlayerNode

MixerNode

Page 89: KKBOX WWDC17 Airplay 2 - Dolphin

Sample Engine Setup Manual rendering

NodeTapBlock (Analyze)

InputNode EffectNode

PlayerNode

PlayerNode

MixerNode OutputNode

NEW

Application

Page 90: KKBOX WWDC17 Airplay 2 - Dolphin

Engine is not connected to any audio device

Renders in response to requests from the client

Modes • Offline • Realtime

Manual RenderingNEW

Page 91: KKBOX WWDC17 Airplay 2 - Dolphin

Engine and nodes operate under no deadlines or realtime constraints

A node may choose to: • Use a more expensive signal processing algorithm • Block on render thread for more data if needed

- For example, player node may wait until its worker thread reads the data from disk

Offline Manual Rendering NEW

Page 92: KKBOX WWDC17 Airplay 2 - Dolphin

Application

Offline Context

Offline Manual Rendering Example NEW

Source File Destination File

PlayerNode EffectNode OutputNode

Page 93: KKBOX WWDC17 Airplay 2 - Dolphin

Post-processing of audio files, for example, apply reverb, effects etc.

Mixing of audio files

Offline audio processing using CPU intensive (higher quality) algorithms

Tuning, debugging or testing the engine setup

Offline Manual Rendering Applications NEW

Page 94: KKBOX WWDC17 Airplay 2 - Dolphin

The engine and nodes: • Operate under realtime constraints • Do not make any blocking calls like blocking on a mutex, calling libdispatch etc.,

on the render thread - A node may drop the data if it is not ready to be rendered in time

Realtime Manual Rendering NEW

Page 95: KKBOX WWDC17 Airplay 2 - Dolphin

Processing audio in an AUAudioUnit’s internalRenderBlock

Processing audio data in a movie/video during streaming/playback

Realtime Manual Rendering Applications NEW

Page 96: KKBOX WWDC17 Airplay 2 - Dolphin

Realtime Manual Rendering Example NEW

Realtime Context

ApplicationAudio from an Input Movie Stream

Audio into an Output Movie Stream

InputNode EffectNode OutputNode

TV

Page 97: KKBOX WWDC17 Airplay 2 - Dolphin

//Realtime Manual Rendering, code example

do { let engine = AVAudioEngine() // by default engine will render to/from the audio device // make connections, e.g. inputNode -> effectNode -> outputNode

// switch to manual rendering mode engine.stop() try engine.enableManualRenderingMode(.realtime, format: outputPCMFormat, maximumFrameCount: frameCount) // e.g. 1024 @ 48 kHz = 21.33 ms

let renderBlock = engine.manualRenderingBlock // cache the render block

NEW

Page 98: KKBOX WWDC17 Airplay 2 - Dolphin

//Realtime Manual Rendering, code example

do { let engine = AVAudioEngine() // by default engine will render to/from the audio device // make connections, e.g. inputNode -> effectNode -> outputNode

// switch to manual rendering mode engine.stop() try engine.enableManualRenderingMode(.realtime, format: outputPCMFormat, maximumFrameCount: frameCount) // e.g. 1024 @ 48 kHz = 21.33 ms

let renderBlock = engine.manualRenderingBlock // cache the render block

NEW

Page 99: KKBOX WWDC17 Airplay 2 - Dolphin

//Realtime Manual Rendering, code example

do { let engine = AVAudioEngine() // by default engine will render to/from the audio device // make connections, e.g. inputNode -> effectNode -> outputNode

// switch to manual rendering mode engine.stop() try engine.enableManualRenderingMode(.realtime, format: outputPCMFormat, maximumFrameCount: frameCount) // e.g. 1024 @ 48 kHz = 21.33 ms

let renderBlock = engine.manualRenderingBlock // cache the render block

NEW

Page 100: KKBOX WWDC17 Airplay 2 - Dolphin

//Realtime Manual Rendering, code example

do { let engine = AVAudioEngine() // by default engine will render to/from the audio device // make connections, e.g. inputNode -> effectNode -> outputNode

// switch to manual rendering mode engine.stop() try engine.enableManualRenderingMode(.realtime, format: outputPCMFormat, maximumFrameCount: frameCount) // e.g. 1024 @ 48 kHz = 21.33 ms

let renderBlock = engine.manualRenderingBlock // cache the render block

NEW

Page 101: KKBOX WWDC17 Airplay 2 - Dolphin

// set the block to provide input data to engine engine.inputNode.setManualRenderingInputPCMFormat(inputPCMFormat) { (inputFrameCount) -> UnsafePointer<AudioBufferList>? in guard haveData else { return nil } // fill and return the input audio buffer list return inputBufferList }) // create output buffer, cache the buffer list let buffer = AVAudioPCMBuffer(pcmFormat: outputPCMFormat, frameCapacity: engine.manualRenderingMaximumFrameCount)! buffer.frameLength = buffer.frameCapacity let outputBufferList = buffer.mutableAudioBufferList try engine.start() } catch { // handle errors }

NEW

Page 102: KKBOX WWDC17 Airplay 2 - Dolphin

// set the block to provide input data to engine engine.inputNode.setManualRenderingInputPCMFormat(inputPCMFormat) { (inputFrameCount) -> UnsafePointer<AudioBufferList>? in guard haveData else { return nil } // fill and return the input audio buffer list return inputBufferList }) // create output buffer, cache the buffer list let buffer = AVAudioPCMBuffer(pcmFormat: outputPCMFormat, frameCapacity: engine.manualRenderingMaximumFrameCount)! buffer.frameLength = buffer.frameCapacity let outputBufferList = buffer.mutableAudioBufferList try engine.start() } catch { // handle errors }

NEW

Page 103: KKBOX WWDC17 Airplay 2 - Dolphin

// set the block to provide input data to engine engine.inputNode.setManualRenderingInputPCMFormat(inputPCMFormat) { (inputFrameCount) -> UnsafePointer<AudioBufferList>? in guard haveData else { return nil } // fill and return the input audio buffer list return inputBufferList }) // create output buffer, cache the buffer list let buffer = AVAudioPCMBuffer(pcmFormat: outputPCMFormat, frameCapacity: engine.manualRenderingMaximumFrameCount)! buffer.frameLength = buffer.frameCapacity let outputBufferList = buffer.mutableAudioBufferList try engine.start() } catch { // handle errors }

NEW

Page 104: KKBOX WWDC17 Airplay 2 - Dolphin

// set the block to provide input data to engine engine.inputNode.setManualRenderingInputPCMFormat(inputPCMFormat) { (inputFrameCount) -> UnsafePointer<AudioBufferList>? in guard haveData else { return nil } // fill and return the input audio buffer list return inputBufferList }) // create output buffer, cache the buffer list let buffer = AVAudioPCMBuffer(pcmFormat: outputPCMFormat, frameCapacity: engine.manualRenderingMaximumFrameCount)! buffer.frameLength = buffer.frameCapacity let outputBufferList = buffer.mutableAudioBufferList try engine.start() } catch { // handle errors }

NEW

Page 105: KKBOX WWDC17 Airplay 2 - Dolphin

// to render from realtime context OSStatus outputError = noErr; const auto status = renderBlock(framesToRender, outputBufferList, &outputError); switch (status) { case AVAudioEngineManualRenderingStatusSuccess: handleProcessedOutput(outputBufferList); // data rendered successfully break;

case AVAudioEngineManualRenderingStatusInsufficientDataFromInputNode: handleProcessedOutput(outputBufferList); // input node did not provide data, // but other sources may have rendered break; .. default: break; }

NEW

Page 106: KKBOX WWDC17 Airplay 2 - Dolphin

// to render from realtime context OSStatus outputError = noErr; const auto status = renderBlock(framesToRender, outputBufferList, &outputError); switch (status) { case AVAudioEngineManualRenderingStatusSuccess: handleProcessedOutput(outputBufferList); // data rendered successfully break;

case AVAudioEngineManualRenderingStatusInsufficientDataFromInputNode: handleProcessedOutput(outputBufferList); // input node did not provide data, // but other sources may have rendered break; .. default: break; }

NEW

Page 107: KKBOX WWDC17 Airplay 2 - Dolphin

Offline • Can use either ObjC/Swift render method or the block based render call

Realtime • Must use the block based render call

Manual Rendering Render calls NEW

Page 108: KKBOX WWDC17 Airplay 2 - Dolphin

What’s New

AVAudioEngine • Manual rendering • Auto shutdown

AVAudioPlayerNode • Completion callbacks

NEW

Page 109: KKBOX WWDC17 Airplay 2 - Dolphin

Hardware is stopped if running idle for a certain duration, started dynamically when needed

Safety net for conserving power

Enforced behavior on watchOS, optional on other platforms

Auto Shutdown

isAutoShutdownEnabled

NEW

Page 110: KKBOX WWDC17 Airplay 2 - Dolphin

What’s New

AVAudioEngine • Manual rendering • Auto shutdown

AVAudioPlayerNode • Completion callbacks

NEW

Page 111: KKBOX WWDC17 Airplay 2 - Dolphin

Existing buffer/file completion handlers called when the data has been consumed

New completion handler and callback types

Completion Callbacks

AVAudioPlayerNodeCompletionCallbackType .dataConsumed .dataRendered .dataPlayedBack

NEW

Page 112: KKBOX WWDC17 Airplay 2 - Dolphin

• Buffer/file has finished playing • Applicable only when the engine is rendering to an audio device • Accounts for both (small) downstream signal processing latency and (possibly

significant) audio playback device latency

Completion Callbacks

.dataPlayedBack

NEW

Page 113: KKBOX WWDC17 Airplay 2 - Dolphin

• Buffer/file has finished playing • Applicable only when the engine is rendering to an audio device • Accounts for both (small) downstream signal processing latency and (possibly

significant) audio playback device latency

Completion Callbacks

.dataPlayedBack

NEW

player.scheduleFile(file, at: nil, completionCallbackType: .dataPlayedBack) { (callbackType) in // file has finished playing from listener’s perspective // notify to stop the engine and update UI })

Page 114: KKBOX WWDC17 Airplay 2 - Dolphin

• Data has been consumed, same as the existing completion handlers • The buffer can be recycled, more data can be scheduled

• Data has been output by the player • Useful in manual rendering mode • Does not account for any downstream signal processing latency

Completion Callbacks

.dataConsumed

.dataRendered

NEW

Page 115: KKBOX WWDC17 Airplay 2 - Dolphin

AVAudioEngine Summary

AVAudioEngine • Manual rendering • Auto shutdown

AVAudioPlayerNode • Completion callbacks

Deprecation coming soon (2018) • AUGraph

NEW

Page 116: KKBOX WWDC17 Airplay 2 - Dolphin

•AVAudioSession

Page 117: KKBOX WWDC17 Airplay 2 - Dolphin

AirPlay 2 Support

AirPlay 2 - new technology on iOS, tvOS and macOS • Multi-room audio with AirPlay 2 capable devices

Long-form audio applications • Content - music, podcasts etc. • Separate, shared audio route to AirPlay 2 devices • New AVAudioSession API for an application to identify itself as long-form

Introducing AirPlay 2 Session 509 Thursday 4:10PM

NEW

Page 118: KKBOX WWDC17 Airplay 2 - Dolphin

•AUAudioUnit

Page 119: KKBOX WWDC17 Airplay 2 - Dolphin

AU MIDI Output

AU can emit MIDI output synchronized with its audio output

Host sets a block on the AU to be called every render cycle

Host can record/edit both MIDI performance and audio output from the AU *MIDIOutputNames MIDIOutputEventBlock

NEW

Page 120: KKBOX WWDC17 Airplay 2 - Dolphin

AU View Configuration

Host applications decide how to display UI for AUs

Current limitations • No standard view sizes defined • AUs are supposed to adapt to any view size chosen by host

Page 121: KKBOX WWDC17 Airplay 2 - Dolphin

Audio Unit ExtensionHost

AU Preferred View Configuration

supportedViewConfigurations (availableViewConfigurations)

Array of all possible view configurations

NEW

Page 122: KKBOX WWDC17 Airplay 2 - Dolphin

Audio Unit ExtensionHost

AU Preferred View Configuration

supportedViewConfigurations (availableViewConfigurations)

Array of all possible view configurations

IndexSet of supported view configurations

NEW

Page 123: KKBOX WWDC17 Airplay 2 - Dolphin

Audio Unit ExtensionHost

AU Preferred View Configuration

supportedViewConfigurations (availableViewConfigurations)

Array of all possible view configurations

IndexSet of supported view configurations

Chosen view configuration select(viewConfiguration)

NEW

Page 124: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - host application

var smallConfigActive: Bool = false // true if the small view is the currently active one @IBAction func toggleViewModes(_ sender: AnyObject?) { guard audioUnit = self.engine.audioUnit else { return } let largeConfig = AUAudioUnitViewConfiguration(width: 600, height: 400, hostHasController: false) let smallConfig = AUAudioUnitViewConfiguration(width: 300, height: 200, hostHasController: true)

let supportedIndices = audioUnit.supportedViewConfigurations([smallConfig, largeConfig]) if supportedIndices.count == 2 { audioUnit.select(self.smallConfigActive ? largeConfig : smallConfig) self.smallConfigActive = !self.smallConfigActive } }

NEW

Page 125: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - host application

var smallConfigActive: Bool = false // true if the small view is the currently active one @IBAction func toggleViewModes(_ sender: AnyObject?) { guard audioUnit = self.engine.audioUnit else { return } let largeConfig = AUAudioUnitViewConfiguration(width: 600, height: 400, hostHasController: false) let smallConfig = AUAudioUnitViewConfiguration(width: 300, height: 200, hostHasController: true)

let supportedIndices = audioUnit.supportedViewConfigurations([smallConfig, largeConfig]) if supportedIndices.count == 2 { audioUnit.select(self.smallConfigActive ? largeConfig : smallConfig) self.smallConfigActive = !self.smallConfigActive } }

NEW

Page 126: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - host application

var smallConfigActive: Bool = false // true if the small view is the currently active one @IBAction func toggleViewModes(_ sender: AnyObject?) { guard audioUnit = self.engine.audioUnit else { return } let largeConfig = AUAudioUnitViewConfiguration(width: 600, height: 400, hostHasController: false) let smallConfig = AUAudioUnitViewConfiguration(width: 300, height: 200, hostHasController: true)

let supportedIndices = audioUnit.supportedViewConfigurations([smallConfig, largeConfig]) if supportedIndices.count == 2 { audioUnit.select(self.smallConfigActive ? largeConfig : smallConfig) self.smallConfigActive = !self.smallConfigActive } }

NEW

Page 127: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - host application

var smallConfigActive: Bool = false // true if the small view is the currently active one @IBAction func toggleViewModes(_ sender: AnyObject?) { guard audioUnit = self.engine.audioUnit else { return } let largeConfig = AUAudioUnitViewConfiguration(width: 600, height: 400, hostHasController: false) let smallConfig = AUAudioUnitViewConfiguration(width: 300, height: 200, hostHasController: true)

let supportedIndices = audioUnit.supportedViewConfigurations([smallConfig, largeConfig]) if supportedIndices.count == 2 { audioUnit.select(self.smallConfigActive ? largeConfig : smallConfig) self.smallConfigActive = !self.smallConfigActive } }

NEW

Page 128: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func supportedViewConfigurations(_ availableViewConfigurations: [AUAudioUnitViewConfiguration]) -> IndexSet { var result = NSMutableIndexSet() for (index, config) in availableViewConfigurations.enumerated() { // check if the config (width, height, hostHasController) is supported // a config of 0x0 (default full size) must always be supported if isConfigurationSupported(config) { result.add(index) } } return result as IndexSet }

NEW

Page 129: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func supportedViewConfigurations(_ availableViewConfigurations: [AUAudioUnitViewConfiguration]) -> IndexSet { var result = NSMutableIndexSet() for (index, config) in availableViewConfigurations.enumerated() { // check if the config (width, height, hostHasController) is supported // a config of 0x0 (default full size) must always be supported if isConfigurationSupported(config) { result.add(index) } } return result as IndexSet }

NEW

Page 130: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func supportedViewConfigurations(_ availableViewConfigurations: [AUAudioUnitViewConfiguration]) -> IndexSet { var result = NSMutableIndexSet() for (index, config) in availableViewConfigurations.enumerated() { // check if the config (width, height, hostHasController) is supported // a config of 0x0 (default full size) must always be supported if isConfigurationSupported(config) { result.add(index) } } return result as IndexSet }

NEW

Page 131: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func supportedViewConfigurations(_ availableViewConfigurations: [AUAudioUnitViewConfiguration]) -> IndexSet { var result = NSMutableIndexSet() for (index, config) in availableViewConfigurations.enumerated() { // check if the config (width, height, hostHasController) is supported // a config of 0x0 (default full size) must always be supported if isConfigurationSupported(config) { result.add(index) } } return result as IndexSet }

NEW

Page 132: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func supportedViewConfigurations(_ availableViewConfigurations: [AUAudioUnitViewConfiguration]) -> IndexSet { var result = NSMutableIndexSet() for (index, config) in availableViewConfigurations.enumerated() { // check if the config (width, height, hostHasController) is supported // a config of 0x0 (default full size) must always be supported if isConfigurationSupported(config) { result.add(index) } } return result as IndexSet }

NEW

Page 133: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func select(_ viewConfiguration: AUAudioUnitViewConfiguration) { // configuration selected by host, used by view controller to re-arrange its view self.currentViewConfiguration = viewConfiguration self.viewController?.selectViewConfig(self.currentViewConfiguration) }

NEW

Page 134: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func select(_ viewConfiguration: AUAudioUnitViewConfiguration) { // configuration selected by host, used by view controller to re-arrange its view self.currentViewConfiguration = viewConfiguration self.viewController?.selectViewConfig(self.currentViewConfiguration) }

NEW

Page 135: KKBOX WWDC17 Airplay 2 - Dolphin

//AU Preferred View Configuration Code example - AU extension

override public func select(_ viewConfiguration: AUAudioUnitViewConfiguration) { // configuration selected by host, used by view controller to re-arrange its view self.currentViewConfiguration = viewConfiguration self.viewController?.selectViewConfig(self.currentViewConfiguration) }

NEW

Page 136: KKBOX WWDC17 Airplay 2 - Dolphin

•Other Enhancements

Page 137: KKBOX WWDC17 Airplay 2 - Dolphin

Audio Formats

FLAC (Free Lossless Audio Codec) • Codec, file, and streaming support • Content distribution, streaming applications

Opus • Codec support • File I/O using .caf container • VOIP applications

NEW

Page 138: KKBOX WWDC17 Airplay 2 - Dolphin

Spatial Audio Formats B-Format

Audio stream is regular PCM

File container .caf

B-format: W,X,Y,Z • 1st order ambisonics kAudioChannelLayoutTag_Ambisonic_B_Format

NEW

Page 139: KKBOX WWDC17 Airplay 2 - Dolphin

•AVAudioEngine •AVAudioSession •watchOS •AUAudioUnit •Other Enhancements •Inter-Device Audio Mode (IDAM)

Page 140: KKBOX WWDC17 Airplay 2 - Dolphin

Torrey Holbrook Walker, Audio/MIDI Black Ops

•Inter-Device Audio Mode (IDAM)

Page 141: KKBOX WWDC17 Airplay 2 - Dolphin

Inter-Device Audio Mode

Record audio digitally via Lightning-to-USB cable

USB 2.0 audio class-compliant implementation

Available since El Capitan and iOS 9

Page 142: KKBOX WWDC17 Airplay 2 - Dolphin

Inter-Device Audio ModeIDAM

Page 143: KKBOX WWDC17 Airplay 2 - Dolphin

Inter-Device Audio Mode+ MIDIIDAM

Page 144: KKBOX WWDC17 Airplay 2 - Dolphin

Inter-Device Audio + MIDI

Send and receive MIDI via Lightning-to-USB cable

Class-compliant USB MIDI implementation

Requires iOS 11 and macOS El Capitan or later

Auto-enabled in IDAM configuration

NEW

Page 145: KKBOX WWDC17 Airplay 2 - Dolphin

Inter-Device Audio + MIDI

Device can charge and sync in IDAM configuration

Photo import and tethering are temporarily disabled

Audio device aggregation is ok

Use your iOS devices as a MIDI controllers, destinations, or both

NEW

Page 146: KKBOX WWDC17 Airplay 2 - Dolphin

Summary

AVAudioEngine - manual rendering

AVAudioSession - Airplay 2 support

watchOS - recording

AUAudioUnit - preferred view size, MIDI output

Other enhancements - audio formats (FLAC, Opus, HOA)

Inter-Device Audio and MIDI