1

I'm trying to record video and audio and sending them over the network so that they can be played back in real time on other clients. I've managed to record and play back video successfully, but audio still cannot be played back (see AVAudioPlayer at the bottom of the code below). What am I doing wrong or what is missing? (There are a couple of other StackOverflow questions which seem to address the same issue, but even if the comments there show that some people were able to make it work, none of them show an explicit, working answer.) Thank you in advance for any input.

let captureSession = AVCaptureSession()

private func startVideoFeed() {
    let sessionPreset = AVCaptureSession.Preset.low
    if captureSession.canSetSessionPreset(sessionPreset) {
        captureSession.sessionPreset = sessionPreset
    }
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { success in
            self.startVideoFeed()
        }
    case .authorized:
        captureSession.beginConfiguration()
        let captureVideoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)!
        let captureVideoInput = try! AVCaptureDeviceInput(device: captureVideoDevice)
        if captureSession.canAddInput(captureVideoInput) {
            captureSession.addInput(captureVideoInput)
        }
        let captureVideoOutput = AVCaptureVideoDataOutput()
        captureVideoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        if captureSession.canAddOutput(captureVideoOutput) {
            captureSession.addOutput(captureVideoOutput)
        }
        captureSession.commitConfiguration()
        captureSession.startRunning()
    default:
        break
    }
}

private func startAudioFeed() {
    switch AVCaptureDevice.authorizationStatus(for: .audio) {
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .audio) { success in
            self.startAudioFeed()
        }
    case .authorized:
        captureSession.beginConfiguration()
        let captureAudioDevice = AVCaptureDevice.default(for: .audio)!
        let captureAudioInput = try! AVCaptureDeviceInput(device: captureAudioDevice)
        if captureSession.canAddInput(captureAudioInput) {
            captureSession.addInput(captureAudioInput)
        }
        let captureAudioOutput = AVCaptureAudioDataOutput()
        captureAudioOutput.audioSettings = [AVFormatIDKey: kAudioFormatLinearPCM, AVNumberOfChannelsKey: NSNumber(value: 1), AVSampleRateKey: NSNumber(value: 44100)]
        captureAudioOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        if captureSession.canAddOutput(captureAudioOutput) {
            captureSession.addOutput(captureAudioOutput)
        }
        captureSession.commitConfiguration()
    default:
        break
    }
}

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    if let imageBuffer = sampleBuffer.imageBuffer {
        let ciImage = CIImage(cvPixelBuffer: imageBuffer)
        let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent)!
        let data = CFDataCreateMutable(nil, 0)!
        let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil)!
        CGImageDestinationAddImage(imageDestination, cgImage, [kCGImageDestinationLossyCompressionQuality: NSNumber(value: 0)] as CFDictionary)
        CGImageDestinationFinalize(imageDestination)
        play(data: data as Data)
    } else if let dataBuffer = sampleBuffer.dataBuffer {
        let data = try! dataBuffer.dataBytes()
        play(data: data)
    }
}

private func play(data: Data) {
    if let image = CGImage(jpegDataProviderSource: CGDataProvider(data: data as CFData)!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) {
        // image is a valid image
    } else if let audioPlayer = try? AVAudioPlayer(data: data) {
        audioPlayer.play()
        // audioPlayer is always nil with error: Error Domain=NSOSStatusErrorDomain Code=1954115647 "(null)"
    }
}
Nickkk
  • 2,261
  • 1
  • 25
  • 34

1 Answers1

1

https://developer.apple.com/documentation/coremedia/1489629-cmsamplebuffergetdatabuffer

The caller does not own the returned dataBuffer, and must retain it explicitly if the caller needs to maintain a reference to it.

You will need to do a CFRetain and CFRelease on the data.

The player is nil because of the data you are initializing it with. Convert sampleBuffer to Data.

NSOSStatusErrorDomain Code=1954115647

cora
  • 1,916
  • 1
  • 10
  • 18
  • Thank you very much for your help, but it seems that `CFRetain` doesn't exist with ARC. Also, since I'm using `dataBuffer.dataBytes()`, which is documented as `This function is used to copy bytes out of a CMBlockBuffer`, is it really necessary to retain anything here since the data is copied? – Nickkk Feb 07 '21 at 17:17
  • I am not familiar with dataBytes(). I edited the answer and included a link to a comment that explains how to convert sampleBuffer into Data. @Nickkk – cora Feb 07 '21 at 17:38
  • Thanks, I've tried that. Now curiously my original code produces the same output as your suggestion, without having changed it. I get these errors: ```[] CMIOHardware.cpp:379:CMIOObjectGetPropertyData Error: 2003332927, failed [] CMIO_Unit_Converter_Audio.cpp:590:RebuildAudioConverter AudioConverterSetProperty(dbca) failed (1886547824) [ac] ACMP3Decoder.cpp:229:SetCurrentInputFormat: (0x7fb601287440) input sample rates must be either 8, 11.025, 12, 16, 22.05, 24, 32, 44.1 or 48 kHz``` – Nickkk Feb 08 '21 at 12:56
  • ```[ac] ACMP3Decoder.cpp:229:SetCurrentInputFormat: (0x7fb601287440) input sample rates must be either 8, 11.025, 12, 16, 22.05, 24, 32, 44.1 or 48 kHz [AQ] AudioQueueObject.cpp:388:AudioQueueObject: AudioConverterNew from AudioQueueNew returned 1718449215 io: 2 ch, 12000 Hz, Float32, non-inter client: 2 ch, 12000 Hz, '.mp2' (0x00000000) 0 bits/channel, 0 bytes/packet, 1152 frames/packet, 0 bytes/frame MP4::LATMHeader::GetStreamFormatInfo Failed LOASAudioFile::ParseAudioFile failed EC3AudioFile::ParseAudioFile : ParseOneCycle failed``` – Nickkk Feb 08 '21 at 12:59
  • ```EC3AudioFile::ParseAudioFile failed [] CMIOHardware.cpp:323:CMIOObjectGetPropertyData the System is exiting [] CMIOHardware.cpp:379:CMIOObjectGetPropertyData Error: 1970171760, failed [] CMIO_DALA_System.cpp:264:GetPropertyData error 1970171760 (unop) getting property selector (inot) scope (glob) element 0``` – Nickkk Feb 08 '21 at 13:00