Swift 3 LPCM录音机| 错误:kAudioFileInvalidPacketOffsetError

下面的录音机只能第一次工作,如果您再次尝试录音,则会在尝试AudioFileWritePackets时发出错误“kAudioFileInvalidPacketOffsetError”。

任何想法为什么发生这种情况?

先谢谢你

存储库位于这里

录音机

import UIKit import CoreAudio import AudioToolbox class SpeechRecorder: NSObject { static let sharedInstance = SpeechRecorder() // MARK:- properties @objc enum Status: Int { case ready case busy case error } internal struct RecordState { var format: AudioStreamBasicDescription var queue: UnsafeMutablePointer<AudioQueueRef?> var buffers: [AudioQueueBufferRef?] var file: AudioFileID? var currentPacket: Int64 var recording: Bool }; private var recordState: RecordState? var format: AudioFormatID { get { return recordState!.format.mFormatID } set { recordState!.format.mFormatID = newValue } } var sampleRate: Float64 { get { return recordState!.format.mSampleRate } set { recordState!.format.mSampleRate = newValue } } var formatFlags: AudioFormatFlags { get { return recordState!.format.mFormatFlags } set { recordState!.format.mFormatFlags = newValue } } var channelsPerFrame: UInt32 { get { return recordState!.format.mChannelsPerFrame } set { recordState!.format.mChannelsPerFrame = newValue } } var bitsPerChannel: UInt32 { get { return recordState!.format.mBitsPerChannel } set { recordState!.format.mBitsPerChannel = newValue } } var framesPerPacket: UInt32 { get { return recordState!.format.mFramesPerPacket } set { recordState!.format.mFramesPerPacket = newValue } } var bytesPerFrame: UInt32 { get { return recordState!.format.mBytesPerFrame } set { recordState!.format.mBytesPerFrame = newValue } } var bytesPerPacket: UInt32 { get { return recordState!.format.mBytesPerPacket } set { recordState!.format.mBytesPerPacket = newValue } } //MARK: - Handlers public var handler: ((Status) -> Void)? // MARK:- Init override init() { super.init() self.recordState = RecordState(format: AudioStreamBasicDescription(), queue: UnsafeMutablePointer<AudioQueueRef?>.allocate(capacity: 1), buffers: [AudioQueueBufferRef?](repeating: nil, count: 1), file: nil, currentPacket: 0, recording: false) }//eom // MARK:- OutputFile func setOutputFile(path: String) { setOutputFile(url: URL(fileURLWithPath: path)) } func setOutputFile(url: URL) { AudioFileCreateWithURL(url as CFURL, kAudioFileWAVEType, &recordState!.format, AudioFileFlags.dontPageAlignAudioData.union(.eraseFile), &recordState!.file) } // MARK:- Start / Stop Recording func start() { handler?(.busy) let inputAudioQueue: AudioQueueInputCallback = { (userData: UnsafeMutableRawPointer?, audioQueue: AudioQueueRef, bufferQueue: AudioQueueBufferRef, startTime: UnsafePointer<AudioTimeStamp>, packets: UInt32, packetDescription: UnsafePointer<AudioStreamPacketDescription>?) in let internalRSP = unsafeBitCast(userData, to: UnsafeMutablePointer<RecordState>.self) if packets > 0 { var packetsReceived = packets let outputStream:OSStatus = AudioFileWritePackets(internalRSP.pointee.file!, false, bufferQueue.pointee.mAudioDataByteSize, packetDescription, internalRSP.pointee.currentPacket, &packetsReceived, bufferQueue.pointee.mAudioData) if outputStream != 0 { // This is where the error occurs when recording after the first time //<----DEBUG switch outputStream { case kAudioFilePermissionsError: print("kAudioFilePermissionsError") break case kAudioFileNotOptimizedError: print("kAudioFileNotOptimizedError") break case kAudioFileInvalidChunkError: print("kAudioFileInvalidChunkError") break case kAudioFileDoesNotAllow64BitDataSizeError: print("kAudioFileDoesNotAllow64BitDataSizeError") break case kAudioFileInvalidPacketOffsetError: print("kAudioFileInvalidPacketOffsetError") break case kAudioFileInvalidFileError: print("kAudioFileInvalidFileError") break case kAudioFileOperationNotSupportedError: print("kAudioFileOperationNotSupportedError") break case kAudioFileNotOpenError: print("kAudioFileNotOpenError") break case kAudioFileEndOfFileError: print("kAudioFileEndOfFileError") break case kAudioFilePositionError: print("kAudioFilePositionError") break case kAudioFileFileNotFoundError: print("kAudioFileFileNotFoundError") break case kAudioFileUnspecifiedError: print("kAudioFileUnspecifiedError") break case kAudioFileUnsupportedFileTypeError: print("kAudioFileUnsupportedFileTypeError") break case kAudioFileUnsupportedDataFormatError: print("kAudioFileUnsupportedDataFormatError") break case kAudioFileUnsupportedPropertyError: print("kAudioFileUnsupportedPropertyError") break case kAudioFileBadPropertySizeError: print("kAudioFileBadPropertySizeError") break default: print("unknown error") break } //<----DEBUG } internalRSP.pointee.currentPacket += Int64(packetsReceived) } if internalRSP.pointee.recording { let outputStream:OSStatus = AudioQueueEnqueueBuffer(audioQueue, bufferQueue, 0, nil) if outputStream != 0 { // This is where the error occurs when recording after the first time //<----DEBUG switch outputStream { case kAudioFilePermissionsError: print("kAudioFilePermissionsError") break case kAudioFileNotOptimizedError: print("kAudioFileNotOptimizedError") break case kAudioFileInvalidChunkError: print("kAudioFileInvalidChunkError") break case kAudioFileDoesNotAllow64BitDataSizeError: print("kAudioFileDoesNotAllow64BitDataSizeError") break case kAudioFileInvalidPacketOffsetError: print("kAudioFileInvalidPacketOffsetError") break case kAudioFileInvalidFileError: print("kAudioFileInvalidFileError") break case kAudioFileOperationNotSupportedError: print("kAudioFileOperationNotSupportedError") break case kAudioFileNotOpenError: print("kAudioFileNotOpenError") break case kAudioFileEndOfFileError: print("kAudioFileEndOfFileError") break case kAudioFilePositionError: print("kAudioFilePositionError") break case kAudioFileFileNotFoundError: print("kAudioFileFileNotFoundError") break case kAudioFileUnspecifiedError: print("kAudioFileUnspecifiedError") break case kAudioFileUnsupportedFileTypeError: print("kAudioFileUnsupportedFileTypeError") break case kAudioFileUnsupportedDataFormatError: print("kAudioFileUnsupportedDataFormatError") break case kAudioFileUnsupportedPropertyError: print("kAudioFileUnsupportedPropertyError") break case kAudioFileBadPropertySizeError: print("kAudioFileBadPropertySizeError") break default: print("unknown error") break } //<----DEBUG } } } let queueResults = AudioQueueNewInput(&recordState!.format, inputAudioQueue, &recordState, nil, nil, 0, recordState!.queue) if queueResults == 0 { let bufferByteSize: Int = calculate(format: recordState!.format, seconds: 0.5) for index in (0..<recordState!.buffers.count) { AudioQueueAllocateBuffer(recordState!.queue.pointee!, UInt32(bufferByteSize), &recordState!.buffers[index]) AudioQueueEnqueueBuffer(recordState!.queue.pointee!, recordState!.buffers[index]!, 0, nil) } AudioQueueStart(recordState!.queue.pointee!, nil) recordState?.recording = true } else { print("Error setting audio input.") handler?(.error) } }//eom func stop() { recordState?.recording = false if let recordingState: RecordState = recordState { AudioQueueStop(recordingState.queue.pointee!, true) AudioQueueDispose(recordingState.queue.pointee!, true) AudioFileClose(recordingState.file!) handler?(.ready) } }//eom // MARK:- Helper methods func calculate(format: AudioStreamBasicDescription, seconds: Double) -> Int { let framesRequiredForBufferTime = Int(ceil(seconds * format.mSampleRate)) if framesRequiredForBufferTime > 0 { return (framesRequiredForBufferTime * Int(format.mBytesPerFrame)) } else { var maximumPacketSize = UInt32(0) if format.mBytesPerPacket > 0 { maximumPacketSize = format.mBytesPerPacket } else { audioQueueProperty(propertyId: kAudioQueueProperty_MaximumOutputPacketSize, value: &maximumPacketSize) } var packets = 0 if format.mFramesPerPacket > 0 { packets = (framesRequiredForBufferTime / Int(format.mFramesPerPacket)) } else { packets = framesRequiredForBufferTime } if packets == 0 { packets = 1 } return (packets * Int(maximumPacketSize)) } }//eom func audioQueueProperty<T>(propertyId: AudioQueuePropertyID, value: inout T) { let propertySize = UnsafeMutablePointer<UInt32>.allocate(capacity: 1) propertySize.pointee = UInt32(MemoryLayout<T>.size) let queueResults = AudioQueueGetProperty(recordState!.queue.pointee!, propertyId, &value, propertySize) propertySize.deallocate(capacity: 1) if queueResults != 0 { print("Unable to get audio queue property.") } }//eom } 

视图控制器

 import UIKit import AudioToolbox class ViewController: UIViewController { //MARK: - Properties var recorder:SpeechRecorder? @IBOutlet weak var startStopRecordingButton: UIButton! //MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() //having same recorder gives error recorder = SpeechRecorder() } //MARK: - Start / End Recording func startRecording() { //alloc/init recorder everytime we start recording gives no error //recorder = SpeechRecorder() //settings recorder?.format = kAudioFormatLinearPCM recorder?.sampleRate = 16000; recorder?.channelsPerFrame = 1 recorder?.bitsPerChannel = 16 recorder?.framesPerPacket = 1 recorder?.bytesPerFrame = ((recorder!.channelsPerFrame * recorder!.bitsPerChannel) / 8) recorder?.bytesPerPacket = recorder!.bytesPerFrame * recorder!.framesPerPacket recorder?.formatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked //outputfile let outputfilePath:String = MyFileManager().createTempFilePathWithUniqueName("recorderAudio", andExtension: "wav") print("temp filepath: ", outputfilePath) recorder?.setOutputFile(path: outputfilePath) //handler recorder?.handler = { [weak self] status in switch status { case .busy: print("started Recording\n\n") break case .ready: print("finish recorder, ready to start recording\n\n") break case .error: print("error occur with recorder\n\n") DispatchQueue.main.async { self?.startStopRecordingButton.isSelected = false self?.view.backgroundColor = UIColor.white } break } }// recorder?.start() }//eom func stopRecording() { recorder?.stop() }//eom //MARK: - Actions @IBAction func startStopRecording() { if startStopRecordingButton.isSelected { startStopRecordingButton.isSelected = false self.view.backgroundColor = UIColor.white startStopRecordingButton.setTitle("Start Recording", for: UIControlState.normal) self.stopRecording() } else { startStopRecordingButton.isSelected = true self.view.backgroundColor = UIColor.green startStopRecordingButton.setTitle("Stop Recording", for: UIControlState.normal) self.startRecording() } }//eom //MARK: - Memory override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } 

FileManager(创build临时文件path)

 import Foundation @objc class MyFileManager:NSObject { private let unique_debug = true private var _temporyDirectory:String = "" //MARK: - Properties var directory:String { return _temporyDirectory } //MARK: - Init override init() { super.init() _temporyDirectory = NSTemporaryDirectory() }//eom func createHomeDirFileUniqueWithName(_ myFileName:String, andExtension fileExtension:String)->URL { //filename let time:Date = Date.init() let dateformatter:DateFormatter = DateFormatter() dateformatter .dateFormat = "ddMMyyyy-hh-mm-ss-a" let tempDate:String = dateformatter .string(from: time) let tempFileName = "\(myFileName)-\(tempDate).\(fileExtension)" //directory var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] documentsDirectory.appendPathComponent(tempFileName) if unique_debug { print("\(documentsDirectory)") } return documentsDirectory }//eom //MARK: - Names func createGlobalUniqueFileName(_ myFileName:String)->String { let guid = ProcessInfo.processInfo.globallyUniqueString let uniqueFileName = ("\(myFileName)_\(guid)") if unique_debug { print("\(uniqueFileName)") } return uniqueFileName }//eom func createUniqueNameWithFilename(_ myFileName:String, andExtension fileExtension:String)->String { //filename let time:Date = Date.init() let dateformatter:DateFormatter = DateFormatter() dateformatter .dateFormat = "ddMMyyyy-hh-mm-ss-a" let currentDateString = dateformatter .string(from: time) let finalName = myFileName + currentDateString + "." + fileExtension if unique_debug { print("\(finalName)") } return finalName }//eom //MARK: - Paths func createTempFilePathWithUniqueName(_ myFileName:String, andExtension fileExtension:String)->String { let tempFileName = self.createUniqueNameWithFilename(myFileName, andExtension: fileExtension) let tempFile = _temporyDirectory + tempFileName if unique_debug { print("\(tempFile)") } return tempFile }//eom //MARK: - Helpers func enumerateDirectory(directory:String) { do { let filesInDir:[String] = try FileManager.default.contentsOfDirectory(atPath: directory) for currFile in filesInDir { print(currFile) }//eofl } catch let error { print("error: \(error.localizedDescription)") } }//eom func doesFileExistInDirectory(filename:String) -> Bool { do { let filesInDir:[String] = try FileManager.default.contentsOfDirectory(atPath: _temporyDirectory) for currFile in filesInDir { print(currFile) if currFile == filename { return true } }//eofl } catch let error { print("error: \(error.localizedDescription)") } return false }//eom }//eoc 

您不会将当前包的数量重置为零,因此在后续录制中,您要求AudioFileWritePackets开始以非零的起始数据包写入其新文件,拒绝这样做。

正确的解决scheme是(可能)每次开始录制时重新创buildRecordState ,尽pipe很简单

 recordState!.currentPacket = 0 

之前调用AudioQueueNewInput似乎也工作。

Interesting Posts