Skip to content

Commit 7f10ff2

Browse files
committed
feat(audio): add audio recording to video file capture
1 parent 2bbf7f6 commit 7f10ff2

File tree

1 file changed

+77
-2
lines changed

1 file changed

+77
-2
lines changed

AllSpark-ios/CameraViewController.swift

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
88
// Camera session
99
private var captureSession: AVCaptureSession!
1010
private var videoOutput: AVCaptureVideoDataOutput!
11+
private var audioOutput: AVCaptureAudioDataOutput!
1112
private var currentCameraPosition: AVCaptureDevice.Position = .front
1213

1314
// Orientation management
@@ -24,6 +25,7 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
2425
// Video Recording
2526
private var assetWriter: AVAssetWriter?
2627
private var assetWriterInput: AVAssetWriterInput?
28+
private var audioWriterInput: AVAssetWriterInput?
2729
private var adapter: AVAssetWriterInputPixelBufferAdaptor?
2830
private var isRecording = false
2931
private var sessionAtSourceTime: CMTime?
@@ -400,6 +402,23 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
400402

401403
adapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: sourcePixelBufferAttributes)
402404

405+
// Setup audio writer input
406+
let audioOutputSettings: [String: Any] = [
407+
AVFormatIDKey: kAudioFormatMPEG4AAC,
408+
AVNumberOfChannelsKey: 1,
409+
AVSampleRateKey: 44100.0,
410+
AVEncoderBitRateKey: 128000
411+
]
412+
413+
audioWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
414+
audioWriterInput?.expectsMediaDataInRealTime = true
415+
416+
if let audioInput = audioWriterInput, assetWriter!.canAdd(audioInput) {
417+
assetWriter!.add(audioInput)
418+
} else {
419+
print("Failed to add audio input to asset writer")
420+
}
421+
403422
assetWriter!.startWriting()
404423
// assetWriter!.startSession(atSourceTime: .zero) // REMOVED: Will start session on first frame
405424

@@ -418,7 +437,7 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
418437
}
419438
}
420439
} else {
421-
print("Failed to add input to asset writer")
440+
print("Failed to add video input to asset writer")
422441
}
423442
} catch {
424443
print("Failed to setup asset writer: \(error)")
@@ -441,6 +460,7 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
441460
}
442461

443462
assetWriterInput?.markAsFinished()
463+
audioWriterInput?.markAsFinished()
444464
assetWriter?.finishWriting { [weak self] in
445465
guard let self = self, let url = self.videoURL else {
446466
completion?()
@@ -481,6 +501,7 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
481501

482502
self.assetWriter = nil
483503
self.assetWriterInput = nil
504+
self.audioWriterInput = nil
484505
self.adapter = nil
485506
}
486507
}
@@ -511,11 +532,44 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
511532
captureSession.addOutput(videoOutput)
512533
}
513534

535+
// Request microphone access and setup audio
536+
AVAudioApplication.requestRecordPermission { [weak self] granted in
537+
if granted {
538+
DispatchQueue.main.async {
539+
self?.setupAudioInput()
540+
}
541+
} else {
542+
print("Microphone permission denied")
543+
}
544+
}
514545

515546
// Set initial video orientation
516547
updateVideoOrientation()
517548
}
518549

550+
private func setupAudioInput() {
551+
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
552+
print("Failed to get audio device")
553+
return
554+
}
555+
556+
guard let audioInput = try? AVCaptureDeviceInput(device: audioDevice) else {
557+
print("Failed to create audio input")
558+
return
559+
}
560+
561+
if captureSession.canAddInput(audioInput) {
562+
captureSession.addInput(audioInput)
563+
}
564+
565+
audioOutput = AVCaptureAudioDataOutput()
566+
audioOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "audioQueue"))
567+
568+
if captureSession.canAddOutput(audioOutput) {
569+
captureSession.addOutput(audioOutput)
570+
}
571+
}
572+
519573
private func updateVideoOrientation() {
520574
if #available(iOS 17.0, *), let videoCaptureDevice = captureSession.inputs.first as? AVCaptureDeviceInput {
521575
// Initialize RotationCoordinator if needed
@@ -606,6 +660,21 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
606660
return outputImage
607661
}
608662

663+
private func recordAudioFrame(_ sampleBuffer: CMSampleBuffer) {
664+
guard isRecording, let audioInput = audioWriterInput, audioInput.isReadyForMoreMediaData else { return }
665+
666+
if sessionAtSourceTime == nil {
667+
let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
668+
sessionAtSourceTime = timestamp
669+
assetWriter?.startSession(atSourceTime: timestamp)
670+
}
671+
672+
let success = audioInput.append(sampleBuffer)
673+
if !success {
674+
print("Failed to append audio buffer. Writer status: \(String(describing: assetWriter?.status.rawValue)) Error: \(String(describing: assetWriter?.error))")
675+
}
676+
}
677+
609678
private func recordVideoFrame(_ image: CIImage, timestamp: CMTime) {
610679
guard isRecording, let adapter = adapter, let input = assetWriterInput, input.isReadyForMoreMediaData else { return }
611680

@@ -972,9 +1041,15 @@ extension CameraViewController {
9721041
}
9731042
}
9741043

975-
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
1044+
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate {
9761045
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
1046+
// Handle audio output
1047+
if output == audioOutput {
1048+
recordAudioFrame(sampleBuffer)
1049+
return
1050+
}
9771051

1052+
// Handle video output
9781053
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
9791054
return
9801055
}

0 commit comments

Comments
 (0)