【问题标题】:How to send an audio file to a server while the user is recording from the microphone?用户从麦克风录制时如何将音频文件发送到服务器?
【发布时间】:2022-01-23 10:41:25
【问题描述】:

当用户从麦克风录制音频笔记时,我想将对应于最后 x 秒录制的音频文件发送到我的服务器

根据我的研究,我在全球范围内了解到我应该将AVAudioEngineinstallTap 函数一起使用。我的代码如下所示:

func start() {
    engine = AVAudioEngine()
    guard let inputNode = engine?.inputNode else { return }
    let bus = 0
    let format = inputNode.inputFormat(forBus: bus)
    engine?.inputNode.installTap(onBus: bus, bufferSize: 2048, format: format) { pcmBuffer, audioTime in
    // This is were I would like to send the audio to my server
    }
    engine?.prepare()
    do {
      try engine?.start()
    } catch {
      print("error:", error.localizedDescription)
    }
  }

但是,闭包返回AVAudioPCMBufferAVAudioTime,我找不到任何方法将其转换为音频文件(m4a、mp3 等...)。是否有任何 API 可以让我这样做?

我也考虑过AVAudioRecorder,问题是如果录音很长,发送到我的服务器需要很长时间...这就是为什么我转向了一个更on-the-飞解决方案

感谢您的帮助

【问题讨论】:

    标签: swift avaudiosession avaudiorecorder avaudioengine


    【解决方案1】:

    我找不到 m4a 或 mp3 的编码器,但我可以找到将缓冲区转换为 aac 文件的解决方案。

    如果您要实现 mp3,我认为aac 是类似的,因为它们可以按顺序首尾相连,并且可以无缝播放。

    所以我的思考过程是:

    1. 在文档目录中创建一个新的 aac 文件并保持打开状态
    2. 开始录制并将输入缓冲区附加到此 aac 文件中
    3. x 秒后,关闭当前打开的文件
    4. 关闭文件后,将最后一个文件上传到您的服务器
    5. 从第 1 步开始重复

    我使用的资源将帮助您了解我所做的事情:

    实施

    您可能已经这样做了,但对于其他偶然发现的人来说,首先,在 info.plist 中添加正确的权限以访问麦克风:

        <key>NSMicrophoneUsageDescription</key>
        <string>Test app needs to access your microphone</string>
    

    接下来,我会记录一些变量:

    // AVAudioEngine used to record
    var engine = AVAudioEngine()
    
    // Set this as per your liking (512, 1024, 2048)
    let estimatedBufferSize: AVAudioFrameCount = 1024
    
    // Will be configured to write the buffer to a file
    var file: AVAudioFile?
    
    // Chunk duration in seconds, adjust as needed
    let chunkDuration: Float64 = 10
    
    // Will be used to uniquely name the different chunks
    var currentChunkCount = 0
    
    // Keeps track of how many frames in current chunk
    // Used to check how much time has elapsed
    var framesInCurrentChunk: AVAudioFrameCount = 0
    
    

    这是 cmets 的其余逻辑

    // Wire this to your start recording button
    @objc
    private func startRecording()
    {
        print("start recording")
        
        // Prepare how the AVAudioEngine should process input
        engine.inputNode.installTap(onBus: 0,
                                    bufferSize: 1024,
                                    format: engine.inputNode.inputFormat(forBus: 0))
        { [weak self] (buffer, time) -> Void in
            
            // Write the buffer to your file
            self?.writeBufferToFile(buffer: buffer)
        }
        
        // Start recording
        try! engine.start()
    }
    
    // Wire this to your stop recording button
    @objc
    private func stopAndPlayRecording()
    {
        print("stop recording")
        
        // Clean up and reset
        engine.inputNode.removeTap(onBus: 0)
        engine.stop()
        file = nil
        framesInCurrentChunk = 0
        
        // Here is where you should upload any files that have not been uploaded
        // Delete files after upload if you want or manage file name duplicates
    }
    
    private func writeBufferToFile(buffer: AVAudioPCMBuffer)
    {
        let samplesPerSecond = buffer.format.sampleRate
        
        // Check if we have an open file writer
        if file == nil
        {
            // Configure an AVAudioFile to write the audio buffer to file
            prepareOutputFile()
        }
        
        do
        {
            try file?.write(from: buffer)
            framesInCurrentChunk += buffer.frameLength
        }
        catch
        {
            // error appending the chunk to file
            print(error)
        }
        
        // Check if the current chunk has reached it's duration
        if framesInCurrentChunk > AVAudioFrameCount(chunkDuration * samplesPerSecond)
        {
            // Here is where you have a valid chunk that has been saved in
            // the duration you want, put the last saved aac file in a queue to be
            // uploaded to your server here
            
            // De-initialize the current file writer so we can start a new one
            file = nil
        }
    }
    
    private func prepareOutputFile()
    {
        // Increment the current chunk count to create a new file
        currentChunkCount += 1
        
        // Set the path of where the file will be stored in the document directory
        let documentsURL = FileManager.default.urls(for: .documentDirectory,
                                                    in: .userDomainMask)[0]
        
        let outputURL = documentsURL.appendingPathComponent("recording_\(currentChunkCount).aac")
        
        print("Recording audio to path: \(outputURL)")
        
        do
        {
            // Configure the AVAudioFile with the output path and output format
            file = try AVAudioFile(forWriting: outputURL,
                                   settings: [AVFormatIDKey: kAudioFormatMPEG4AAC])
            
            
        }
        catch
        {
            // Handle errors in configuring the the AVAudioFile
            print(error)
        }
        
        // Reset the frames saved in the current chunk
        framesInCurrentChunk = 0
    }
    

    如果你在 iOS 设备上运行它,你应该会在运行 startRecording() 函数后看到它打印出当前记录输入流的文件的路径

    运行stopAndPlayRecording()后,您可以查看您的文档目录,您将看到保存在您的文档目录中的AAC文件可以上传。 Here is how you can check that on a device

    这是我的测试的输出:

    最后的想法

    • 您需要在最后处理错误,我保持简短
    • 您可能希望使用某种逻辑删除文件以避免覆盖记录

    试一试,如果这对您有用,请在 cmets 中告诉我,或者如果有帮助,我可以准备一个测试项目。

    【讨论】:

      猜你喜欢
      • 2020-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-24
      • 2018-06-07
      • 2023-03-21
      • 1970-01-01
      相关资源
      最近更新 更多