Mixing two Audio Files using AVComposition on IOS
I'm posting the code that I eventually got to work, in case anybody else is trying to do the same thing and would like some code samples (My problem above I suspect was that the audio files weren't being loaded correctly)
[self showActivityIndicator]; // This code takes a while so show the user an activity Indicator
AVMutableComposition *composition = [AVMutableComposition composition];
NSArray* tracks = [NSArray arrayWithObjects:@"backingTrack", @"RobotR33", nil];
NSString* audioFileType = @"wav";
for (NSString* trackName in tracks) {
AVURLAsset* audioAsset = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:trackName ofType:audioFileType]]options:nil];
AVMutableCompositionTrack* audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
NSError* error;
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset.duration) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0] atTime:kCMTimeZero error:&error];
if (error)
{
NSLog(@"%@", [error localizedDescription]);
}
}
AVAssetExportSession* _assetExport = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
NSString* mixedAudio = @"mixedAudio.m4a";
NSString *exportPath = [NSTemporaryDirectory() stringByAppendingString:mixedAudio];
NSURL *exportURL = [NSURL fileURLWithPath:exportPath];
if ([[NSFileManager defaultManager]fileExistsAtPath:exportPath]) {
[[NSFileManager defaultManager]removeItemAtPath:exportPath error:nil];
}
_assetExport.outputFileType = AVFileTypeAppleM4A;
_assetExport.outputURL = exportURL;
_assetExport.shouldOptimizeForNetworkUse = YES;
[_assetExport exportAsynchronouslyWithCompletionHandler:^{
[self hideActivityIndicator];
NSLog(@"Completed Sucessfully");
}];
Concatenate two audio files in Swift and play them
I got your code working by changing two things:
the preset name: from
AVAssetExportPresetPassthrough
toAVAssetExportPresetAppleM4A
the output file type: from
AVFileTypeWAVE
toAVFileTypeAppleM4A
Modify your assetExport
declaration like this:
var assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
assetExport.outputFileType = AVFileTypeAppleM4A
then it will properly merge the files.
It looks like AVAssetExportSession
only exports M4A format and ignores other presets. There may be a way to make it export other formats (by subclassing it?), though I haven't explored this possibility yet.
Merging audio tracks in a single track using AVMutableComposition
OK, finally I should have found the solution. Really using the AVMutableAudioMix
resulting movie file has only one audio track instead of two.
EDIT
Answering to Justin comment, here is the trick:
let audioMix = AVMutableAudioMix()
let vip = AVMutableAudioMixInputParameters(track: self.videoAudioTrack!)
vip.trackID = self.videoAudioTrack!.trackID
vip.setVolume(self.videoAudioMixerVolume, at: .zero)
let aip = AVMutableAudioMixInputParameters(track: self.audioTrack!)
aip.trackID = self.audioTrack!.trackID
aip.setVolume(self.audioMixerVolume, at: .zero)
audioMix.inputParameters = [vip, aip]
easset.audioMix = audioMix
Where videoAudioTrack
is the audio track for the video clip, wherease audioTrack
is another simple audio track. easset
is the AVAssetExporterSession
object.
SWIFT - Is it possible to save audio from AVAudioEngine, or from AudioPlayerNode? If yes, how?
Yes, it's quite easy. You simply put a tap on a node and save the buffer into a file.
Unfortunately this means you have to play through the node. I was hoping that AVAudioEngine would let me process one sound file into another directly, but apparently that's impossible - you have to play and process in real time.
iOS - Mixing two audio files
This code merges the audio tracks down into one - I think that's thanks to the AVMutableComposition
(and not the AVAssetExportSession
):
AVAssetTrack *videoTrack = ...;
AVAssetTrack *audioTrack1 = ...;
AVAssetTrack *audioTrack2 = ...;
AVMutableComposition *composition = [AVMutableComposition composition];
for (AVAssetTrack* inputTrack in @[videoTrack, audioTrack1, audioTrack2]) {
AVMutableCompositionTrack* outputTrack;
outputTrack = [composition addMutableTrackWithMediaType:inputTrack.mediaType preferredTrackID:kCMPersistentTrackID_Invalid];
NSError* error;
if (![outputTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, inputTrack.timeRange.duration) ofTrack:inputTrack atTime:kCMTimeZero error:&error]) {
NSLog(@"insertTimeRange error: %@", error);
}
}
AVAssetExportSession* exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
NSURL *outputURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0] URLByAppendingPathComponent:@"output.mp4"];
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.outputURL = outputURL;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (AVAssetExportSessionStatusCompleted == exportSession.status) {
NSLog(@"success %@", outputURL);
} else {
NSLog(@"failed %li", (long)exportSession.status);
}
}];
Setting multiple Volumes to each Video tracks using AudioMixInputParameters AVFoundation is not working in Swift iOS
Here is the working solution for my question:
func addVolumeToIndividualVideoClip(_ assetURL: URL, video: VideoFileModel, completion : ((_ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?){
//Create Asset from Url
let filteredVideoAsset: AVAsset = AVAsset(url: assetURL)
video.fileID = String(video.videoID)
//Get the path of App Document Directory
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let url = URL(fileURLWithPath: documentDirectory).appendingPathComponent("\(video.fileID)_\("FilterVideo").mov")
let filePath = url.path
let fileManager = FileManager.default
do {
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
try fileManager.removeItem(atPath:filePath)
} else {
print("FILE NOT AVAILABLE")
}
} catch _ {
}
let composition: AVMutableComposition = AVMutableComposition()
let compositionVideo: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID())
let compositionAudioVideo: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
//Add video to the final record
do {
try compositionVideo.insertTimeRange(CMTimeRangeMake(kCMTimeZero, filteredVideoAsset.duration), of: filteredVideoAsset.tracks(withMediaType: AVMediaTypeVideo)[0], at: kCMTimeZero)
} catch _ {
}
//Extract audio from the video and the music
let audioMix: AVMutableAudioMix = AVMutableAudioMix()
var audioMixParam: [AVMutableAudioMixInputParameters] = []
let assetVideoTrack: AVAssetTrack = filteredVideoAsset.tracks(withMediaType: AVMediaTypeAudio)[0]
let videoParam: AVMutableAudioMixInputParameters = AVMutableAudioMixInputParameters(track: assetVideoTrack)
videoParam.trackID = compositionAudioVideo.trackID
//Set final volume of the audio record and the music
videoParam.setVolume(video.videoVolume, at: kCMTimeZero)
//Add setting
audioMixParam.append(videoParam)
//Add audio on final record
//First: the audio of the record and Second: the music
do {
try compositionAudioVideo.insertTimeRange(CMTimeRangeMake(kCMTimeZero, filteredVideoAsset.duration), of: assetVideoTrack, at: kCMTimeZero)
} catch _ {
assertionFailure()
}
//Fading volume out for background music
let durationInSeconds = CMTimeGetSeconds(filteredVideoAsset.duration)
let firstSecond = CMTimeRangeMake(CMTimeMakeWithSeconds(0, 1), CMTimeMakeWithSeconds(1, 1))
let lastSecond = CMTimeRangeMake(CMTimeMakeWithSeconds(durationInSeconds-1, 1), CMTimeMakeWithSeconds(1, 1))
videoParam.setVolumeRamp(fromStartVolume: 0, toEndVolume: video.videoVolume, timeRange: firstSecond)
videoParam.setVolumeRamp(fromStartVolume: video.videoVolume, toEndVolume: 0, timeRange: lastSecond)
//Add parameter
audioMix.inputParameters = audioMixParam
//Remove the previous temp video if exist
let filemgr = FileManager.default
do {
if filemgr.fileExists(atPath: "\(video.fileID)_\("FilterVideo").mov") {
try filemgr.removeItem(atPath: "\(video.fileID)_\("FilterVideo").mov")
} else {
}
} catch _ {
}
//Exporte the final record’
let exporter: AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)!
exporter.outputURL = url
exporter.outputFileType = AVFileTypeMPEG4
exporter.audioMix = audioMix
exporter.exportAsynchronously(completionHandler: { () -> Void in
completion!(exporter, url)
// self.saveVideoToLibrary(from: filePath)
})
}
Related Topics
Remove Cell When Button Pressed Inside Cell Customtableviewcell
How to Cut a Hole in a Sprite Image or Texture to Show What Is Behind It Using Spritekit in Swift
Open Attachment from Mail Using iOS 8 App [Swift]
In Swift Can You Trap "Fatal Error Unexpectedly Found Nil While Unwrapping an Optional Value"
Adding Data to a Specific UId in Firebase
Avoid Equatable and Hashable Boilerplate, Swift 4.2
Sklabelnode Text with Two Different Fonts and Colour. How Is This Possible
Spritekit: Sprites Are Moving Through Each Other with a Physicsbody Already Set
Protocol Variable Implementing Another Protocol
Swift-Animate Cashapelayer Stroke Color
Swift: Draw a Semi-Sphere in Mkmapview
Swift, Detect Ibeacons on The Background and Send Notifications When in Range
Transparent Sticky Header UI Collectionview Don't Show Cells Underneath
Is It a Good Way to Access Instance Variable with Self? If I Use a Lot
How to Make Phone Calls in Swift
How to Set Local Notifications Between 8Am and 8Pm Every Day
Cloudkit - What to Do When a User Adds, Modifies or Deletes an Object While Offline