skip to Main Content

I’m trying to convert mp4 video file to m4a audio format by AVAssetExportSession on my iOS app.

This is the conversion code:

let outputUrl = URL(fileURLWithPath: NSTemporaryDirectory() + "out.m4a")
if FileManager.default.fileExists(atPath: outputUrl.path) {
    try? FileManager.default.removeItem(atPath: outputUrl.path)
}

let asset = AVURLAsset(url: inputUrl)
// tried the `AVAssetExportPresetAppleM4A` preset name but the same result
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputFileType = AVFileType.m4a
exportSession.outputURL = outputUrl

await exportSession.export()
switch exportSession.status {
case .completed:
    return outputUrl
default:
    // This becomes `4` which is `.failed`
    print("Status: (exportSession.status)")
    throw exportSession.error!
}

Currently, it seems to work on iPhone simulators (confirmed on iOS 16.1/15.5) but it doesn’t on my iPhone 7 (iOS 15.7.1) real device. It doesn’t seem to work as well on my colleague’s iOS 16.1 real device, so it shouldn’t be a matter of the iOS version.

The mp4 file is located in the iOS Files app and the inputUrl in the above code becomes something like this (I get this URL via UIDocumentPickerViewController):

  • file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Downloads/%E3%81%8A%E3%81%97%E3%82%83%E3%81%B8%E3%82%99%E3%82%8A%E3%81%B2%E3%82%8D%E3%82%86%E3%81%8D.mp4

and the error is:

  • Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x2808f30c0 {Error Domain=NSOSStatusErrorDomain Code=-16979 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16979), NSLocalizedRecoverySuggestion=XXXXDEFAULTVALUEXXXX, NSURL=file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Downloads/%E3%81%8A%E3%81%97%E3%82%83%E3%81%B8%E3%82%99%E3%82%8A%E3%81%B2%E3%82%8D%E3%82%86%E3%81%8D.mp4, NSLocalizedDescription=The operation could not be completed}

2

Answers


  1. Chosen as BEST ANSWER

    It seems to be resolved by calling startAccessingSecurityScopedResource() to the inputUrl before exporting.

    inputUrl.startAccessingSecurityScopedResource()
    

    Not sure exactly why but that's probably because the inputUrl is under the file:///private namespace?


  2. Use this function for extract audio from video :—-

    Export audio from video url into new path :-

        
    
    func extractAudioFromVideo(videoUrl:URL) {
            
            let mixComposition: AVMutableComposition = AVMutableComposition()
            var mutableCompositionAudioVideoTrack: [AVMutableCompositionTrack] = []
            let videoAsset: AVAsset = AVAsset(url: videoUrl)
            
            if let audioVideoTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid){
                
                mutableCompositionAudioVideoTrack.append(audioVideoTrack)
                
                if let audioVideoAssetTrack: AVAssetTrack = videoAsset.tracks(withMediaType: .audio).first {
                    do {
                        try mutableCompositionAudioVideoTrack.first?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: audioVideoAssetTrack, at: CMTime.zero)
                    } catch {
                        print(error)
                    }
                }
                
            }
            
            if let documentsPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first {
                let outputURL = URL(fileURLWithPath: documentsPath).appendingPathComponent(".m4a")
                do {
                    if FileManager.default.fileExists(atPath: outputURL.path) {
                        try FileManager.default.removeItem(at: outputURL)
                    }
                } catch { }
                
                
                if let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetAppleM4A) {
                    exportSession.outputURL = outputURL
                    exportSession.outputFileType = AVFileType.m4a
                    exportSession.shouldOptimizeForNetworkUse = true
                    exportSession.exportAsynchronously(completionHandler: {
                        
                        switch exportSession.status {
                            
                        case . completed:
                            
                            DispatchQueue.main.async {
                                print("audio url :---- (outputURL)")
                                // -------- play output audio URL in player ------
                            }
                            
                        case .failed:
                            if let _error = exportSession.error {
                                print(_error.localizedDescription)
                            }
                            
                        case .cancelled:
                            if let _error = exportSession.error {
                                print(_error.localizedDescription)
                            }
                            
                        default:
                            print("")
                        }
                    })
                }
                
            }
            
        }
    
    

    AVMutableComposition Play :-

    You can play direct AVMutableComposition without exporting audio track.

    Benefits of play AVMutableComposition is that you can instant play audio into player.

    var avplayer = AVPlayer()
    var playerController : AVPlayerViewController?
    
    
    @IBAction func btnAudioPlay(sender:UIButton) {
            
        self.playAudioCompositionFromVideo(fromVideoURL: URL(string: "")!) { Composition in
               
              let playerItem = AVPlayerItem(asset: Composition)
              self.playerController = AVPlayerViewController()
              self.avplayer = AVPlayer(playerItem: playerItem)
              self.playerController?.player = self.avplayer
              self.playerController?.player?.play()
                
          } failure: { errore in
              print(errore as Any)
         }
    }
    
    
    func playAudioCompositionFromVideo(fromVideoURL url: URL, success: @escaping ((AVMutableComposition) -> Void), failure: @escaping ((String?) -> Void)) {
            
            
            let asset = AVPlayerItem(url: url).asset
            let mixComposition = AVMutableComposition()
            let timeRange = CMTimeRangeMake(start: CMTime.zero, duration: asset.duration)
            
            //------------ Get Audio Tracks From Asset ---------
            
            let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
            
            if audioTracks.count > 0 {
                
                // ---- Use audio if video contains the audio track ---
                
                let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
                
                // -------- Get First Audio track --------
                
                guard let audioTrack = audioTracks.first else { return }
                
                do {
                    try compositionAudioTrack?.insertTimeRange(timeRange, of: audioTrack, at: CMTime.zero)
                   
                    compositionAudioTrack?.preferredTransform = audioTrack.preferredTransform
                    
                    success(mixComposition)
                    
                } catch _ {
                    failure("audio track insert failed!")
                }
            } else {
                failure("audio track is not available!")
            }
    
     }
        
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search