1

我正在使用 AVAssetReader 和 AVAssetWriter 来反转音频文件。但是,产生的反转音频非常生涩。

反转音频文件的最佳做法是什么?

任何帮助深表感谢。

-(void)reverseAudio:(NSURL *)videoURL andVideoAsset:(AVURLAsset *)videoAsset{

AVAssetReader *video2AssetReader = [[AVAssetReader alloc] initWithAsset:videoAsset error:nil];
video2AssetReader.timeRange = CMTimeRangeFromTimeToTime(kCMTimeZero, [videoAsset duration]);


NSArray *audioTracks = [videoAsset tracksWithMediaType:AVMediaTypeAudio];
AVAssetTrack* audioTrack = [audioTracks objectAtIndex:0];

NSDictionary *outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:
                                    [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
                                    [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                                    [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
                                    [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
                                    [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,
                                    nil];

AVAssetReaderTrackOutput *readerAudioTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:outputSettingsDict];
[video2AssetReader addOutput:readerAudioTrackOutput];

[video2AssetReader startReading];

// read in the samples
NSMutableArray *audioSamples = [[NSMutableArray alloc] init];

CMSampleBufferRef audioSample;

while((audioSample = [readerAudioTrackOutput copyNextSampleBuffer])){
    [audioSamples addObject:(__bridge id)audioSample];

    CFRelease(audioSample);
}

videoReverseProcess3TotalFrames = audioSamples.count;
NSLog(@"AUDIO SAMPLES COUNT = %f", videoReverseProcess3TotalFrames);

[video2AssetReader cancelReading];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *videoPath = [documentsDirectory stringByAppendingPathComponent:@"videoReverseAudioFile.m4a"];
NSError *error = nil;
if([[NSFileManager defaultManager] fileExistsAtPath:videoPath]){
    [[NSFileManager defaultManager] removeItemAtPath:videoPath error:&error];
    if(error){
        NSLog(@"VIDEO DELETE FAILED");
    }
    else{
        NSLog(@"VIDEO DELETED");

    }
}

NSURL *audioExportURL = [[NSURL alloc] initFileURLWithPath:videoPath];

AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:audioExportURL fileType:AVFileTypeAppleM4A error:&error];

AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
NSDictionary *audioCompressionSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                          [NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
                                          [NSNumber numberWithFloat:44100.0], AVSampleRateKey,
                                          [NSNumber numberWithInt:2], AVNumberOfChannelsKey,
                                          [NSNumber numberWithInt:128000], AVEncoderBitRateKey,
                                          [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey, nil];

AVAssetWriterInput *writerAudioInput;

writerAudioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioCompressionSettings];
writerAudioInput.expectsMediaDataInRealTime = NO;

if([writer canAddInput:writerAudioInput]){
    [writer addInput:writerAudioInput];
}
else{
    NSLog(@"ERROR ADDING AUDIO");
}

[writer startWriting];

CMTime timeStamp = CMSampleBufferGetPresentationTimeStamp((__bridge CMSampleBufferRef)audioSamples[0]);
[writer startSessionAtSourceTime:timeStamp];

while(audioSamples.count > 0){
    if(writer && writerAudioInput.readyForMoreMediaData){
        CMSampleBufferRef audioBufferRef = (__bridge CMSampleBufferRef)audioSamples[audioSamples.count - 1];

        [writerAudioInput appendSampleBuffer:audioBufferRef];

        [audioSamples removeObjectAtIndex:audioSamples.count - 1];
    }
    else{
        [NSThread sleepForTimeInterval:0.2];
    }
}

if(writer.status != AVAssetWriterStatusCancelled){
    [writerAudioInput markAsFinished];
    [writer finishWritingWithCompletionHandler:^{

    }];
}

}

4

1 回答 1

1

您不是在反转音频,您只是在反转音频片段(缓冲区)的顺序。

所以你有这个输入:S1,S2,S3,S4,你产生以下输出:S4,S3,S2,S1。但是在这个片段中,你仍然有原始的帧顺序。

您还需要反转缓冲区数据。

更新#1

这是如何执行此操作的示例。

- (void)reverseAudio:(AVURLAsset *)videoAsset {

    AVAssetReader *video2AssetReader = [[AVAssetReader alloc] initWithAsset:videoAsset error:nil];
    video2AssetReader.timeRange = CMTimeRangeFromTimeToTime(kCMTimeZero, [videoAsset duration]);


    NSArray *audioTracks = [videoAsset tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack* audioTrack = [audioTracks objectAtIndex:0];

    NSDictionary *outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:
                                        [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
                                        [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                                        [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
                                        [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
                                        [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,
                                        nil];

    AVAssetReaderTrackOutput *readerAudioTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:outputSettingsDict];
    [video2AssetReader addOutput:readerAudioTrackOutput];

    [video2AssetReader startReading];

    // read in the samples
    CMTime timeStamp = kCMTimeInvalid;

    NSMutableArray *audioSamples = [[NSMutableArray alloc] init];
    CMSampleBufferRef audioSample;
    while ((audioSample = [readerAudioTrackOutput copyNextSampleBuffer])) {
        [audioSamples addObject:(__bridge id)[self reverseSampleBuffer:audioSample]];

        if (CMTIME_IS_INVALID(timeStamp)) {
            timeStamp = CMSampleBufferGetPresentationTimeStamp(audioSample);
        }

        CFRelease(audioSample);
    }

    NSLog(@"AUDIO SAMPLES COUNT = %d", (int)audioSamples.count);

    [video2AssetReader cancelReading];

    // rest of the code
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *videoPath = [documentsDirectory stringByAppendingPathComponent:@"videoReverseAudioFile.m4a"];
    NSError *error = nil;
    if ([[NSFileManager defaultManager] fileExistsAtPath:videoPath]) {
        [[NSFileManager defaultManager] removeItemAtPath:videoPath error:&error];
        if (error) {
            NSLog(@"VIDEO DELETE FAILED");
        } else {
            NSLog(@"VIDEO DELETED");
        }
    }

    NSURL *audioExportURL = [[NSURL alloc] initFileURLWithPath:videoPath];

    AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:audioExportURL fileType:AVFileTypeAppleM4A error:&error];

    AudioChannelLayout channelLayout;
    memset(&channelLayout, 0, sizeof(AudioChannelLayout));
    channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
    NSDictionary *audioCompressionSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                              [NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey,
                                              [NSNumber numberWithFloat:44100.0], AVSampleRateKey,
                                              [NSNumber numberWithInt:2], AVNumberOfChannelsKey,
                                              [NSNumber numberWithInt:128000], AVEncoderBitRateKey,
                                              [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey, nil];

    AVAssetWriterInput *writerAudioInput;

    writerAudioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioCompressionSettings];
    writerAudioInput.expectsMediaDataInRealTime = NO;

    if ([writer canAddInput:writerAudioInput]) {
        [writer addInput:writerAudioInput];
    } else {
        NSLog(@"ERROR ADDING AUDIO");
    }

    [writer startWriting];

    [writer startSessionAtSourceTime:timeStamp];

    while (audioSamples.count > 0) {
        if(writer && writerAudioInput.readyForMoreMediaData) {
            CMSampleBufferRef audioBufferRef = (__bridge CMSampleBufferRef)audioSamples[audioSamples.count - 1];

            [writerAudioInput appendSampleBuffer:audioBufferRef];

            [audioSamples removeObjectAtIndex:audioSamples.count - 1];
        } else {
            [NSThread sleepForTimeInterval:0.2];
        }
    }

    if (writer.status != AVAssetWriterStatusCancelled) {
        [writerAudioInput markAsFinished];
        [writer finishWritingWithCompletionHandler:^{

        }];
    }
}

- (CMSampleBufferRef)reverseSampleBuffer:(CMSampleBufferRef)buffer {
    AudioBufferList list;
    CMBlockBufferRef dataBuffer = NULL;
    // TODO check result code
    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(buffer,
                                                            NULL,
                                                            &list,
                                                            sizeof(list),
                                                            NULL,
                                                            NULL,
                                                            kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
                                                            &dataBuffer);

    CMItemCount numberOfSamples = CMSampleBufferGetNumSamples(buffer);
    for (int i = 0; i < list.mNumberBuffers; i++) {
        SInt16 *samples = (SInt16 *)list.mBuffers[i].mData;
        for (int j = 0; j < numberOfSamples / 2; j++) {
            SInt16 t = samples[j];
            samples[j] = samples[numberOfSamples - 1 - j];
            samples[numberOfSamples - 1 - j] = t;
        }
    }



    CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(buffer);
    CMSampleBufferRef result = NULL;
    // TODO check result code
    CMSampleBufferCreate(kCFAllocatorDefault, dataBuffer, true, NULL, NULL, format, 0, 0, NULL, 0, NULL, &result);

    return result;
}
于 2017-06-03T18:00:45.570 回答