首页 文章

我可以使用AVAudioEngine从文件读取,使用音频单元处理并写入文件,比实时更快吗?

提问于
浏览
12

我正在开发一个iOS应用程序,它使用AVAudioEngine进行各种操作,包括将音频录制到文件,使用音频单元对音频应用效果,以及播放应用效果的音频 . 我使用tap也将输出写入文件 . 完成此操作后,它会在播放音频时实时写入文件 .

是否可以设置AVAudioEngine图形,该图形从文件读取,使用音频单元处理声音,并输出到文件,但比实时更快(即,与硬件可以处理的速度一样快)?用于此的用例是输出几分钟的音频并应用效果,我当然不希望等待几分钟来处理它 .

Edit: 这里's the code that I' m用于设置AVAudioEngine的图形,并播放声音文件:

AVAudioEngine* engine = [[AVAudioEngine alloc] init];

AVAudioPlayerNode* player = [[AVAudioPlayerNode alloc] init];
[engine attachNode:player];

self.player = player;
self.engine = engine;

if (!self.distortionEffect) {
    self.distortionEffect = [[AVAudioUnitDistortion alloc] init];
    [self.engine attachNode:self.distortionEffect];
    [self.engine connect:self.player to:self.distortionEffect format:[self.distortionEffect outputFormatForBus:0]];
    AVAudioMixerNode* mixer = [self.engine mainMixerNode];
    [self.engine connect:self.distortionEffect to:mixer format:[mixer outputFormatForBus:0]];
}

[self.distortionEffect loadFactoryPreset:AVAudioUnitDistortionPresetDrumsBitBrush];

NSError* error;
if (![self.engine startAndReturnError:&error]) {
    NSLog(@"error: %@", error);
} else {
    NSURL* fileURL = [[NSBundle mainBundle] URLForResource:@"test2" withExtension:@"mp3"];
    AVAudioFile* file = [[AVAudioFile alloc] initForReading:fileURL error:&error];

    if (error) {
        NSLog(@"error: %@", error);
    } else {
        [self.player scheduleFile:file atTime:nil completionHandler:nil];
        [self.player play];
    }
}

上面的代码在test2.mp3文件中播放声音,实时应用 AVAudioUnitDistortionPresetDrumsBitBrush 失真预设 .

然后我通过在[self.player play]之后添加这些行来修改上面的代码:

[self.engine stop];
        [self renderAudioAndWriteToFile];

我修改了Vladimir提供的renderAudioAndWriteToFile方法,这样它就不会在第一行中分配新的AVAudioEngine,而只使用已经设置的self.engine .

但是,在renderAudioAndWriteToFile中,它正在记录"Can not render audio unit",因为AudioUnitRender返回的状态为 kAudioUnitErr_Uninitialized .

Edit 2 :我应该提一下,我很高兴转换我发布的AVAudioEngine代码使用C apis,如果这会让事情变得更容易 . 但是,我希望代码生成与AVAudioEngine代码相同的输出(包括使用上面显示的工厂预设) .

1 回答

  • 11
    • 配置引擎和播放器节点 .

    • 为您的玩家节点调用 play 方法 .

    • 暂停引擎 .

    • 使用此method从AVAudioOutputNode( audioEngine.outputNode )获取音频设备 .

    • 在循环中使用AudioUnitRender从音频单元渲染,并使用Extended Audio File Services将音频缓冲区列表写入文件 .

    例:

    音频引擎配置

    - (void)configureAudioEngine {
        self.engine = [[AVAudioEngine alloc] init];
        self.playerNode = [[AVAudioPlayerNode alloc] init];
        [self.engine attachNode:self.playerNode];
        AVAudioUnitDistortion *distortionEffect = [[AVAudioUnitDistortion alloc] init];
        [self.engine attachNode:distortionEffect];
        [self.engine connect:self.playerNode to:distortionEffect format:[distortionEffect outputFormatForBus:0]];
        self.mixer = [self.engine mainMixerNode];
        [self.engine connect:distortionEffect to:self.mixer format:[self.mixer outputFormatForBus:0]];
        [distortionEffect loadFactoryPreset:AVAudioUnitDistortionPresetDrumsBitBrush];
        NSError* error;
        if (![self.engine startAndReturnError:&error])
            NSLog(@"Can't start engine: %@", error);
        else
            [self scheduleFileToPlay];
    }
    
    - (void)scheduleFileToPlay {
        NSError* error;
        NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"filename" withExtension:@"m4a"];
        self.file = [[AVAudioFile alloc] initForReading:fileURL error:&error];
        if (self.file)
            [self.playerNode scheduleFile:self.file atTime:nil completionHandler:nil];
        else
            NSLog(@"Can't read file: %@", error);
    }
    

    渲染方法

    - (void)renderAudioAndWriteToFile {
        [self.playerNode play];
        [self.engine pause];
        AVAudioOutputNode *outputNode = self.engine.outputNode;
        AudioStreamBasicDescription const *audioDescription = [outputNode outputFormatForBus:0].streamDescription;
        NSString *path = [self filePath];
        ExtAudioFileRef audioFile = [self createAndSetupExtAudioFileWithASBD:audioDescription andFilePath:path];
        if (!audioFile)
            return;
        AVURLAsset *asset = [AVURLAsset assetWithURL:self.file.url];
        NSTimeInterval duration = CMTimeGetSeconds(asset.duration);
        NSUInteger lengthInFrames = duration * audioDescription->mSampleRate;
        const NSUInteger kBufferLength = 4096;
        AudioBufferList *bufferList = AEAllocateAndInitAudioBufferList(*audioDescription, kBufferLength);
        AudioTimeStamp timeStamp;
        memset (&timeStamp, 0, sizeof(timeStamp));
        timeStamp.mFlags = kAudioTimeStampSampleTimeValid;
        OSStatus status = noErr;
        for (NSUInteger i = kBufferLength; i < lengthInFrames; i += kBufferLength) {
            status = [self renderToBufferList:bufferList writeToFile:audioFile bufferLength:kBufferLength timeStamp:&timeStamp];
            if (status != noErr)
                break;
        }
        if (status == noErr && timeStamp.mSampleTime < lengthInFrames) {
            NSUInteger restBufferLength = (NSUInteger) (lengthInFrames - timeStamp.mSampleTime);
            AudioBufferList *restBufferList = AEAllocateAndInitAudioBufferList(*audioDescription, restBufferLength);
            status = [self renderToBufferList:restBufferList writeToFile:audioFile bufferLength:restBufferLength timeStamp:&timeStamp];
            AEFreeAudioBufferList(restBufferList);
        }
        AEFreeAudioBufferList(bufferList);
        ExtAudioFileDispose(audioFile);
        if (status != noErr)
            NSLog(@"An error has occurred");
        else
            NSLog(@"Finished writing to file at path: %@", path);
    }
    
    - (NSString *)filePath {
        NSArray *documentsFolders =
                NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *fileName = [NSString stringWithFormat:@"%@.m4a", [[NSUUID UUID] UUIDString]];
        NSString *path = [documentsFolders[0] stringByAppendingPathComponent:fileName];
        return path;
    }
    
    - (ExtAudioFileRef)createAndSetupExtAudioFileWithASBD:(AudioStreamBasicDescription const *)audioDescription
                                              andFilePath:(NSString *)path {
        AudioStreamBasicDescription destinationFormat;
        memset(&destinationFormat, 0, sizeof(destinationFormat));
        destinationFormat.mChannelsPerFrame = audioDescription->mChannelsPerFrame;
        destinationFormat.mSampleRate = audioDescription->mSampleRate;
        destinationFormat.mFormatID = kAudioFormatMPEG4AAC;
        ExtAudioFileRef audioFile;
        OSStatus status = ExtAudioFileCreateWithURL(
                (__bridge CFURLRef) [NSURL fileURLWithPath:path],
                kAudioFileM4AType,
                &destinationFormat,
                NULL,
                kAudioFileFlags_EraseFile,
                &audioFile
        );
        if (status != noErr) {
            NSLog(@"Can not create ext audio file");
            return nil;
        }
        UInt32 codecManufacturer = kAppleSoftwareAudioCodecManufacturer;
        status = ExtAudioFileSetProperty(
                audioFile, kExtAudioFileProperty_CodecManufacturer, sizeof(UInt32), &codecManufacturer
        );
        status = ExtAudioFileSetProperty(
                audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), audioDescription
        );
        status = ExtAudioFileWriteAsync(audioFile, 0, NULL);
        if (status != noErr) {
            NSLog(@"Can not setup ext audio file");
            return nil;
        }
        return audioFile;
    }
    
    - (OSStatus)renderToBufferList:(AudioBufferList *)bufferList
                       writeToFile:(ExtAudioFileRef)audioFile
                      bufferLength:(NSUInteger)bufferLength
                         timeStamp:(AudioTimeStamp *)timeStamp {
        [self clearBufferList:bufferList];
        AudioUnit outputUnit = self.engine.outputNode.audioUnit;
        OSStatus status = AudioUnitRender(outputUnit, 0, timeStamp, 0, bufferLength, bufferList);
        if (status != noErr) {
            NSLog(@"Can not render audio unit");
            return status;
        }
        timeStamp->mSampleTime += bufferLength;
        status = ExtAudioFileWrite(audioFile, bufferLength, bufferList);
        if (status != noErr)
            NSLog(@"Can not write audio to file");
        return status;
    }
    
    - (void)clearBufferList:(AudioBufferList *)bufferList {
        for (int bufferIndex = 0; bufferIndex < bufferList->mNumberBuffers; bufferIndex++) {
            memset(bufferList->mBuffers[bufferIndex].mData, 0, bufferList->mBuffers[bufferIndex].mDataByteSize);
        }
    }
    

    我使用了this酷框架中的一些函数:

    AudioBufferList *AEAllocateAndInitAudioBufferList(AudioStreamBasicDescription audioFormat, int frameCount) {
        int numberOfBuffers = audioFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved ? audioFormat.mChannelsPerFrame : 1;
        int channelsPerBuffer = audioFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved ? 1 : audioFormat.mChannelsPerFrame;
        int bytesPerBuffer = audioFormat.mBytesPerFrame * frameCount;
        AudioBufferList *audio = malloc(sizeof(AudioBufferList) + (numberOfBuffers-1)*sizeof(AudioBuffer));
        if ( !audio ) {
            return NULL;
        }
        audio->mNumberBuffers = numberOfBuffers;
        for ( int i=0; i<numberOfBuffers; i++ ) {
            if ( bytesPerBuffer > 0 ) {
                audio->mBuffers[i].mData = calloc(bytesPerBuffer, 1);
                if ( !audio->mBuffers[i].mData ) {
                    for ( int j=0; j<i; j++ ) free(audio->mBuffers[j].mData);
                    free(audio);
                    return NULL;
                }
            } else {
                audio->mBuffers[i].mData = NULL;
            }
            audio->mBuffers[i].mDataByteSize = bytesPerBuffer;
            audio->mBuffers[i].mNumberChannels = channelsPerBuffer;
        }
        return audio;
    }
    
    void AEFreeAudioBufferList(AudioBufferList *bufferList ) {
        for ( int i=0; i<bufferList->mNumberBuffers; i++ ) {
            if ( bufferList->mBuffers[i].mData ) free(bufferList->mBuffers[i].mData);
        }
        free(bufferList);
    }
    

相关问题