@@ -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