反转音频文件Swift / Objective-C

时间:2016-12-24 06:30:35

标签: ios objective-c swift audio avfoundation

有没有办法可以反转并导出.m4a音频文件?我找到了一个反转音轨here的解决方案,但它似乎只是在处理.caf文件格式。如果唯一的方法是使用.caf,有没有办法将.m4a文件首先转换为.caf?

更新another post中,我发现AVAssetReader可用于从音频文件中读取音频样本,但我不知道如何以相反的顺序写回样本。以下代码片段直接来自帖子。任何帮助,将不胜感激。感谢

+ (void) reverseAudioTrack: (AVAsset *)audioAsset outputURL: (NSURL *)outputURL {
NSError *error;

AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:audioAsset error:&error];
if (error) {NSLog(@"%@", error.localizedDescription);}

AVAssetTrack* track = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];

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

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

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

// Get all samples
while((sample = [readerOutput copyNextSampleBuffer])) {
    [samples addObject:(__bridge id)sample];
    CFRelease(sample);
}

// Process samples in reverse
AudioChannelLayout acl;
bzero(&acl, sizeof(acl));
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:outputURL
                                                   fileType:AVFileTypeAppleM4A
                                                      error:&error];
if (error) {NSLog(@"%@", error.localizedDescription);}
NSDictionary *writerOutputSettings = [ NSDictionary dictionaryWithObjectsAndKeys:
                                      [ NSNumber numberWithInt: kAudioFormatAppleLossless ], AVFormatIDKey,
                                      [ NSNumber numberWithInt: 16 ], AVEncoderBitDepthHintKey,
                                      [ NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey,
                                      [ NSNumber numberWithInt: 1 ], AVNumberOfChannelsKey,
                                      [ NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey, nil ];

AVAssetWriterInput *audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:writerOutputSettings];

[writer addInput:audioWriterInput];
[writer startWriting];
[writer startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp((__bridge CMSampleBufferRef)samples[0]) ];

// (1) Would it work if I loop in reverse here?
for (NSInteger i = 0; i < samples.count; i++) {
    CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer((__bridge CMSampleBufferRef)samples[i]);

    CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples((__bridge CMSampleBufferRef)samples[i]);
    AudioBufferList audioBufferList;
    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer((__bridge CMSampleBufferRef)samples[i],
                                                            NULL,
                                                            &audioBufferList,
                                                            sizeof(audioBufferList),
                                                            NULL,
                                                            NULL,
                                                            kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
                                                            &buffer
                                                            );

    for (int bufferCount = 0; bufferCount < audioBufferList.mNumberBuffers; bufferCount++) {
        SInt16* samples = (SInt16 *)audioBufferList.mBuffers[bufferCount].mData;
        for (int i=0; i < numSamplesInBuffer; i++) {
            // amplitude for the sample is samples[i], assuming you have linear pcm to start with

            // (2) What should I be doing to write the samples into an audio file?
        }
    }
    CFRelease(buffer);
}

2 个答案:

答案 0 :(得分:1)

是的,有 方式可以处理,然后导出任何有iOS支持的音频文件。

但是,大多数这些格式(名称为mp3)都是有损和压缩的。您必须首先解压缩数据,应用转换并重新压缩。您将应用于音频信息的大多数转换应该可以在原始PCM级别完成。

结合这两个陈述,你可以在几个过程中完成这个:

  1. 将原始文件转换为符合kAudioFormatLinearPCM的音频文件,例如AIFF
  2. 处理该临时文件(反转其内容)
  3. 将临时文件转换回原始格式
  4. 就像您正在对压缩的jpeg图像应用转换一样,过程中会出现降级。最后的音频最多只会遭受一次压缩周期。

    因此,这种方法的真正数学答案实际上是没有。

    仅供参考,以下是swift 3中的一些入门代码。需要进一步细化才能跳过文件头。

    var outAudioFile:AudioFileID?
    var pcm = AudioStreamBasicDescription(mSampleRate: 44100.0,
                                          mFormatID: kAudioFormatLinearPCM,
                                          mFormatFlags: kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger,
                                          mBytesPerPacket: 2,
                                          mFramesPerPacket: 1,
                                          mBytesPerFrame: 2,
                                          mChannelsPerFrame: 1,
                                          mBitsPerChannel: 16,
                                          mReserved: 0)
    
    var theErr = AudioFileCreateWithURL(destUrl as CFURL!,
                                        kAudioFileAIFFType,
                                        &pcm,
                                        .eraseFile,
                                        &outAudioFile)
    if noErr == theErr, let outAudioFile = outAudioFile {
        var inAudioFile:AudioFileID?
        theErr = AudioFileOpenURL(sourceUrl as! CFURL, .readPermission, 0, &inAudioFile)
    
        if noErr == theErr, let inAudioFile = inAudioFile {
    
            var fileDataSize:UInt64 = 0
            var thePropertySize:UInt32 = UInt32(MemoryLayout<UInt64>.stride)
            theErr = AudioFileGetProperty(inAudioFile,
                                          kAudioFilePropertyAudioDataByteCount,
                                          &thePropertySize,
                                          &fileDataSize)
    
            if( noErr == theErr) {
                let dataSize:Int64 = Int64(fileDataSize)
                let theData = UnsafeMutableRawPointer.allocate(bytes: Int(dataSize),
                                                               alignedTo: MemoryLayout<UInt8>.alignment)
    
                var readPoint:Int64 = Int64(dataSize)
                var writePoint:Int64 = 0
    
                while( readPoint > 0 )
                {
                    var bytesToRead = UInt32(2)
    
                    AudioFileReadBytes( inAudioFile, false, readPoint, &bytesToRead, theData)
                    AudioFileWriteBytes( outAudioFile, false, writePoint, &bytesToRead, theData)
    
                    writePoint += 2
                    readPoint -= 2
                }
    
                theData.deallocate(bytes: Int(dataSize), alignedTo: MemoryLayout<UInt8>.alignment)
    
                AudioFileClose(inAudioFile);
                AudioFileClose(outAudioFile);
            }
        }
    }
    

答案 1 :(得分:0)

我从这个 repo 在 GitHub 上找到了一个解决方案:https://github.com/tomisacat/AudioReverse

下面的函数对我来说非常适合反转 m4a 文件

    func reverse(fromUrl: URL) -> URL? {
    do {
        let inFile: AVAudioFile = try AVAudioFile(forReading: fromUrl)
        let format: AVAudioFormat = inFile.processingFormat
        let frameCount: AVAudioFrameCount = UInt32(inFile.length)
        let outSettings = [AVNumberOfChannelsKey: format.channelCount,
                           AVSampleRateKey: format.sampleRate,
                           AVLinearPCMBitDepthKey: 16,
                           AVFormatIDKey: kAudioFormatMPEG4AAC] as [String : Any]
        let outputPath = NSTemporaryDirectory() + "/" + "reverse.m4a"
        let outputUrl = URL(fileURLWithPath: outputPath)
        let outFile: AVAudioFile = try AVAudioFile(forWriting: outputUrl, settings: outSettings)
        let forwardBuffer: AVAudioPCMBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount)
        let reverseBuffer: AVAudioPCMBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount)
        
        try inFile.read(into: forwardBuffer)
        let frameLength = forwardBuffer.frameLength
        reverseBuffer.frameLength = frameLength
        let audioStride = forwardBuffer.stride
        
        for channelIdx in 0..<forwardBuffer.format.channelCount {
            let forwardChannelData = forwardBuffer.floatChannelData?.advanced(by: Int(channelIdx)).pointee
            let reverseChannelData = reverseBuffer.floatChannelData?.advanced(by: Int(channelIdx)).pointee
            
            var reverseIdx: Int = 0
            for idx in stride(from: frameLength, to: 0, by: -1) {
                memcpy(reverseChannelData?.advanced(by: reverseIdx * audioStride), forwardChannelData?.advanced(by: Int(idx) * audioStride), MemoryLayout<Float>.size)
                reverseIdx += 1
            }
        }
        
        try outFile.write(from: reverseBuffer)
        
        return outputUrl
    } catch let error {
        print(error.localizedDescription)
        
        return nil
    }
}