代码之家  ›  专栏  ›  技术社区  ›  LuAndre

AudioToolBox记录器受到AVFoundation AudioPlayer的影响

  •  1
  • LuAndre  · 技术社区  · 8 年前

    因此,我有以下课程:

    1. 录音机(使用 AudioToolbox CoreAudio )记录音频的。
    2. 音频播放器(使用 AVFoundation )

    记录器捕获音频,将其发送到服务器,然后服务器用另一个音频回复,然后播放器播放接收到的音频。

    当我试图再次呼叫录音机录制音频时,它无法正确录制音频。

    您知道如何重置录音机,以便在音频播放器播放结束后正确录音吗?

    我试图再次初始化记录器(就在录制之前),但无法正常工作。

    影响录音机的一行是下面的两行,不幸的是,播放音频时需要这两行 AVFoundation .

    let sharedSession = AVAudioSession.sharedInstance()
    try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
    

    执行流程

    • 录音机音频
    • 向服务器发送和接收音频
    • 播放服务器录音机音频中的音频 此时,记录器无法正确记录
    • 向服务器发送和接收音频 失败
    • 播放音频 失败

    链接 recorder project

    录音机:

    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?
        private var _audioURL:URL?
    
        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:Status, _ data:NSData?, _ errorDesc:String?) -> 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
        private func getDocumentsPath()->URL
        {
            let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            let documentsDirectory = paths[0]
            return documentsDirectory
        }
    
        func setOutputFileNameWithDocumentsDirectory(nameDesired:String)
        {
            _audioURL = getDocumentsPath().appendingPathComponent(nameDesired)
            setOutputFile(url: _audioURL!)
        }//eom
    
        func setOutputFileNameWithTempDirectory(nameDesired:String)
        {
            let tempDir = NSTemporaryDirectory()
            let tempURLdir = URL(fileURLWithPath: tempDir)
            _audioURL = tempURLdir.appendingPathComponent(nameDesired)
            setOutputFile(url: _audioURL!)
        }//eom
    
        private func setOutputFile(path: String)
        {
            setOutputFile(url: URL(fileURLWithPath: path))
        }//eom
    
        private 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, nil, nil)
    
            self._recordState?.currentPacket = 0
    
            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
                        {
                            if verbose
                            {
    
                                print("Error with AudioFileWritePackets")
                                //<----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
                        {
                            if verbose
                            {
                                print("Error with AudioQueueEnqueueBuffer")
                                //<----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
            {
                handler?(.error, nil, "Error setting audio input.")
            }
        }//eom
    
        func stop()
        {
            _recordState?.recording = false
            if let recordingState: RecordState = _recordState
            {
                AudioQueueStop(recordingState.queue.pointee!, true)
                AudioQueueDispose(recordingState.queue.pointee!, true)
                AudioFileClose(recordingState.file!)
    
                let audioData:NSData? = NSData(contentsOf: _audioURL!)
                handler?(.ready, audioData, nil)
            }
        }//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 {
                handler?(.error, nil, "Unable to get audio queue property.")
            }
        }//eom
    }
    

    玩家:

    import UIKit
    import AVFoundation
    
    
    protocol AudioPlayerDelegate {
        func audioPlayer_playbackError(playerItemID:String, error:String)
        func audioPlayer_playbackSuccess(playerItemID:String)
    }
    
    class AudioPlayer: NSObject, AVAudioPlayerDelegate
    {
        //properties
        private var _audioPlayer:AVAudioPlayer?
        var delegate:AudioPlayerDelegate?
        var playerItemID:String = ""
        var volume:Float?
    
        //MARK: - Play Audio
        func playAudioFromData(_ playerItemID:String, dataToPlay:Data)
        {
            do {
                let sharedSession = AVAudioSession.sharedInstance()
                try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
                try sharedSession.setActive(true)
    
                _audioPlayer = try AVAudioPlayer(data: dataToPlay)
    
                _audioPlayer?.numberOfLoops         = 0
                _audioPlayer?.isMeteringEnabled     = true
                _audioPlayer?.delegate              = self
    
                //volume
                if volume != nil {
                    _audioPlayer?.volume = volume!
                }
    
                //id
                self.playerItemID = playerItemID
    
                _audioPlayer?.play()
            }
            catch let error {
                self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: error.localizedDescription)
            }
        }//eom
    
        func playAudioFromUrl(_ url:URL)
        {
            do {
                let sharedSession = AVAudioSession.sharedInstance()
                try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
                try sharedSession.setActive(true)
    
                if FileManager.default.fileExists(atPath: url.path) {
                    _audioPlayer = try AVAudioPlayer(contentsOf: url)
    
                    _audioPlayer?.numberOfLoops         = 0
                    _audioPlayer?.isMeteringEnabled     = true
                    _audioPlayer?.delegate              = self
    
                    //volume
                    if volume != nil {
                        _audioPlayer?.volume = volume!
                    }
    
                    //id
                    self.playerItemID = url.absoluteString
    
                    _audioPlayer?.play()
                }
                else {
                    self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: "audio file does not exist")
                }
            }
            catch let error  {
                self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: error.localizedDescription)
            }
        }//eom
    
        //MARK: - Player Options
        func pausePlay()
        {
            _audioPlayer?.pause()
        }//eom
    
        func stopPlay()
        {
            _audioPlayer?.stop()
    
            do {
                let sharedSession = AVAudioSession.sharedInstance()
                try sharedSession.setActive(false)
            }
            catch let error {
                if verbose { print("un-able to set session to inactive, error: \(error)") }
            }
        }//eom
    
        //MARK: - Delegates
        func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
            //inactive session
            do {
                let sharedSession = AVAudioSession.sharedInstance()
                try sharedSession.setActive(false)
            }
            catch let error {
                if verbose { print("un-able to set session to inactive, error: \(error)") }
            }
    
            //report status
            if error != nil {
                self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: error!.localizedDescription)
            }
            else {
                self.delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: "decode error did occurred")
            }
    
            //reset
            self._audioPlayer?.delegate = nil
            self._audioPlayer = nil
            self.playerItemID = ""
        }//eom
    
        func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
    
            //inactive session
            do {
                let sharedSession = AVAudioSession.sharedInstance()
                try sharedSession.setActive(false)
            }
            catch let error {
                if verbose { print("un-able to set session to inactive, error: \(error)") }
            }
    
            //report status
            if flag {
                delegate?.audioPlayer_playbackSuccess(playerItemID: self.playerItemID)
            }
            else {
                delegate?.audioPlayer_playbackError(playerItemID: self.playerItemID, error: "player finish playing with error")
            }
    
            //reset
            self._audioPlayer?.delegate = nil
            self._audioPlayer = nil
            self.playerItemID = ""
        }//eom
    
    }//eoc
    
    1 回复  |  直到 8 年前
        1
  •  2
  •   mcfresh    8 年前

    如果您要同时使用AudioToolBox和AVFoundation,您可能需要小心使用AudioSession。AVFoundation对后端的AudioSession进行了大量更新。

    您的播放器的快速修复方法是删除任何音频会话调用,如下所示:

    let sharedSession = AVAudioSession.sharedInstance()
    try sharedSession.setCategory(AVAudioSessionCategoryPlayback)
    try sharedSession.setActive(true)
    
    _audioPlayer?.numberOfLoops         = 0
    _audioPlayer?.isMeteringEnabled     = true
    

    要了解更多高级音频操作,请参阅本书 Learning Core Audio by Chris Adamson, Kevin Avila