简体   繁体   English

iOS 通过 AVAssetWriter 反向音频

[英]iOS reverse audio through AVAssetWriter

I'm trying to reverse audio in iOS with AVAsset and AVAssetWriter.我正在尝试使用 AVAsset 和 AVAssetWriter 在 iOS 中反转音频。 The following code is working, but the output file is shorter than input.以下代码有效,但输出文件比输入文件短。 For example, input file has 1:59 duration, but output 1:50 with the same audio content.例如,输入文件具有 1:59 的持续时间,但输出具有相同音频内容的 1:50。

- (void)reverse:(AVAsset *)asset
{
AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:asset error:nil];

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

NSMutableDictionary* audioReadSettings = [NSMutableDictionary dictionary];
[audioReadSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM]
                     forKey:AVFormatIDKey];

AVAssetReaderTrackOutput* readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:audioReadSettings];
[reader addOutput:readerOutput];
[reader startReading];

NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
                                [NSNumber numberWithFloat:44100.0], AVSampleRateKey,
                                [NSNumber numberWithInt:2], AVNumberOfChannelsKey,
                                [NSNumber numberWithInt:128000], AVEncoderBitRateKey,
                                [NSData data], AVChannelLayoutKey,
                                nil];

AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio
                                                                 outputSettings:outputSettings];

NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"out.m4a"];

NSURL *exportURL = [NSURL fileURLWithPath:exportPath];
NSError *writerError = nil;
AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:exportURL
                                                  fileType:AVFileTypeAppleM4A
                                                     error:&writerError];
[writerInput setExpectsMediaDataInRealTime:NO];
[writer addInput:writerInput];
[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];

CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];
NSMutableArray *samples = [[NSMutableArray alloc] init];

while (sample != NULL) {

    sample = [readerOutput copyNextSampleBuffer];

    if (sample == NULL)
        continue;

    [samples addObject:(__bridge id)(sample)];
    CFRelease(sample);
}

NSArray* reversedSamples = [[samples reverseObjectEnumerator] allObjects];

for (id reversedSample in reversedSamples) {
    if (writerInput.readyForMoreMediaData)  {
        [writerInput appendSampleBuffer:(__bridge CMSampleBufferRef)(reversedSample)];
    }
    else {
        [NSThread sleepForTimeInterval:0.05];
    }
}

[writerInput markAsFinished];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
    [writer finishWriting];
});
}

UPDATE:更新:

If I write samples directly in first while loop - everything is ok (even with writerInput.readyForMoreMediaData checking).如果我直接在第一个while循环中写入样本 - 一切正常(即使使用writerInput.readyForMoreMediaData检查)。 In this case result file has exactly the same duration as original.在这种情况下,结果文件与原始文件的持续时间完全相同。 But if I write the same samples from reversed NSArray - the result is shorter.但是如果我从反向NSArray写入相同的样本 - 结果会更短。

It is not sufficient to write the audio samples in the reverse order.以相反的顺序写入音频样本是不够的。 The sample data needs to be reversed itself, and its timing information needs to be properly set.样本数据需要自己反转,其时序信息需要正确设置。

In Swift, we create an extension to AVAsset.在 Swift 中,我们为 AVAsset 创建了一个扩展。

The samples must be processed as decompressed samples.样本必须作为解压缩样本进行处理。 To that end create audio reader settings with kAudioFormatLinearPCM:为此,使用 kAudioFormatLinearPCM 创建音频阅读器设置:

let kAudioReaderSettings = [
    AVFormatIDKey: Int(kAudioFormatLinearPCM) as AnyObject,
    AVLinearPCMBitDepthKey: 16 as AnyObject,
    AVLinearPCMIsBigEndianKey: false as AnyObject,
    AVLinearPCMIsFloatKey: false as AnyObject,
    AVLinearPCMIsNonInterleaved: false as AnyObject]

Use our AVAsset extension method audioReader:使用我们的 AVAsset 扩展方法 audioReader:

func audioReader(outputSettings: [String : Any]?) -> (audioTrack:AVAssetTrack?, audioReader:AVAssetReader?, audioReaderOutput:AVAssetReaderTrackOutput?) {
    
    if let audioTrack = self.tracks(withMediaType: .audio).first {
        if let audioReader = try? AVAssetReader(asset: self)  {
            let audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: outputSettings)
            return (audioTrack, audioReader, audioReaderOutput)
        }
    }
    
    return (nil, nil, nil)
}

let (_, audioReader, audioReaderOutput) = self.audioReader(outputSettings: kAudioReaderSettings)

to create an audioReader (AVAssetReader) and audioReaderOutput (AVAssetReaderTrackOutput) for reading the audio samples.创建用于读取音频样本的 audioReader (AVAssetReader) 和 audioReaderOutput (AVAssetReaderTrackOutput)。

We need to keep track of the audio sample and the new timing infomation:我们需要跟踪音频样本和新的时间信息:

var audioSamples:[CMSampleBuffer] = []
var timingInfos:[CMSampleTimingInfo] = []

Now start reading samples.现在开始阅读样本。 And for each audio sample obtain its timing information to produce new timing information that will be relative to the end of the audio track (because we will be writing it back in reverse order).对于每个音频样本,获取其计时信息以生成新的计时信息,这些信息将与音轨的末尾相关(因为我们将以相反的顺序将其写回)。

In other words we will adjust the presentation times of the samples.换句话说,我们将调整样本的呈现时间。

if audioReader.startReading() {
    while audioReader.status == .reading {
        if let sampleBuffer = audioReaderOutput.copyNextSampleBuffer(){ 
           // process sample                                       
        }
    }
}

So to “process sample” we use CMSampleBufferGetSampleTimingInfoArray to get the timingInfo (CMSampleTimingInfo):所以为了“处理样本”,我们使用 CMSampleBufferGetSampleTimingInfoArray 来获取timingInfo(CMSampleTimingInfo):

var timingInfo = CMSampleTimingInfo()

CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, entryCount: 0, arrayToFill: &timingInfo, entriesNeededOut: &timingInfoCount)

Get the presentation time and duration:获取演示时间和持续时间:

let presentationTime = timingInfo.presentationTimeStamp
let duration = CMSampleBufferGetDuration(sampleBuffer)

Calculate the end time for the sample:计算样本的结束时间:

let endTime = CMTimeAdd(presentationTime, duration)

And now calculate the new presentation time relative to the end of the track:现在计算相对于曲目结束的新呈现时间:

let newPresentationTime = CMTimeSubtract(self.duration, endTime)

And use it to set the timingInfo:并使用它来设置timingInfo:

timingInfo.presentationTimeStamp = newPresentationTime

Finally save the audio sample buffer and its timing info, we need it later when we create the reversed sample:最后保存音频样本缓冲区及其时序信息,我们稍后在创建反向样本时需要它:

timingInfos.append(timingInfo)
audioSamples.append(sampleBuffer)

We need an AVAssetWriter:我们需要一个 AVAssetWriter:

guard let assetWriter = try? AVAssetWriter(outputURL: destinationURL, fileType: AVFileType.wav) else {
    // error handling
    return
}

The file type is 'wav' because the reversed samples will be written as uncompressed audio format Linear PCM, as follows.文件类型为“wav”,因为反向采样将被写入为未压缩的音频格式 Linear PCM,如下所示。

For the assetWriter we specify audio compression settings, and a 'source format hint' and can acquire this from an uncompressed sample buffer:对于 assetWriter,我们指定音频压缩设置和“源格式提示”,并且可以从未压缩的样本缓冲区中获取它:

let sampleBuffer = audioSamples[0]
let sourceFormat = CMSampleBufferGetFormatDescription(sampleBuffer)

let audioCompressionSettings = [AVFormatIDKey: kAudioFormatLinearPCM] as [String : Any]

Now we can create the AVAssetWriterInput, add it to the writer and start writing:现在我们可以创建 AVAssetWriterInput,将其添加到 writer 并开始编写:

let assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings:audioCompressionSettings, sourceFormatHint: sourceFormat)

assetWriter.add(assetWriterInput)

assetWriter.startWriting()
assetWriter.startSession(atSourceTime: CMTime.zero)

Now iterate through the samples, in reverse order, and for each reverse the samples themselves.现在以相反的顺序遍历样本,并为每个反转样本本身。

We have an extension for CMSampleBuffer that does just that, called 'reverse'.我们有一个 CMSampleBuffer 的扩展,它就是这样做的,称为“反向”。

Using requestMediaDataWhenReady we do this as follows:使用 requestMediaDataWhenReady 我们按如下方式执行此操作:

let nbrSamples = audioSamples.count
var index = 0

let serialQueue: DispatchQueue = DispatchQueue(label: "com.limit-point.reverse-audio-queue")
    
assetWriterInput.requestMediaDataWhenReady(on: serialQueue) {
        
    while assetWriterInput.isReadyForMoreMediaData, index < nbrSamples {
        let sampleBuffer = audioSamples[nbrSamples - 1 - index]
            
        let timingInfo = timingInfos[index]
            
        if let reversedBuffer = sampleBuffer.reverse(timingInfo: [timingInfo]), assetWriterInput.append(reversedBuffer) == true {
            index += 1
        }
        else {
            index = nbrSamples
        }
            
        if index == nbrSamples {
            assetWriterInput.markAsFinished()
            
            finishWriting() // call assetWriter.finishWriting, check assetWriter status, etc.
        }
    }
}

So the last thing to explain is how do you reverse the audio sample in the 'reverse' method?所以最后要解释的是如何在“反向”方法中反转音频样本?

We create an extension to CMSampleBuffer that takes a sample buffer and returns the properly timed reversed sample buffer, as an extension on CMSampleBuffer:我们为 CMSampleBuffer 创建了一个扩展,它接受一个样本缓冲区并返回正确定时的反向样本缓冲区,作为 CMSampleBuffer 的扩展:

func reverse(timingInfo:[CMSampleTimingInfo]) -> CMSampleBuffer? 

The data that has to be reversed needs to be obtained using the method:需要反转的数据需要使用以下方法获取:

CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer

The CMSampleBuffer header files descibes this method as follows: CMSampleBuffer 头文件对这种方法的描述如下:

“Creates an AudioBufferList containing the data from the CMSampleBuffer, and a CMBlockBuffer which references (and manages the lifetime of) the data in that AudioBufferList.” “创建一个包含来自 CMSampleBuffer 的数据的 AudioBufferList,以及一个引用(并管理其生命周期)该 AudioBufferList 中数据的 CMBlockBuffer。”

Call it as follows, where 'self' refers to the CMSampleBuffer we are reversing since this is an extension:如下调用它,其中“self”指的是我们正在反转的 CMSampleBuffer,因为这是一个扩展:

var blockBuffer: CMBlockBuffer? = nil
let audioBufferList: UnsafeMutableAudioBufferListPointer = AudioBufferList.allocate(maximumBuffers: 1)

CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
    self,
    bufferListSizeNeededOut: nil,
    bufferListOut: audioBufferList.unsafeMutablePointer,
    bufferListSize: AudioBufferList.sizeInBytes(maximumBuffers: 1),
    blockBufferAllocator: nil,
    blockBufferMemoryAllocator: nil,
    flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
    blockBufferOut: &blockBuffer
 )

Now you can access the raw data as:现在您可以通过以下方式访问原始数据:

let data: UnsafeMutableRawPointer = audioBufferList.unsafePointer.pointee.mBuffers.mData

Reversing data we need to access the data as an array of 'samples' called sampleArray, and is done as follows in Swift:反转数据我们需要访问数据作为一个名为 sampleArray 的“样本”数组,并在 Swift 中按如下方式完成:

let samples = data.assumingMemoryBound(to: Int16.self)
        
let sizeofInt16 = MemoryLayout<Int16>.size
let dataSize = audioBufferList.unsafePointer.pointee.mBuffers.mDataByteSize  

let dataCount = Int(dataSize) / sizeofInt16
        
var sampleArray = Array(UnsafeBufferPointer(start: samples, count: dataCount)) as [Int16]

Now reverse the array sampleArray:现在反转数组 sampleArray:

sampleArray.reverse()

Using the reversed samples we need to create a new CMSampleBuffer that contains the reversed samples and the new timing info which we generated previously while we read the audio samples from the source file.使用反向样本,我们需要创建一个新的 CMSampleBuffer,其中包含反向样本和我们之前在从源文件读取音频样本时生成的新计时信息。

Now we replace the data in the CMBlockBuffer we previously obtained with CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer:现在我们用 CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer 替换我们之前获得的 CMBlockBuffer 中的数据:

First reassign 'samples' using the reversed array:首先使用反向数组重新分配“样本”:

var status:OSStatus = noErr
        
sampleArray.withUnsafeBytes { sampleArrayPtr in
    if let baseAddress = sampleArrayPtr.baseAddress {
        let bufferPointer: UnsafePointer<Int16> = baseAddress.assumingMemoryBound(to: Int16.self)
        let rawPtr = UnsafeRawPointer(bufferPointer)
                
        status = CMBlockBufferReplaceDataBytes(with: rawPtr, blockBuffer: blockBuffer!, offsetIntoDestination: 0, dataLength: Int(dataSize))
    } 
}

if status != noErr {
    return nil
}

Finally create the new sample buffer using CMSampleBufferCreate.最后使用 CMSampleBufferCreate 创建新的样本缓冲区。 This function needs two arguments we can get from the original sample buffer, namely the formatDescription and numberOfSamples:该函数需要我们可以从原始样本缓冲区中获取的两个参数,即 formatDescription 和 numberOfSamples:

let formatDescription = CMSampleBufferGetFormatDescription(self)   
let numberOfSamples = CMSampleBufferGetNumSamples(self)
        
var newBuffer:CMSampleBuffer?
        

Now create the new sample buffer with the reversed blockBuffer and most notably the new timing information that was passed as an argument to the function 'reverse' that we are defining:现在使用反向块缓冲区创建新的样本缓冲区,最值得注意的是作为参数传递给我们正在定义的函数“reverse”的新计时信息:

guard CMSampleBufferCreate(allocator: kCFAllocatorDefault, dataBuffer: blockBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: formatDescription, sampleCount: numberOfSamples, sampleTimingEntryCount: timingInfo.count, sampleTimingArray: timingInfo, sampleSizeEntryCount: 0, sampleSizeArray: nil, sampleBufferOut: &newBuffer) == noErr else {
    return self
}
        
return newBuffer

And that's all there is to it!这就是全部!

As a final note the Core Audio and AVFoundation headers provide a lot of useful information, such as CoreAudioTypes.h, CMSampleBuffer.h, and many more.最后要注意的是,Core Audio 和 AVFoundation 标头提供了许多有用的信息,例如 CoreAudioTypes.h、CMSampleBuffer.h 等等。

Complete example for reverse video and audio using Swift 5 into the same asset output, audio processed using above recommendations:使用 Swift 5 将视频和音频反向转换为相同资产输出的完整示例,使用上述建议处理音频:

 private func reverseVideo(inURL: URL, outURL: URL, queue: DispatchQueue, _ completionBlock: ((Bool)->Void)?) {
    Log.info("Start reverse video!")
    let asset = AVAsset.init(url: inURL)
    guard
        let reader = try? AVAssetReader.init(asset: asset),
        let videoTrack = asset.tracks(withMediaType: .video).first,
        let audioTrack = asset.tracks(withMediaType: .audio).first

        else {
            assert(false)
            completionBlock?(false)
            return
    }

    let width = videoTrack.naturalSize.width
    let height = videoTrack.naturalSize.height

    // Video reader
    let readerVideoSettings: [String : Any] = [ String(kCVPixelBufferPixelFormatTypeKey) : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,]
    let readerVideoOutput = AVAssetReaderTrackOutput.init(track: videoTrack, outputSettings: readerVideoSettings)
    reader.add(readerVideoOutput)

    // Audio reader
    let readerAudioSettings: [String : Any] = [
        AVFormatIDKey: kAudioFormatLinearPCM,
        AVLinearPCMBitDepthKey: 16 ,
        AVLinearPCMIsBigEndianKey: false ,
        AVLinearPCMIsFloatKey: false,]
    let readerAudioOutput = AVAssetReaderTrackOutput.init(track: audioTrack, outputSettings: readerAudioSettings)
    reader.add(readerAudioOutput)

    //Start reading content
    reader.startReading()

    //Reading video samples
    var videoBuffers = [CMSampleBuffer]()
    while let nextBuffer = readerVideoOutput.copyNextSampleBuffer() {
        videoBuffers.append(nextBuffer)
    }

    //Reading audio samples
    var audioBuffers = [CMSampleBuffer]()
    var timingInfos = [CMSampleTimingInfo]()
    while let nextBuffer = readerAudioOutput.copyNextSampleBuffer() {

        var timingInfo = CMSampleTimingInfo()
        var timingInfoCount = CMItemCount()
        CMSampleBufferGetSampleTimingInfoArray(nextBuffer, entryCount: 0, arrayToFill: &timingInfo, entriesNeededOut: &timingInfoCount)

        let duration = CMSampleBufferGetDuration(nextBuffer)
        let endTime = CMTimeAdd(timingInfo.presentationTimeStamp, duration)
        let newPresentationTime = CMTimeSubtract(duration, endTime)

        timingInfo.presentationTimeStamp = newPresentationTime

        timingInfos.append(timingInfo)
        audioBuffers.append(nextBuffer)
    }

    //Stop reading
    let status = reader.status
    reader.cancelReading()
    guard status == .completed, let firstVideoBuffer = videoBuffers.first, let firstAudioBuffer = audioBuffers.first else {
        assert(false)
        completionBlock?(false)
        return
    }

    //Start video time
    let sessionStartTime = CMSampleBufferGetPresentationTimeStamp(firstVideoBuffer)

    //Writer for video
    let writerVideoSettings: [String:Any] = [
        AVVideoCodecKey : AVVideoCodecType.h264,
        AVVideoWidthKey : width,
        AVVideoHeightKey: height,
    ]
    let writerVideoInput: AVAssetWriterInput
    if let formatDescription = videoTrack.formatDescriptions.last {
        writerVideoInput = AVAssetWriterInput.init(mediaType: .video, outputSettings: writerVideoSettings, sourceFormatHint: (formatDescription as! CMFormatDescription))
    } else {
        writerVideoInput = AVAssetWriterInput.init(mediaType: .video, outputSettings: writerVideoSettings)
    }
    writerVideoInput.transform = videoTrack.preferredTransform
    writerVideoInput.expectsMediaDataInRealTime = false

    //Writer for audio
    let writerAudioSettings: [String:Any] = [
        AVFormatIDKey : kAudioFormatMPEG4AAC,
        AVSampleRateKey : 44100,
        AVNumberOfChannelsKey: 2,
        AVEncoderBitRateKey:128000,
        AVChannelLayoutKey: NSData(),
    ]
    let sourceFormat = CMSampleBufferGetFormatDescription(firstAudioBuffer)
    let writerAudioInput: AVAssetWriterInput = AVAssetWriterInput.init(mediaType: .audio, outputSettings: writerAudioSettings, sourceFormatHint: sourceFormat)
    writerAudioInput.expectsMediaDataInRealTime = true

    guard
        let writer = try? AVAssetWriter.init(url: outURL, fileType: .mp4),
        writer.canAdd(writerVideoInput),
        writer.canAdd(writerAudioInput)
        else {
            assert(false)
            completionBlock?(false)
            return
    }

    let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor.init(assetWriterInput: writerVideoInput, sourcePixelBufferAttributes: nil)
    let group = DispatchGroup.init()

    group.enter()
    writer.add(writerVideoInput)
    writer.add(writerAudioInput)
    writer.startWriting()
    writer.startSession(atSourceTime: sessionStartTime)

    var videoFinished = false
    var audioFinished = false

    //Write video samples in reverse order
    var currentSample = 0
    writerVideoInput.requestMediaDataWhenReady(on: queue) {
        for i in currentSample..<videoBuffers.count {
            currentSample = i
            if !writerVideoInput.isReadyForMoreMediaData {
                return
            }
            let presentationTime = CMSampleBufferGetPresentationTimeStamp(videoBuffers[i])
            guard let imageBuffer = CMSampleBufferGetImageBuffer(videoBuffers[videoBuffers.count - i - 1]) else {
                Log.info("VideoWriter reverseVideo: warning, could not get imageBuffer from SampleBuffer...")
                continue
            }
            if !pixelBufferAdaptor.append(imageBuffer, withPresentationTime: presentationTime) {
                Log.info("VideoWriter reverseVideo: warning, could not append imageBuffer...")
            }
        }

        // finish write video samples
        writerVideoInput.markAsFinished()
        Log.info("Video writing finished!")
        videoFinished = true
        if(audioFinished){
            group.leave()
        }
    }
    //Write audio samples in reverse order
    let totalAudioSamples = audioBuffers.count
    writerAudioInput.requestMediaDataWhenReady(on: queue) {
        for i in 0..<totalAudioSamples-1 {
            if !writerAudioInput.isReadyForMoreMediaData {
                return
            }
            let audioSample = audioBuffers[totalAudioSamples-1-i]
            let timingInfo = timingInfos[i]
            // reverse samples data using timing info
            if let reversedBuffer = audioSample.reverse(timingInfo: [timingInfo]) {
                // append data
                if writerAudioInput.append(reversedBuffer) == false {
                    break
                }
            }
        }

        // finish
        writerAudioInput.markAsFinished()
        Log.info("Audio writing finished!")
        audioFinished = true
        if(videoFinished){
            group.leave()
        }
    }

    group.notify(queue: queue) {
        writer.finishWriting {
            if writer.status != .completed {
                Log.info("VideoWriter reverse video: error - \(String(describing: writer.error))")
                completionBlock?(false)
            } else {
                Log.info("Ended reverse video!")
                completionBlock?(true)
            }
        }
    }
}

Happy coding!快乐编码!

Print out the size of each buffer in number of samples (through the "reading" readerOuput while loop), and repeat in the "writing" writerInput for-loop.以样本数打印出每个缓冲区的大小(通过“读取”readerOuput while 循环),并在“写入” writerInput for 循环中重复。 This way you can see all the buffer sizes and see if they add up.通过这种方式,您可以查看所有缓冲区大小并查看它们是否相加。

For example, are you missing or skipping a buffer if (writerInput.readyForMoreMediaData) is false, you "sleep", but then proceed to the next reversedSample in reversedSamples (that buffer effectively gets dropped from the writerInput)例如, if (writerInput.readyForMoreMediaData)为假,您是否丢失或跳过缓冲区,您“睡眠”,但随后继续处理 reversedSamples 中的下一个 reversedSample(该缓冲区实际上已从 writerInput 中删除)

UPDATE (based on comments): I found in the code, there are two problems: UPDATE (基于评论):我在代码中发现,有两个问题:

  1. The output settings is incorrect (the input file is mono ( 1 channel), but the output settings is configured to be 2 channels. It should be: [NSNumber numberWithInt:1], AVNumberOfChannelsKey . Look at the info on output and input files:输出设置不正确(输入文件是单声道1声道),但输出设置被配置为2声道。应该是: [NSNumber numberWithInt:1], AVNumberOfChannelsKey 。查看输出和输入文件的信息:

在此处输入图片说明在此处输入图片说明

  1. The second problem is that you are reversing 643 buffers of 8192 audio samples, instead of reversing the index of each audio sample.第二个问题是您正在反转 8192 个音频样本的 643 个缓冲区,而不是反转每个音频样本的索引。 To see each buffer, I changed your debugging from looking at the size of each sample to looking at the size of the buffer, which is 8192. So line 76 is now: size_t sampleSize = CMSampleBufferGetNumSamples(sample);为了查看每个缓冲区,我将您的调试从查看每个样本的大小更改为查看缓冲区的大小,即size_t sampleSize = CMSampleBufferGetNumSamples(sample);所以第 76 行现在是: size_t sampleSize = CMSampleBufferGetNumSamples(sample);

The output looks like:输出看起来像:

2015-03-19 22:26:28.171 audioReverse[25012:4901250] Reading [0]: 8192
2015-03-19 22:26:28.172 audioReverse[25012:4901250] Reading [1]: 8192
...
2015-03-19 22:26:28.651 audioReverse[25012:4901250] Reading [640]: 8192
2015-03-19 22:26:28.651 audioReverse[25012:4901250] Reading [641]: 8192
2015-03-19 22:26:28.651 audioReverse[25012:4901250] Reading [642]: 5056


2015-03-19 22:26:28.651 audioReverse[25012:4901250] Writing [0]: 5056
2015-03-19 22:26:28.652 audioReverse[25012:4901250] Writing [1]: 8192
...
2015-03-19 22:26:29.134 audioReverse[25012:4901250] Writing [640]: 8192
2015-03-19 22:26:29.135 audioReverse[25012:4901250] Writing [641]: 8192
2015-03-19 22:26:29.135 audioReverse[25012:4901250] Writing [642]: 8192

This shows that you're reversing the order of each buffer of 8192 samples, but in each buffer the audio is still "facing forward".这表明您正在颠倒 8192 个样本的每个缓冲区的顺序,但在每个缓冲区中,音频仍然“面向前方”。 We can see this in this screen shot I took of a correctly reversed (sample-by-sample) versus your buffer reversal:我们可以在这个屏幕截图中看到这一点,我正确地反转(逐个样本)与缓冲区反转:

在此处输入图片说明

I think your current scheme can work if you also reverse each sample each 8192 buffer.我认为如果您还反转每个 8192 缓冲区的每个样本,您当前的方案就可以工作。 I personally would not recommend using NSArray enumerators for signal-processing, but it can work if you operate at the sample-level.我个人不建议使用 NSArray 枚举器进行信号处理,但如果您在样本级别操作,它可以工作。

extension CMSampleBuffer {

func reverse(timingInfo:[CMSampleTimingInfo]) -> CMSampleBuffer? {
    var blockBuffer: CMBlockBuffer? = nil
    let audioBufferList: UnsafeMutableAudioBufferListPointer = AudioBufferList.allocate(maximumBuffers: 1)

    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
        self,
        bufferListSizeNeededOut: nil,
        bufferListOut: audioBufferList.unsafeMutablePointer,
        bufferListSize: AudioBufferList.sizeInBytes(maximumBuffers: 1),
        blockBufferAllocator: nil,
        blockBufferMemoryAllocator: nil,
        flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
        blockBufferOut: &blockBuffer
     )
    
    if let data = audioBufferList.unsafePointer.pointee.mBuffers.mData {
    
        let samples = data.assumingMemoryBound(to: Int16.self)

        let sizeofInt16 = MemoryLayout<Int16>.size
        let dataSize = audioBufferList.unsafePointer.pointee.mBuffers.mDataByteSize

        let dataCount = Int(dataSize) / sizeofInt16

        var sampleArray = Array(UnsafeBufferPointer(start: samples, count: dataCount)) as [Int16]
        
        sampleArray.reverse()
        
        var status:OSStatus = noErr
                
        sampleArray.withUnsafeBytes { sampleArrayPtr in
            if let baseAddress = sampleArrayPtr.baseAddress {
                let bufferPointer: UnsafePointer<Int16> = baseAddress.assumingMemoryBound(to: Int16.self)
                let rawPtr = UnsafeRawPointer(bufferPointer)
                        
                status = CMBlockBufferReplaceDataBytes(with: rawPtr, blockBuffer: blockBuffer!, offsetIntoDestination: 0, dataLength: Int(dataSize))
            }
        }

        if status != noErr {
            return nil
        }
        
        let formatDescription = CMSampleBufferGetFormatDescription(self)
        let numberOfSamples = CMSampleBufferGetNumSamples(self)

        var newBuffer:CMSampleBuffer?
        
        guard CMSampleBufferCreate(allocator: kCFAllocatorDefault, dataBuffer: blockBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: formatDescription, sampleCount: numberOfSamples, sampleTimingEntryCount: timingInfo.count, sampleTimingArray: timingInfo, sampleSizeEntryCount: 0, sampleSizeArray: nil, sampleBufferOut: &newBuffer) == noErr else {
            return self
        }

        return newBuffer
    }
    return nil
}
}

Missed function!错过了功能!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM