Save audio with fade in fade out with setVolumeRampFromStartVolume not working in iOS

3.6k views Asked by At

I am trying to cut an audio file for an iPhone project. I can cut it and save it, but any fade in / fade out that I try to apply doesn't work, the audio file is just saved cutted but not faded.

I am using the following code:

//
// NO PROBLEMS TO SEE HERE, MOVE ON
//
    NSArray *documentsFolders = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    int currentFileNum = 10;
    NSURL *url = [NSURL fileURLWithPath: [[documentsFolders objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%d.%@", AUDIO_SOURCE_FILE_NAME ,currentFileNum, AUDIO_SOURCE_FILE_EXTENSION ]]];
    NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
                                                        forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:options];
    AVAssetExportSession* exporter = [AVAssetExportSession exportSessionWithAsset:asset presetName:AVAssetExportPresetAppleM4A];

    for (NSString* filetype in exporter.supportedFileTypes) {
        if ([filetype isEqualToString:AVFileTypeAppleM4A]) {
            exporter.outputFileType = AVFileTypeAppleM4A;
            break;
        }
    }
    if (exporter.outputFileType == nil) {
        NSLog(@"Needed output file type not found? (%@)", AVFileTypeAppleM4A);
        //return;
    }

    NSString* outPath = [[documentsFolders objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%d.%@", AUDIO_CUTTED_FILE_NAME ,currentFileNum, AUDIO_SOURCE_FILE_EXTENSION ]];

    NSURL* const outUrl = [NSURL fileURLWithPath:outPath];
    exporter.outputURL = outUrl;

    float endTrimTime = CMTimeGetSeconds(asset.duration);
    float startTrimTime = fminf(AUDIO_DURATION, endTrimTime);
    CMTime startTrimCMTime=CMTimeSubtract(asset.duration, CMTimeMake(startTrimTime, 1));
    exporter.timeRange = CMTimeRangeMake(startTrimCMTime, asset.duration);

//
// TRYING TO APPLY FADEIN FADEOUT, NOT WORKING, NO RESULTS, "CODE IGNORED"
//
    AVMutableAudioMix *exportAudioMix = [AVMutableAudioMix audioMix];

    NSMutableArray* inputParameters = [NSMutableArray arrayWithCapacity:1];

    CMTime startFadeInTime = startTrimCMTime;
    CMTime endFadeInTime = CMTimeMake(startTrimTime+1, 1);
    CMTime startFadeOutTime = CMTimeMake(endTrimTime-1, 1);
    CMTime endFadeOutTime = CMTimeMake(endTrimTime, 1);

    CMTimeRange fadeInTimeRange = CMTimeRangeFromTimeToTime(startFadeInTime, endFadeInTime);

    CMTimeRange fadeOutTimeRange = CMTimeRangeFromTimeToTime(startFadeOutTime, endFadeOutTime);

    AVMutableAudioMixInputParameters *exportAudioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParameters];
    [exportAudioMixInputParameters setVolume:0.0 atTime:CMTimeMakeWithSeconds(startTrimTime-0.01, 1)];
    [exportAudioMixInputParameters setVolumeRampFromStartVolume:0.0 toEndVolume:1.0 timeRange:fadeInTimeRange];
    [exportAudioMixInputParameters setVolumeRampFromStartVolume:1.0 toEndVolume:0.0 timeRange:fadeOutTimeRange];

    [inputParameters insertObject:exportAudioMixInputParameters atIndex:0];

    exportAudioMix.inputParameters = inputParameters;
    exporter.audioMix = exportAudioMix;

    [exporter exportAsynchronouslyWithCompletionHandler:^(void) {
        NSString* message;
        switch (exporter.status) {
            case AVAssetExportSessionStatusFailed:
                message = [NSString stringWithFormat:@"Export failed. Error: %@", exporter.error.description];
                [asset release];
                break;
            case AVAssetExportSessionStatusCompleted: {
                [asset release];
                [self reallyConvert:currentFileNum];
                message = [NSString stringWithFormat:@"Export completed: %@", outPath];
                break;
            }
            case AVAssetExportSessionStatusCancelled:
                message = [NSString stringWithFormat:@"Export cancelled!"];
                [asset release];
                break;
            default:
                NSLog(@"Export 4 unhandled status: %d", exporter.status);
                [asset release];
                break;
        }       
    }];
4

There are 4 answers

1
Scorpreg On

Here is the solution.

setVolumeRampFromStartVolume doesn't work.

AVMutableAudioMix *exportAudioMix = [AVMutableAudioMix audioMix];
AVMutableAudioMixInputParameters *exportAudioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:track];

//fade in

[exportAudioMixInputParameters setVolume:0.0 atTime:CMTimeMakeWithSeconds(start-1, 1)];
[exportAudioMixInputParameters setVolume:0.1 atTime:CMTimeMakeWithSeconds(start, 1)];
[exportAudioMixInputParameters setVolume:0.5 atTime:CMTimeMakeWithSeconds(start+1, 1)];
[exportAudioMixInputParameters setVolume:1.0 atTime:CMTimeMakeWithSeconds(start+2, 1)];

//fade out

[exportAudioMixInputParameters setVolume:1.0 atTime:CMTimeMakeWithSeconds((start+length-2), 1)];
[exportAudioMixInputParameters setVolume:0.5 atTime:CMTimeMakeWithSeconds((start+length-1), 1)];
[exportAudioMixInputParameters setVolume:0.1 atTime:CMTimeMakeWithSeconds((start+length), 1)];

exportAudioMix.inputParameters = [NSArray arrayWithObject:exportAudioMixInputParameters];


// configure export session  output with all our parameters
exportSession.outputURL = [NSURL fileURLWithPath:filePath]; // output path
exportSession.outputFileType = AVFileTypeAppleM4A; // output file type
exportSession.timeRange = exportTimeRange; // trim time ranges
exportSession.audioMix = exportAudioMix; // fade in audio mix
// perform the export
[exportSession exportAsynchronouslyWithCompletionHandler:^{

    if (AVAssetExportSessionStatusCompleted == exportSession.status) {
        NSLog(@"AVAssetExportSessionStatusCompleted");

    } else if (AVAssetExportSessionStatusFailed == exportSession.status) {
        NSLog(@"AVAssetExportSessionStatusFailed");

    } else {
        NSLog(@"Export Session Status: %d", exportSession.status);
    }
}];
0
Taiko On

I've made the same mistake as you dozens of times ! Apple's API is really weird on this :

CMTimeRange fadeInTimeRange = CMTimeRangeFromTimeToTime(startFadeInTime, endFadeInTime);

CMTimeRange fadeOutTimeRange = CMTimeRangeFromTimeToTime(startFadeOutTime, endFadeOutTime);

Should be :

CMTimeRangeFromTimeToTime(startFadeInTime, fadeInDURATION);

CMTimeRangeFromTimeToTime(startFadeOutTime, fadeOutDURATION);

CMTimeRange is created from start and duration, not from start and end !

But most of the time, the end time is also the duration (if the start time is 0) that's why so many people (including me) make the mistake.

And no Apple, that's not intuitive at all !

0
Julio Bailon On

You need to select the track. Instead of calling:

AVMutableAudioMixInputParameters *exportAudioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParameters];

Call:

AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0];

AVMutableAudioMixInputParameters *exportAudioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:assetTrack];

In your existing code you can also specify the track like this:

exportAudioMixInputParameters.trackID = [[[asset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0] trackID];

Good luck!

0
CarmeloS On

This is my working code, just take it and have a nice day!

+(void)makeAudioFadeOutWithSourceURL:(NSURL*)sourceURL destinationURL:(NSURL*)destinationURL fadeOutBeginSecond:(NSInteger)beginTime fadeOutEndSecond:(NSInteger)endTime fadeOutBeginVolume:(CGFloat)beginVolume fadeOutEndVolume:(CGFloat)endVolume callback:(void(^)(BOOL))callback
{
    NSAssert(callback, @"need callback");
    NSParameterAssert(beginVolume >= 0 && beginVolume <=1);
    NSParameterAssert(endVolume >= 0 && endVolume <= 1);

    BOOL sourceExist = [[NSFileManager defaultManager] fileExistsAtPath:sourceURL.path];
    NSAssert(sourceExist, @"source not exist");

    AVURLAsset *asset = [AVAsset assetWithURL:sourceURL];;

    AVAssetExportSession* exporter = [AVAssetExportSession exportSessionWithAsset:asset presetName:AVAssetExportPresetAppleM4A];

    exporter.outputURL = destinationURL;
    exporter.outputFileType = AVFileTypeAppleM4A;

    AVMutableAudioMix *exportAudioMix = [AVMutableAudioMix audioMix];

    AVMutableAudioMixInputParameters *exportAudioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:asset.tracks.lastObject];
    [exportAudioMixInputParameters setVolumeRampFromStartVolume:beginVolume toEndVolume:endVolume timeRange:CMTimeRangeMake(CMTimeMakeWithSeconds(beginTime, 1), CMTimeSubtract(CMTimeMakeWithSeconds(endTime, 1), CMTimeMakeWithSeconds(beginTime, 1)))];
    NSArray *audioMixParameters = @[exportAudioMixInputParameters];
    exportAudioMix.inputParameters = audioMixParameters;

    exporter.audioMix = exportAudioMix;

    [exporter exportAsynchronouslyWithCompletionHandler:^(void){
        AVAssetExportSessionStatus status = exporter.status;
        if (status != AVAssetExportSessionStatusCompleted) {
            if (callback) {
                callback(NO);
            }
        }
        else {
            if (callback) {
                callback(YES);
            }
        }
        NSError *error = exporter.error;
        NSLog(@"export done,error %@,status %d",error,status);
    }];
}