2016-11-12 13 views
2

下面的录音机只能在第一次使用时,如果您尝试第二次录音,则会在尝试AudioFileWritePackets时发出错误“kAudioFileInvalidPacketOffsetError”。Swift 3 LPCM录音机|错误:kAudioFileInvalidPacketOffsetError

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

预先感谢您

Repository located here

记录

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 
    } 

的ViewController

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. 
    } 


} 

文件管理器(创建临时文件路径)

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 

回答

1

你不重置currentPacket计数为零,所以在随后的录音,你问AudioFileWritePackets,开始以非零启动包,写入到其新的文件,该文件它拒绝这样做。

正确的解决方法(可能)重新创建每次启动一个记录时间RecordState,尽管调用AudioQueueNewInput似乎太工作之前hackily设置

recordState!.currentPacket = 0 

+0

感谢以下命令完美工作 recordState!.currentPacket = 0 – LuAndre

+0

[如何从视图控制器和模型记录不同?(http://stackoverflow.com/questions/40595382/recording-from-viewcontroller-vs -a-model-class)在从模型中记录时获取diff nsdata – LuAndre