Guest User

Untitled

a guest
Jan 19th, 2018
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.68 KB | None | 0 0
  1. class Recorder {
  2.  
  3. static let shared = Recorder()
  4. private static let sampleRate: Float64 = 16000
  5.  
  6. var processAudioData: ((Data) -> ())?
  7.  
  8. fileprivate var remoteIOUnit: AudioComponentInstance?
  9. private var audioFile: AudioFileID?
  10. private var startingByte: Int64 = 0
  11. // Audio recording settings
  12. private let formatId: AudioFormatID = kAudioFormatLinearPCM
  13. private let bitsPerChannel: UInt32 = 16
  14. private let channelsPerFrame: UInt32 = 1
  15. private let bytesPerFrame: UInt32 = 2 // channelsPerFrame * 2
  16. private let framesPerPacket: UInt32 = 1
  17. private let encoderBitRate = 12800
  18. private let formatFlags: AudioFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
  19.  
  20. func record(atURL url: URL) {
  21. var status = openFileForWriting(fileURL: url)
  22. startingByte = 0
  23. status = prepareAudioToolbox()
  24. status = startAudioToolboxRecording()
  25. }
  26.  
  27. func openFileForWriting(fileURL: URL) -> OSStatus {
  28. var asbd = AudioStreamBasicDescription()
  29. memset(&asbd, 0, MemoryLayout<AudioStreamBasicDescription>.size)
  30. asbd.mSampleRate = Recorder.sampleRate
  31. asbd.mFormatID = formatId
  32. asbd.mFormatFlags = formatFlags
  33. asbd.mBitsPerChannel = bitsPerChannel
  34. asbd.mChannelsPerFrame = channelsPerFrame
  35. asbd.mFramesPerPacket = framesPerPacket
  36. asbd.mBytesPerFrame = bytesPerFrame
  37. asbd.mBytesPerPacket = framesPerPacket * bytesPerFrame
  38. // Set up the file
  39. var audioFile: AudioFileID?
  40. var audioErr: OSStatus = noErr
  41. audioErr = AudioFileCreateWithURL(fileURL as CFURL, AudioFileTypeID(kAudioFileWAVEType), &asbd, .eraseFile, &audioFile)
  42. if audioErr == noErr {
  43. self.audioFile = audioFile
  44. }
  45. return audioErr
  46. }
  47.  
  48. func prepareAudioToolbox() -> OSStatus {
  49. var status = noErr
  50. // Describe the RemoteIO unit
  51. var audioComponentDescription = AudioComponentDescription()
  52. audioComponentDescription.componentType = kAudioUnitType_Output
  53. audioComponentDescription.componentSubType = kAudioUnitSubType_RemoteIO
  54. audioComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple
  55. audioComponentDescription.componentFlags = 0
  56. audioComponentDescription.componentFlagsMask = 0
  57. // Get the RemoteIO unit
  58. var ioUnit: AudioComponentInstance?
  59. let remoteIOComponent = AudioComponentFindNext(nil, &audioComponentDescription)
  60. status = AudioComponentInstanceNew(remoteIOComponent!, &ioUnit)
  61. guard status == noErr else {
  62. return status
  63. }
  64. guard let remoteIOUnit = ioUnit else {
  65. return 656783
  66. }
  67. self.remoteIOUnit = remoteIOUnit
  68. // Configure the RemoteIO unit for input
  69. let bus1: AudioUnitElement = 1
  70. var oneFlag: UInt32 = 1
  71. status = AudioUnitSetProperty(remoteIOUnit,
  72. kAudioOutputUnitProperty_EnableIO,
  73. kAudioUnitScope_Input,
  74. bus1,
  75. &oneFlag,
  76. UInt32(MemoryLayout<UInt32>.size));
  77. guard status == noErr else {
  78. return status
  79. }
  80. var asbd = AudioStreamBasicDescription()
  81. asbd.mSampleRate = Recorder.sampleRate
  82. asbd.mFormatID = formatId
  83. asbd.mFormatFlags = formatFlags
  84. asbd.mBitsPerChannel = bitsPerChannel
  85. asbd.mChannelsPerFrame = channelsPerFrame
  86. asbd.mFramesPerPacket = framesPerPacket
  87. asbd.mBytesPerFrame = bytesPerFrame
  88. asbd.mBytesPerPacket = framesPerPacket * bytesPerFrame
  89. // Set format for mic input (bus 1) on RemoteIO's output scope
  90. status = AudioUnitSetProperty(remoteIOUnit,
  91. kAudioUnitProperty_StreamFormat,
  92. kAudioUnitScope_Output,
  93. bus1,
  94. &asbd,
  95. UInt32(MemoryLayout<AudioStreamBasicDescription>.size))
  96. guard status == noErr else {
  97. return status
  98. }
  99. // Set the recording callback
  100. var callbackStruct = AURenderCallbackStruct()
  101. callbackStruct.inputProc = recordingCallback
  102. callbackStruct.inputProcRefCon = nil
  103. status = AudioUnitSetProperty(remoteIOUnit,
  104. kAudioOutputUnitProperty_SetInputCallback,
  105. kAudioUnitScope_Global,
  106. bus1,
  107. &callbackStruct,
  108. UInt32(MemoryLayout<AURenderCallbackStruct>.size));
  109. guard status == noErr else {
  110. return status
  111. }
  112. // Initialize the RemoteIO unit
  113. return AudioUnitInitialize(remoteIOUnit)
  114. }
  115.  
  116. func startAudioToolboxRecording() -> OSStatus {
  117. guard let remoteIOUnit = remoteIOUnit else {
  118. return 656783
  119. }
  120. return AudioOutputUnitStart(remoteIOUnit)
  121. }
  122.  
  123. func writeDataToFile(audioBuffers: UnsafeMutableBufferPointer<AudioBuffer>) -> OSStatus {
  124. guard let audioFile = audioFile else {
  125. return 176136
  126. }
  127. var startingByte = self.startingByte
  128. for audioBuffer in audioBuffers {
  129. var numBytes = audioBuffer.mDataByteSize
  130. guard let mData = audioBuffer.mData else {
  131. continue
  132. }
  133. // [1] following call fails with `-38` error (`kAudioFileNotOpenError`). Less often it fails with `1868981823` error (`kAudioFileDoesNotAllow64BitDataSizeError`)
  134. let audioErr = AudioFileWriteBytes(audioFile,
  135. false,
  136. startingByte,
  137. &numBytes,
  138. mData)
  139. guard audioErr == noErr else {
  140. return audioErr
  141. }
  142. let data = Data(bytes: mData, count: Int(numBytes))
  143. processAudioData?(data)
  144. startingByte += Int64(numBytes)
  145. }
  146. self.startingByte = startingByte
  147. return noErr
  148. }
  149.  
  150. }
  151.  
  152. private func recordingCallback(
  153. inRefCon: UnsafeMutableRawPointer,
  154. ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
  155. inTimeStamp: UnsafePointer<AudioTimeStamp>,
  156. inBusNumber: UInt32,
  157. inNumberFrames: UInt32,
  158. ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus {
  159.  
  160. guard let remoteIOUnit = Recorder.shared.remoteIOUnit else {
  161. return 656783
  162. }
  163. var status = noErr
  164. let channelCount: UInt32 = 1
  165. var bufferList = AudioBufferList()
  166. bufferList.mNumberBuffers = channelCount
  167. let buffers = UnsafeMutableBufferPointer<AudioBuffer>(start: &bufferList.mBuffers,
  168. count: Int(bufferList.mNumberBuffers))
  169. buffers[0].mNumberChannels = 1
  170. buffers[0].mDataByteSize = inNumberFrames * 2
  171. buffers[0].mData = nil
  172. // get the recorded samples
  173. // [2] following call fails with `-10863` error (`kAudioUnitErr_CannotDoInCurrentContext`) and less often with `-1` error
  174. status = AudioUnitRender(remoteIOUnit,
  175. ioActionFlags,
  176. inTimeStamp,
  177. inBusNumber,
  178. inNumberFrames,
  179. UnsafeMutablePointer<AudioBufferList>(&bufferList))
  180. guard status == noErr else {
  181. return status
  182. }
  183. status = Recorder.shared.writeDataToFile(audioBuffers: buffers)
  184. return status
  185. }
Add Comment
Please, Sign In to add comment