Top Banner
25

Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

Apr 14, 2017

Download

Software

Evan Owen
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 2: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 3: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 4: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 5: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 6: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 7: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 8: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 9: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 10: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 11: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 12: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

/// A tiny value that can be easily transferred acoustically between devices. public struct Token {

public let value: UInt16

/// create a token from an unsigned 16-bit quantity public init(_ value: UInt16)

/// create a token from 16 bits with the most-significant-bit first public init(bigEndianBits bits: [Bit])

/// extract the bits in most-significant-bit-first order internal var bigEndianBits: [Bit] { get } }

Protocol (1/2)

Page 13: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

/// a more concise name for the pointer to buffers of our audio data typealias FloatBuffer = UnsafeMutablePointer<Float>

// protocol parameters let sampleRate: Float = 44100 // samples per second let baseFreq: Float = 843.8 // hertz let fftLength: Int = 4096 let toneBinStride: Int = 8

// derived constants let firstToneBin = hz2bin(Float(baseFreq)) let freqSpacing = bin2hz(firstToneBin + toneBinStride) - bin2hz(firstToneBin) let lastToneBin = firstToneBin + ((Token.numBits-1) * toneBinStride) let toneBins = firstToneBin.stride(through: lastToneBin, by: toneBinStride)

Protocol (2/2)

Page 14: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 15: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

/// generates a buffer of 32-bit float audio samples where the /// provided `token` is encoded in the frequency domain /// according to the "audio barcode" protocol. func encode(token: Token, buffer: FloatBuffer, numSamples: Int) { // generate a tone for each bit that is turned on for (i, bit) in token.bigEndianBits.enumerate() where bit == .One {

let freq = baseFreq + (Float(i) * freqSpacing) tone(Float(freq), buffer: buffer, numSamples: numSamples) } }

Transmit (1/4)

Page 16: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

/// mixes a tone with frequency 'hz' into 'buffer' private func tone(hz: Float, buffer: FloatBuffer, numSamples: Int) {

let x = twoPi * hz / sampleRate for i in 0..<numSamples { buffer[i] += sinf(Float(i) * x) }

}

Transmit (2/4)

Page 17: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

Transmit (3/4)

self.engine = AVAudioEngine() self.toneGen = AVAudioPlayerNode() self.engine.attachNode(self.toneGen) self.audioFormat = AVAudioFormat(standardFormatWithSampleRate: Double(sampleRate), channels: 1)

self.engine.connect(self.toneGen, to: self.engine.outputNode, format: self.audioFormat)

try! AVAudioSession.sharedInstance() .setCategory(AVAudioSessionCategoryPlayAndRecord)

try! engine.start()

Page 18: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

Transmit (4/4)

let pcmBuffer = AVAudioPCMBuffer(PCMFormat: audioFormat, frameCapacity: AVAudioFrameCount(10 * sampleRate)) let bufferRaw = pcmBuffer.floatChannelData[0] let numFrames = Int(pcmBuffer.frameCapacity) encode(token, buffer: bufferRaw, numSamples: numFrames) pcmBuffer.frameLength = AVAudioFrameCount(numFrames) toneGen.scheduleBuffer(pcmBuffer, atTime: nil, options: .Loops) { print("buffer finished playing") } toneGen.play()

Page 19: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 20: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

Receive (1/4)

let desiredBufferSize = AVAudioFrameCount(1 * sampleRate)

inputNode.installTapOnBus(0, bufferSize: desiredBufferSize, format: nil) { pcmBuffer, time in let rawBuffer = pcmBuffer.floatChannelData[0] if let token = decode(rawBuffer, numSamples: fftLength) { print("got token", token) } }

Page 21: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

func decode(inputSamples: FloatBuffer, numSamples: Int) -> Token? {

// ...snip... allocate raw memory // de-interleave to get the audio input into the format that vDSP wants let (evenSamples, oddSamples) = deinterleave(inputSamples, inputLength: fftLength) // perform the DFT vDSP_DFT_Execute(setup, evenSamples, oddSamples, outReal, outImaginary)

// compute magnitudes for each frequency bin (convert from complex to real) var freqComplex = DSPSplitComplex(realp: outReal, imagp: outImaginary) vDSP_zvmags(&freqComplex, 1, freqMagnitudes, 1, vDSP_Length(fftLength/2)) // analyze let token = decodeStage2(freqMagnitudes, numMagnitudes: fftLength/2) // ...snip... cleanup raw memory

return token }

Receive (2/4)

Page 22: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

private func decodeStage2(magnitudes: FloatBuffer, numMagnitudes: Int) -> Token? { // estimate the noise floor by using the magnitude in nearby bins let preBins = [firstToneBin-7, firstToneBin-6, firstToneBin-5] let postBins = [lastToneBin+5, lastToneBin+6, lastToneBin+7] let noiseFloor = (preBins + postBins) .map { magnitudes[$0] } .mean() // find the magnitude of the tallest peak var peak: Float = 0.0 for bin in toneBins { peak = max(peak, magnitudes[bin]) }

// ... continued on next slide

Receive (3/4)

Page 23: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

// ... continued from decodeStage2 on prev slide

// bail out early if the signal isn't strong enough if peak/noiseFloor < 5.0 { return nil } // set the hard-decision threshold to be above the noise floor // but also not too high that it would reject weak tones let threshold = (0.1 * (peak - noiseFloor)) + noiseFloor // decide which tones are present and which are not var bits = [Bit]() for bin in toneBins { let magnitude = magnitudes[bin] let isOn = magnitude > threshold bits.append(isOn ? .One : .Zero) } return Token(bigEndianBits: bits) }

Receive (4/4)

Page 24: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift
Page 25: Cotap Tech Talks: Keith Lazuka, Digital Communication using Sound and Swift

https://github.com/klazuka/CotapTechTalkSource code and demo app

Contact [email protected]

Recommended DSP BookUnderstanding Digital Signal Processing by Richard G. Lyons