Audio export fail iOS Swift - ios

I'm trying to concatenate two audio files with the following code:
func concatenateFiles(audioFiles: [NSURL], completion: (concatenatedFile: NSURL?) -> ()) {
// Result file
var nextClipStartTime = kCMTimeZero
let composition = AVMutableComposition()
let track = composition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
// Add each track
for audio in audioFiles {
let asset = AVURLAsset(URL: NSURL(fileURLWithPath: audio.path!), options: nil)
if let assetTrack = asset.tracksWithMediaType(AVMediaTypeAudio).first {
let timeRange = CMTimeRange(start: kCMTimeZero, duration: asset.duration)
do {
try track.insertTimeRange(timeRange, ofTrack: assetTrack, atTime: nextClipStartTime)
nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRange.duration)
} catch {
print("Error concatenating file - \(error)")
completion(concatenatedFile: nil)
return
}
}
}
// Export the new file
if let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough) {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documents = NSURL(string: paths.first!)
if let fileURL = documents?.URLByAppendingPathComponent("\(stringFromDate(NSDate())).m4a") {
// Remove existing file
do {
try NSFileManager.defaultManager().removeItemAtPath(fileURL.path!)
print("Removed \(fileURL)")
} catch {
print("Could not remove file - \(error)")
}
// Configure export session output
exportSession.outputURL = fileURL
exportSession.outputFileType = AVFileTypeAppleM4A
// Perform the export
exportSession.exportAsynchronouslyWithCompletionHandler() { handler -> Void in
if exportSession.status == .Completed {
print("Export complete")
dispatch_async(dispatch_get_main_queue(), {
completion(concatenatedFile: fileURL)
})
return
} else if exportSession.status == .Failed {
print("Export failed - \(exportSession.error)")
}
completion(concatenatedFile: nil)
return
}
}
}
}
but i've receive this error exporting the file:
Export failed - Optional(Error Domain=AVFoundationErrorDomain Code=-11838 "Operation Stopped" UserInfo={NSLocalizedDescription=Operation Stopped, NSLocalizedFailureReason=The operation is not supported for this media.})
i've try to change the format but doesnt work, i have no more idea,
there's some one can help me?

I really don't know why, but the problem was solved when i change the attribution of the outputURL in the exportSession,
before:
exportSession.outputURL = fileURL
now:
exportSession.outputURL = NSURL.fileURLWithPath(fileURL.path!)

Related

AVAssetExportSession succeeds to convert mp4 to m4a on iPhone simulator but iPhone device

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}
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?
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!")
}
}

how to get video and audio from MKV? (swift)

i want to convert MKV (Matroska) to MP4 in swift
when i add a file with MKV format my code break in line 8 , what should i do to fix that?
this is my code:
let composition = AVMutableComposition()
do {
let sourceUrl = Bundle.main.url(forResource: "sample", withExtension: "mov")!
let asset = AVURLAsset(url: sourceUrl)
8 ->here guard let videoAssetTrack = asset.tracks(withMediaType: AVMediaType.video).first else { return }
guard let audioCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid) else { return }
try audioCompositionTrack.insertTimeRange(videoAssetTrack.timeRange, of: videoAssetTrack, at: CMTime.zero)
} catch {
print(error)
}
// Create an export session
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputFileType = AVFileType.mp4
exportSession.outputURL = browseURL
// Export file
exportSession.exportAsynchronously {
guard case exportSession.status = AVAssetExportSession.Status.completed else { return }
DispatchQueue.main.async {
// Present a UIActivityViewController to share audio file
print("completed")
}
}

AVAssetExportSession throws error when exporting AVAsset to temporary iOS path

I am trying to trim a local MP3-File picked by the user before for getting an 18 seconds snippet. This snippet should be exported to a temporary file path. This is my code:
guard songUrl.startAccessingSecurityScopedResource() else {
print("failed to access path")
return
}
// Make sure you release the security-scoped resource when you are done.
defer { songUrl.stopAccessingSecurityScopedResource() }
// Use file coordination for reading and writing any of the URL’s content.
var error: NSError? = nil
NSFileCoordinator().coordinate(readingItemAt: songUrl, error: &error) { (url) in
// Set temporary file path
let temporaryDirectoryUrl: URL = FileManager.default.temporaryDirectory
let temporaryDirectoryString: String = temporaryDirectoryUrl.absoluteString
let temporaryFilename = ProcessInfo().globallyUniqueString + ".m4a"
let temporaryFilepath = URL(string: (temporaryDirectoryString + temporaryFilename))!
// shorten audio file
let originalAsset = AVAsset(url: (url))
if let exporter = AVAssetExportSession(asset: originalAsset, presetName: AVAssetExportPresetAppleM4A) {
exporter.outputFileType = AVFileType.m4a
exporter.outputURL = temporaryFilepath
let originalDuration = Int64(CMTimeGetSeconds(originalAsset.duration))
let halftime: Int64 = (originalDuration/2)
let startTime = CMTimeMake(value: (halftime-9), timescale: 1)
let stopTime = CMTimeMake(value: (halftime+9), timescale: 1)
exporter.timeRange = CMTimeRangeFromTimeToTime(start: startTime, end: stopTime)
print(CMTimeGetSeconds(startTime), CMTimeGetSeconds(stopTime))
//Export audio snippet
exporter.exportAsynchronously(completionHandler: {
print("export complete \(exporter.status)")
switch exporter.status {
case AVAssetExportSessionStatus.failed:
if let e = exporter.error {
print("export failed \(e)")
}
case AVAssetExportSessionStatus.cancelled:
print("export cancelled \(String(describing: exporter.error))")
default:
print("export complete")
self.shortenedExported(temporaryFilePath: temporaryFilepath)
}
})
}
else {
print("cannot create AVAssetExportSession for asset \(originalAsset)")
}
}
It prints the following:
export complete AVAssetExportSessionStatus
export failed Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-17508), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x282368b40 {Error Domain=NSOSStatusErrorDomain Code=-17508 "(null)"}}
I don't get an error when I use a MP3-file from my bundle's resources using Bundle.main.url(forResource: "sample_song", withExtension: "mp3") instead of the Coordinators's url
Thanks in advance!
For anyone with the same problem: I could solve it using an AVMutableComposition():
// Access url
guard songUrl.startAccessingSecurityScopedResource() else {
print("failed to access path")
return
}
// Make sure you release the security-scoped resource when you are done.
defer { songUrl.stopAccessingSecurityScopedResource() }
// Use file coordination for reading and writing any of the URL’s content.
var error: NSError? = nil
NSFileCoordinator().coordinate(readingItemAt: songUrl, error: &error) { (url) in
// Set temporary file's path
let temporaryDirectoryUrl: URL = FileManager.default.temporaryDirectory
let temporaryFilename = ProcessInfo().globallyUniqueString
let temporaryFilepath = temporaryDirectoryUrl.appendingPathComponent("\(temporaryFilename).m4a")
// Prework
let originalAsset = AVURLAsset(url: url)
print(originalAsset)
let composition = AVMutableComposition()
let audioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)!
let originalDuration = Int64(CMTimeGetSeconds(originalAsset.duration))
let startTime, stopTime: CMTime
// Shorten audio file if longer than 20 seconds
if originalDuration < 20 {
startTime = CMTimeMake(value: 0, timescale: 1)
stopTime = CMTimeMake(value: originalDuration, timescale: 1)
}
else {
let halftime: Int64 = (originalDuration/2)
startTime = CMTimeMake(value: (halftime-10), timescale: 1)
stopTime = CMTimeMake(value: (halftime+10), timescale: 1)
}
// Export shortened file
do {
try audioTrack.insertTimeRange(CMTimeRangeFromTimeToTime(start: startTime, end: stopTime), of: originalAsset.tracks(withMediaType: AVMediaType.audio)[0], at: CMTime.zero)
let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)!
if FileManager.default.fileExists(atPath: temporaryFilepath.absoluteString) {
try? FileManager.default.removeItem(atPath: temporaryFilepath.absoluteString)
print("removed existing file")
}
assetExport.outputFileType = AVFileType.m4a
assetExport.outputURL = temporaryFilepath
assetExport.shouldOptimizeForNetworkUse = true
assetExport.exportAsynchronously(completionHandler: {
switch assetExport.status {
case AVAssetExportSessionStatus.failed:
if let e = assetExport.error {
print("export failed \(e)")
}
case AVAssetExportSessionStatus.cancelled:
print("export cancelled \(String(describing: assetExport.error))")
default:
print("export completed")
}
})
}
catch {
print("error trying to shorten audio file")
}

AVAssetTrack does not see AVMediaTypeAudio

I am new in swift and programming, I tried to concatenate some recorder files, which I made with success like that:
func concatenateFiles(audioFiles: [URL], completion: #escaping (_ concatenatedFile: NSURL?) -> ()) {
// Result file
var nextClipStartTime = kCMTimeZero
let composition = AVMutableComposition()
let track = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
// Add each track
for audio in audioFiles {
let asset = AVURLAsset(url: NSURL(fileURLWithPath: audio.path) as URL, options: nil)
if let assetTrack = asset.tracks(withMediaType: AVMediaTypeAudio).first {
let timeRange = CMTimeRange(start: kCMTimeZero, duration: asset.duration)
do {
try track.insertTimeRange(timeRange, of: assetTrack, at: nextClipStartTime)
nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRange.duration)
} catch {
print("Error concatenating file - \(error)")
completion(nil)
return
}
}
}
// Export the new file
if let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A) {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let format = DateFormatter()
format.dateFormat = "yyyy:MM:dd-HH:mm:ss"
let currentFileName = "REC:\(format.string(from: Date()))"
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsDirectory.appendingPathComponent("\(currentFileName).m4a")
// Remove existing file
do {
print(audioFiles.count)
try FileManager.default.removeItem(atPath: fileURL.path)
print("Removed \(fileURL)")
} catch {
print("Could not remove file - \(error)")
}
// Configure export session output
exportSession.outputURL = fileURL as URL
exportSession.outputFileType = AVFileTypeAppleM4A
// Perform the export
exportSession.exportAsynchronously() { () -> Void in
switch exportSession.status
{
case AVAssetExportSessionStatus.completed:
print("Export complete")
DispatchQueue.main.async(execute: {
if self.concatinatedArray == nil
{
self.concatinatedArray = [URL]()
}
self.concatinatedArray?.append(exportSession.outputURL!)
completion(fileURL as NSURL?)
})
return print("success to Merge Video")
case AVAssetExportSessionStatus.failed:
completion(nil)
return print("failed to MERGE )")
case AVAssetExportSessionStatus.cancelled:
completion(nil)
return print("cancelled merge)")
default:
print("complete")
}
}
}
}
But now, when I want to Merge it with a video, I got crashes on moment:
let aAudioAssetTrack: AVAssetTrack = aAudioAsset.tracks(withMediaType: AVMediaTypeAudio)[0]
I use standard method of merging, it works with other sounds that I have, it doesn't only with the concatenated audio files.. please help how to manage it working?...
AVURLAsset* avAsset = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:path2] options:nil];
if ([[avAsset tracksWithMediaType:AVMediaTypeAudio] count] > 0)
{
AVAssetTrack *clipAudioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
[firstTrackAudio insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration) ofTrack:clipAudioTrack atTime:kCMTimeZero error:nil];
}

append or concatenate audio files in swift

Hi I want to append voice files.
I'm recording voice with AVAudioRecorder, but to play the recording I need to call "stop", but after playing it I want to continue record. Like the native iOS Voice memo app.
Should I use AVMutableCompositionTrack and how do I do that in swift? Thanks!
If you are looking to simply pause your recording and continue it later you can use AVAudioRecorder's pause() function rather than stop() and it will continue the recording when you use play() again.
However, if you are looking to actually concatenate audio files, you can do it like this:
func concatenateFiles(audioFiles: [NSURL], completion: (concatenatedFile: NSURL?) -> ()) {
guard audioFiles.count > 0 else {
completion(concatenatedFile: nil)
return
}
if audioFiles.count == 1 {
completion(concatenatedFile: audioFiles.first)
return
}
// Concatenate audio files into one file
var nextClipStartTime = kCMTimeZero
let composition = AVMutableComposition()
let track = composition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
// Add each track
for recording in audioFiles {
let asset = AVURLAsset(URL: NSURL(fileURLWithPath: recording.path!), options: nil)
if let assetTrack = asset.tracksWithMediaType(AVMediaTypeAudio).first {
let timeRange = CMTimeRange(start: kCMTimeZero, duration: asset.duration)
do {
try track.insertTimeRange(timeRange, ofTrack: assetTrack, atTime: nextClipStartTime)
nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRange.duration)
} catch {
print("Error concatenating file - \(error)")
completion(concatenatedFile: nil)
return
}
}
}
// Export the new file
if let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough) {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documents = NSURL(string: paths.first!)
if let fileURL = documents?.URLByAppendingPathComponent("file_name.caf") {
// Remove existing file
do {
try NSFileManager.defaultManager().removeItemAtPath(fileURL.path!)
print("Removed \(fileURL)")
} catch {
print("Could not remove file - \(error)")
}
// Configure export session output
exportSession.outputURL = NSURL.fileURLWithPath(fileURL.path!)
exportSession.outputFileType = AVFileTypeCoreAudioFormat
// Perform the export
exportSession.exportAsynchronouslyWithCompletionHandler() { handler -> Void in
if exportSession.status == .Completed {
print("Export complete")
dispatch_async(dispatch_get_main_queue(), {
completion(file: fileURL)
})
return
} else if exportSession.status == .Failed {
print("Export failed - \(exportSession.error)")
}
completion(concatenatedFile: nil)
return
}
}
}
}

Resources