我可以使用 AVAudioEngine 从文件中读取,使用音频单元处理并写入文件,比实时更快吗?
Posted
技术标签:
【中文标题】我可以使用 AVAudioEngine 从文件中读取,使用音频单元处理并写入文件,比实时更快吗?【英文标题】:Can I use AVAudioEngine to read from a file, process with an audio unit and write to a file, faster than real-time? 【发布时间】:2015-08-21 03:26:59 【问题描述】:我正在开发一个使用 AVAudioEngine 进行各种操作的 ios 应用程序,包括将音频录制到文件中,使用音频单元对音频应用效果,以及播放应用了效果的音频。我使用水龙头也将输出写入文件。完成此操作后,它会在播放音频时实时写入文件。
是否可以设置一个 AVAudioEngine 图形来读取文件,使用音频单元处理声音,然后输出到文件,但速度比实时快(即,硬件可以处理它的速度) ?这个用例是输出几分钟的音频并应用效果,我当然不想等待几分钟来处理它。
编辑:这是我用来设置 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 中,它正在记录“无法渲染音频单元”,因为 AudioUnitRender 返回的状态为 kAudioUnitErr_Uninitialized
。
编辑 2:我应该提一下,我非常乐意将我发布的 AVAudioEngine 代码转换为使用 C api,如果这会使事情变得更容易的话。但是,我希望代码产生与 AVAudioEngine 代码相同的输出(包括使用上面显示的工厂预设)。
【问题讨论】:
【参考方案1】:对于现在正在寻找这个问题的答案的任何人,Apple 似乎为此添加了离线处理功能:see the docs。
【讨论】:
【参考方案2】:-
配置您的引擎和播放器节点。
为您的播放器节点调用
play
方法。
暂停引擎。
从 AVAudioOutputNode (audioEngine.outputNode
) 获取音频单元
用这个method。
在循环中使用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);
【讨论】:
感谢弗拉基米尔的建议。不幸的是,此代码正在输出消息“无法渲染音频单元”,因为 AudioUnitRender 返回的状态是 kAudioUnitErr_Uninitialized。我将在我的问题中添加一些代码,以显示在执行您提供的代码之前如何设置 AVAudioEngine 的图表。 这段代码只是向你展示了我的想法。不幸的是,我无法使用您的配置对其进行测试。我在我的项目中使用了这段代码的很大一部分,正是为了满足您的要求。但我使用的是 Core Audio 和 AUGraph,而不是 AVAudioEngine。如果您正确配置引擎,我相信它会起作用。 我尝试了您的代码并解决了一个问题。看看github.com/VladimirKravchenko/AVAudioEngineOfflineRender 我下载、构建并运行了演示。生成的文件是 40 秒的静音。我会试着找出原因,但想给你一个提示。 感谢您的评论,我将尝试重现并解决此问题。您是否使用我的示例项目github.com/VladimirKravchenko/AVAudioEngineOfflineRender 没有任何更改?以上是关于我可以使用 AVAudioEngine 从文件中读取,使用音频单元处理并写入文件,比实时更快吗?的主要内容,如果未能解决你的问题,请参考以下文章
在 AVAudioEngine 中从网络流式传输数据,这可能吗?
SWIFT - 是不是可以从 AVAudioEngine 或 AudioPlayerNode 保存音频?如果是,如何?