【问题标题】:iOS reverse audio through AVAssetWriteriOS 通过 AVAssetWriter 反向音频
【发布时间】:2015-03-19 14:46:07
【问题描述】:

我正在尝试使用 AVAsset 和 AVAssetWriter 在 iOS 中反转音频。 以下代码有效,但输出文件比输入文件短。 例如,输入文件的持续时间为 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];
});
}

更新:

如果我直接在第一个 while 循环中编写样本 - 一切正常(即使有 writerInput.readyForMoreMediaData 检查)。在这种情况下,结果文件的持续时间与原始文件完全相同。但是,如果我从反转的NSArray 编写相同的样本 - 结果会更短。

【问题讨论】:

  • 样品都在吗? IE。是按时间压缩或截断的音频(缺少样本)...在这两种情况下,输出都比输入短。
  • 这真的有效吗?我正在尝试相同的代码,但使用视频,似乎时间是内置在 CMSampleBufferRef 中的。所以即使你以相反的顺序附加帧,它仍然可以正常播放。
  • 它适用于 .m4a 音频
  • @sx00 你做了什么来解决这个问题?当我尝试这段代码时,我遇到了同样的问题。当声音反转时,它似乎正在跳过。就像您说的那样,如果我不反转它,那听起来就很好,但是当我反转数组时,它的声音就不对了。 NSArray* reversedSamples = [[samples reverseObjectEnumerator] allObjects];

标签: ios objective-c core-audio avassetwriter avasset


【解决方案1】:

这里描述的方法是在这个链接的一个 Xcode 项目中实现的(多平台 SwiftUI 应用程序):

ReverseAudio Xcode Project

以相反的顺序编写音频样本是不够的。样本数据需要自行反转。

在 Swift 中,我们为 AVAsset 创建了一个扩展。

样本必须作为解压缩样本处理。为此,使用 kAudioFormatLinearPCM 创建音频阅读器设置:

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

使用我们的 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)

创建用于读取音频样本的 audioReader (AVAssetReader) 和 audioReaderOutput (AVAssetReaderTrackOutput)。

我们需要跟踪音频样本:

var audioSamples:[CMSampleBuffer] = []

现在开始阅读示例。

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

保存音频样本缓冲区,我们稍后在创建反向样本时需要它:

audioSamples.append(sampleBuffer)

我们需要一个 AVAssetWriter:

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

文件类型为“wav”,因为反向采样将被写入未压缩的音频格式 Linear PCM,如下所示。

对于assetWriter,我们指定音频压缩设置和“源格式提示”,并且可以从未压缩的样本缓冲区中获取:

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

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

现在我们可以创建 AVAssetWriterInput,将其添加到 writer 并开始编写:

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

assetWriter.add(assetWriterInput)

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

现在以相反的顺序遍历样本,并为每个反向样本本身。

我们有一个用于 CMSampleBuffer 的扩展,称为“reverse”。

使用 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]
            
        if let reversedBuffer = sampleBuffer.reverse(), assetWriterInput.append(reversedBuffer) == true {
            index += 1
        }
        else {
            index = nbrSamples
        }
            
        if index == nbrSamples {
            assetWriterInput.markAsFinished()
            
            finishWriting() // call assetWriter.finishWriting, check assetWriter status, etc.
        }
    }
}

所以最后要解释的是如何在“reverse”方法中反转音频样本?

我们为 CMSampleBuffer 创建了一个扩展,它接受一个样本缓冲区并返回反向的样本缓冲区,作为 CMSampleBuffer 的扩展:

func reverse() -> CMSampleBuffer? 

需要取反的数据需要使用方法:

CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer

CMSampleBuffer 头文件对这种方法的描述如下:

“创建一个包含来自 CMSampleBuffer 的数据的 AudioBufferList,以及一个引用该 AudioBufferList 中数据(并管理其生命周期)的 CMBlockBuffer。”

如下调用它,其中“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
 )

现在您可以通过以下方式访问原始数据:

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

我们需要将数据作为一个名为 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]

现在反转数组 sampleArray:

sampleArray.reverse()

使用反向样本,我们创建一个包含反向样本的新 CMSampleBuffer。

现在我们用 CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer 替换之前获取的 CMBlockBuffer 中的数据:

首先使用反转数组重新分配“样本”:

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
}

最后使用 CMSampleBufferCreate 创建新的样本缓冲区。这个函数需要两个我们可以从原始样本缓冲区中获取的参数,即 formatDescription 和 numberOfSamples:

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

现在使用反转的 blockBuffer 创建新的示例缓冲区:

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

仅此而已!

最后一点,Core Audio 和 AVFoundation 标头提供了许多有用的信息,例如 CoreAudioTypes.h、CMSampleBuffer.h 等等。

【讨论】:

  • 是否有人制作了上述示例的任何示例。请问?
  • 自 Xcode 11.4 以来,之前的代码“samples = UnsafeMutablePointer(mutating: sampleArray)”出现警告消息“'UnsafeMutablePointer'的初始化导致悬空指针”,这可能导致碰撞。请改用“sampleArray.withUnsafeBytes”。
  • 添加了指向所请求示例项目的链接。示例应用程序通过反转反转的音频来测试反转的音频 - 应该会恢复原来的。
【解决方案2】:

使用 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)
            }
        }
    }
}

编码愉快!

【讨论】:

  • 嗨!这一行 -- if let reversedBuffer = audioSample.reverse(timingInfo: [timingInfo]) -- 得到一个错误:'CMSampleBuffer' 类型的值没有成员'reverse'。请指教。谢谢
【解决方案3】:

以样本数打印每个缓冲区的大小(通过“读取” readerOutput while 循环),并在“写入” writerInput for 循环中重复。通过这种方式,您可以查看所有缓冲区大小并查看它们是否相加。

例如,您是否缺少或跳过缓冲区if (writerInput.readyForMoreMediaData) 为假,您“睡觉”,但随后继续执行 reversedSamples 中的下一个 reversedSample(该缓冲区有效地从 writerInput 中删除)

更新(基于 cmets): 我在代码中发现,有两个问题:

  1. 输出设置不正确(输入文件为单声道1通道),但输出设置配置为2通道. 应该是:[NSNumber numberWithInt:1], AVNumberOfChannelsKey. 查看输出和输入文件的信息:

  1. 第二个问题是您正在反转 8192 个音频样本的 643 个缓冲区,而不是反转每个音频样本的索引。为了查看每个缓冲区,我将调试从查看每个样本的大小更改为查看缓冲区的大小,即 8192。所以第 76 行现在是:size_t sampleSize = CMSampleBufferGetNumSamples(sample);

输出如下:

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

这表明您正在颠倒每个缓冲区的 8192 个样本的顺序,但在每个缓冲区中,音频仍然“面向前方”。我们可以在我拍摄的正确反转(逐个样本)与您的缓冲区反转的屏幕截图中看到这一点:

如果您还反转每个 8192 缓冲区的每个样本,我认为您当前的方案可以工作。我个人不建议使用 NSArray 枚举器进行信号处理,但如果您在样本级别操作,它可以工作。

【讨论】:

  • 我用 CMSampleBufferGetSampleSize 打印了所有缓冲区大小,所有缓冲区都相同 - 2,然后我检查了两个循环中的缓冲区数量,我的示例文件也一样 - 643。
  • 如果我直接在第一个 while 循环中编写样本 - 一切正常(即使有 writerInput.readyForMoreMediaData 检查)。在这种情况下,结果文件的持续时间与原始文件完全相同。但是,如果我从反转的NSArray 编写相同的样本 - 结果会更短。
  • hmmm ... cmsamplebuffergetsamplesize 返回 2?这似乎很小。你有一个可运行的项目吗?在github上说?
  • @ruohoruotsi 那么这个问题的解决方案是什么?我正在运行上面的代码,当声音反转时,它的生涩。我多次阅读了您的答案,即使它被标记了,我也不知道 sx00 提供的代码中要更改什么
  • 我没有添加代码来反转每个缓冲区。这样想,音频被分成 8192 个连续的块,每个块说 1,2,3 被反转为 3,2,1 但每个块中的所有单独的 8192 个样本仍然面向前方,这就是它听起来生涩的原因。每个块的样本也必须反转......这只是从后到前读取缓冲区。这就是为什么出于性能原因,我建议不要使用 NSArrays 进行音频处理。看看这里:stackoverflow.com/questions/6593118/…希望这会有所帮助。
【解决方案4】:
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
}
}

功能缺失!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    • 1970-01-01
    • 2019-01-11
    • 1970-01-01
    • 2019-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多