如何录制音频流并将其保存在文件/swift 4.2中

Nic*_*aft 5 audio record stream ios swift

我正在为 iPhone 创建一个收音机应用程序(用 Swift 4.2 编码),我想添加一个功能,允许我记录并保存在文件中,当我按下按钮时收音机产生的声音(从 AVPlayer 读取)。我应该使用哪个代码?

该代码使用 Swift 4.2 和 Xcode 10.1。我在网上搜索:“如何录制音频流 swift 4.2”,“如何从 AVPlayer swift 4.2 录制音频”,但我找不到答案。

我的代码:

import UIKit
import AVFoundation
import MediaPlayer

class ViewControllerPlayer: UIViewController { 

    var URl = "http://link_of_audio_stream"
    var player:AVPlayer?
    var playerItem:AVPlayerItem?
    var playerLayer:AVPlayerLayer?

    override func viewDidLoad() {
        super.viewDidLoad()

        let url = URL(string: URl)
        let playerItem1:AVPlayerItem = AVPlayerItem(url: url!)
        player = AVPlayer(playerItem: playerItem1)

    }

    @IBAction func Play(_ sender: Any) {
            player?.play()
    }
    @IBAction func Pause(_ sender: Any) {
            player?.pause()
    }
private var audioRecorder: AVAudioRecorder!

    func startRecording() throws {
        guard let newFileURL = createURLForNewRecord() else {
            throw RecordingServiceError.canNotCreatePath
        }
        do {
            var urlString = URL(string: URl)
            urlString = newFileURL
            audioRecorder = try AVAudioRecorder(url: newFileURL,
                                                settings: [AVFormatIDKey:Int(kAudioFormatMPEG4AAC),
                                                           AVSampleRateKey: 8000,
                                                           AVNumberOfChannelsKey: 1,
                                                           AVEncoderAudioQualityKey: AVAudioQuality.min.rawValue])
            audioRecorder.delegate = self as? AVAudioRecorderDelegate
            audioRecorder.prepareToRecord()

            audioRecorder.record(forDuration: TimeConstants.recordDuration) 
            //error: Use of unresolved identifier 'TimeConstants'

        } catch let error {
            print(error)
        }
    }

    func STOPREC1() throws {
        audioRecorder.stop()
        audioRecorder = nil
            print("Recording finished successfully.")
    }

    enum RecordingServiceError: String, Error {
        case canNotCreatePath = "Can not create path for new recording"
    }

    private func createURLForNewRecord() -> URL? {
        guard let appGroupFolderUrl = FileManager.getAppFolderURL() else {
            return nil
        }

        let date = String(describing: Date())
        let fullFileName = "Enregistrement radio " + date + ".m4a"
        let newRecordFileName = appGroupFolderUrl.appendingPathComponent(fullFileName)
        return newRecordFileName
    }
}
    extension FileManager {
        class func getAppFolderURL() -> URL? {
            let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            let documentsDirectory = paths[0]
            return documentsDirectory
        }
    }
Run Code Online (Sandbox Code Playgroud)

Nic*_*aft 4

经过多次网上搜索,我找到了解决方案。

\n

我在互联网上的某个地方发现了这个名为 \xc2\xab\xc2\xa0CachingPlayerItem.swift\xc2\xa0\xc2\xbb 的 Swift 类,它将允许录制在线音频流。

\n
import Foundation\nimport AVFoundation\n\nfileprivate extension URL {\n    \n    func withScheme(_ scheme: String) -> URL? {\n        var components = URLComponents(url: self, resolvingAgainstBaseURL: false)\n        components?.scheme = scheme\n        return components?.url\n    }\n    \n}\n\n@objc protocol CachingPlayerItemDelegate {\n    \n    /// Is called when the media file is fully downloaded.\n    @objc optional func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data)\n    \n    /// Is called every time a new portion of data is received.\n    @objc optional func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int)\n    \n    /// Is called after initial prebuffering is finished, means\n    /// we are ready to play.\n    @objc optional func playerItemReadyToPlay(_ playerItem: CachingPlayerItem)\n    \n    /// Is called when the data being downloaded did not arrive in time to\n    /// continue playback.\n    @objc optional func playerItemPlaybackStalled(_ playerItem: CachingPlayerItem)\n    \n    /// Is called on downloading error.\n    @objc optional func playerItem(_ playerItem: CachingPlayerItem, downloadingFailedWith error: Error)\n    \n}\n\nopen class CachingPlayerItem: AVPlayerItem {\n    \n    class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {\n        \n        var playingFromData = false\n        var mimeType: String? // is required when playing from Data\n        var session: URLSession?\n        var mediaData: Data?\n        var response: URLResponse?\n        var pendingRequests = Set<AVAssetResourceLoadingRequest>()\n        weak var owner: CachingPlayerItem?\n        var fileURL: URL!\n        var outputStream: OutputStream?\n        \n        func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {\n            \n            if playingFromData {\n                \n                // Nothing to load.\n                \n            } else if session == nil {\n                \n                // If we're playing from a url, we need to download the file.\n                // We start loading the file on first request only.\n                guard let initialUrl = owner?.url else {\n                    fatalError("internal inconsistency")\n                }\n\n                startDataRequest(with: initialUrl)\n            }\n            \n            pendingRequests.insert(loadingRequest)\n            processPendingRequests()\n            return true\n            \n        }\n        \n        func startDataRequest(with url: URL) {\n            \n            var recordingName = "record.mp3"\n            if let recording = owner?.recordingName{\n                recordingName = recording\n            }\n            \n            fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)\n                .appendingPathComponent(recordingName)\n            let configuration = URLSessionConfiguration.default\n            configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData\n            session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)\n            session?.dataTask(with: url).resume()\n            outputStream = OutputStream(url: fileURL, append: true)\n            outputStream?.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default)\n            outputStream?.open()\n            \n        }\n        \n        func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {\n            pendingRequests.remove(loadingRequest)\n        }\n        \n        // MARK: URLSession delegate\n        \n        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {\n            let bytesWritten = data.withUnsafeBytes{outputStream?.write($0, maxLength: data.count)}\n        }\n        \n        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {\n            completionHandler(Foundation.URLSession.ResponseDisposition.allow)\n            mediaData = Data()\n            self.response = response\n            processPendingRequests()\n        }\n        \n        func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {\n            if let errorUnwrapped = error {\n                owner?.delegate?.playerItem?(owner!, downloadingFailedWith: errorUnwrapped)\n                return\n            }\n            processPendingRequests()\n            owner?.delegate?.playerItem?(owner!, didFinishDownloadingData: mediaData!)\n        }\n        \n        // MARK: -\n        \n        func processPendingRequests() {\n            \n            // get all fullfilled requests\n            let requestsFulfilled = Set<AVAssetResourceLoadingRequest>(pendingRequests.compactMap {\n                self.fillInContentInformationRequest($0.contentInformationRequest)\n                if self.haveEnoughDataToFulfillRequest($0.dataRequest!) {\n                    $0.finishLoading()\n                    return $0\n                }\n                return nil\n            })\n        \n            // remove fulfilled requests from pending requests\n            _ = requestsFulfilled.map { self.pendingRequests.remove($0) }\n\n        }\n        \n        func fillInContentInformationRequest(_ contentInformationRequest: AVAssetResourceLoadingContentInformationRequest?) {\n            if playingFromData {\n                contentInformationRequest?.contentType = self.mimeType\n                contentInformationRequest?.contentLength = Int64(mediaData!.count)\n                contentInformationRequest?.isByteRangeAccessSupported = true\n                return\n            }\n            \n            guard let responseUnwrapped = response else {\n                // have no response from the server yet\n                return\n            }\n            \n            contentInformationRequest?.contentType = responseUnwrapped.mimeType\n            contentInformationRequest?.contentLength = responseUnwrapped.expectedContentLength\n            contentInformationRequest?.isByteRangeAccessSupported = true\n            \n        }\n        \n        func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool {\n            \n            let requestedOffset = Int(dataRequest.requestedOffset)\n            let requestedLength = dataRequest.requestedLength\n            let currentOffset = Int(dataRequest.currentOffset)\n            \n            guard let songDataUnwrapped = mediaData,\n                songDataUnwrapped.count > currentOffset else {\n                return false\n            }\n            \n            let bytesToRespond = min(songDataUnwrapped.count - currentOffset, requestedLength)\n            let dataToRespond = songDataUnwrapped.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond)))\n            dataRequest.respond(with: dataToRespond)\n            \n            return songDataUnwrapped.count >= requestedLength + requestedOffset\n            \n        }\n        \n        deinit {\n            session?.invalidateAndCancel()\n        }\n        \n    }\n    \n    fileprivate let resourceLoaderDelegate = ResourceLoaderDelegate()\n    fileprivate let url: URL\n    fileprivate let initialScheme: String?\n    fileprivate var customFileExtension: String?\n    \n    \n    weak var delegate: CachingPlayerItemDelegate?\n    \n    func stopDownloading(){\n        resourceLoaderDelegate.session?.invalidateAndCancel()\n    }\n    \n    open func download() {\n        if resourceLoaderDelegate.session == nil {\n            resourceLoaderDelegate.startDataRequest(with: url)\n        }\n    }\n    \n    private let cachingPlayerItemScheme = "cachingPlayerItemScheme"\n    var recordingName = "record.mp3"\n    /// Is used for playing remote files.\n    convenience init(url: URL, recordingName: String) {\n        self.init(url: url, customFileExtension: nil, recordingName: recordingName)\n    }\n    \n    /// Override/append custom file extension to URL path.\n    /// This is required for the player to work correctly with the intended file type.\n    init(url: URL, customFileExtension: String?, recordingName: String) {\n        \n        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),\n            let scheme = components.scheme,\n            var urlWithCustomScheme = url.withScheme(cachingPlayerItemScheme) else {\n            fatalError("Urls without a scheme are not supported")\n        }\n        self.recordingName = recordingName\n        self.url = url\n        self.initialScheme = scheme\n        \n        if let ext = customFileExtension {\n            urlWithCustomScheme.deletePathExtension()\n            urlWithCustomScheme.appendPathExtension(ext)\n            self.customFileExtension = ext\n        }\n        \n        let asset = AVURLAsset(url: urlWithCustomScheme)\n        asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)\n        super.init(asset: asset, automaticallyLoadedAssetKeys: nil)\n        \n        resourceLoaderDelegate.owner = self\n        \n        addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)\n        \n        NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)\n        \n    }\n    \n    /// Is used for playing from Data.\n    init(data: Data, mimeType: String, fileExtension: String) {\n        \n        guard let fakeUrl = URL(string: cachingPlayerItemScheme + "://whatever/file.\\(fileExtension)") else {\n            fatalError("internal inconsistency")\n        }\n        \n        self.url = fakeUrl\n        self.initialScheme = nil\n        \n        resourceLoaderDelegate.mediaData = data\n        resourceLoaderDelegate.playingFromData = true\n        resourceLoaderDelegate.mimeType = mimeType\n        \n        let asset = AVURLAsset(url: fakeUrl)\n        asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)\n        super.init(asset: asset, automaticallyLoadedAssetKeys: nil)\n        resourceLoaderDelegate.owner = self\n        \n        addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)\n        \n        NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)\n        \n    }\n    \n    // MARK: KVO\n    \n    override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {\n        delegate?.playerItemReadyToPlay?(self)\n    }\n    \n    // MARK: Notification hanlers\n    \n    @objc func playbackStalledHandler() {\n        delegate?.playerItemPlaybackStalled?(self)\n    }\n\n    // MARK: -\n    \n    override init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?) {\n        fatalError("not implemented")\n    }\n    \n    deinit {\n        NotificationCenter.default.removeObserver(self)\n        removeObserver(self, forKeyPath: "status")\n        resourceLoaderDelegate.session?.invalidateAndCancel()\n    }\n    \n}\n
Run Code Online (Sandbox Code Playgroud)\n

之后,在您的主 swift 文件中,您将以下代码记录下来:

\n
let recordingName = "my_rec_name.mp3"\nvar playerItem: CachingPlayerItem!\nlet url_stream = URL(string: "http://my_url_stream_link")\nplayerItem = CachingPlayerItem(url: url_stream!, recordingName: recordingName ?? "record.mp3")\nvar player1 = AVPlayer(playerItem: playerItem)\nplayer1.automaticallyWaitsToMinimizeStalling = false\n
Run Code Online (Sandbox Code Playgroud)\n

要停止记录,请使用以下代码:

\n
playerItem.stopDownloading()\nrecordingName = nil\nplayerItem = nil\n
Run Code Online (Sandbox Code Playgroud)\n

录音将保存在您的应用程序的目录中。

\n