在处理输入时,AVAudioEngine
手动渲染无法正常工作。当没有输入节点并且音频来自播放器节点时,它很容易工作。
这是我得到的代码,可以粘贴到测试中:
- (void)testManualRendering {
auto engine = [[AVAudioEngine alloc] init];
auto format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100.0
channels:2];
auto inputBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format
frameCapacity:1024];
auto abl = inputBuffer.mutableAudioBufferList;
XCTAssert(abl != NULL);
XCTAssertEqual(abl->mNumberBuffers, 2);
[engine connect:engine.inputNode to:engine.mainMixerNode format:format];
NSError* error;
[engine enableManualRenderingMode:AVAudioEngineManualRenderingModeOffline
format:format
maximumFrameCount:1024 error:&error];
XCTAssertNil(error);
XCTAssert([format isEqual:engine.manualRenderingFormat]);
NSLog(@"manualRenderingFormat: %@", engine.manualRenderingFormat);
auto success = [engine.inputNode setManualRenderingInputPCMFormat:format
inputBlock:^const AudioBufferList * _Nullable(AVAudioFrameCount inNumberOfFrames) {
XCTAssert(abl->mBuffers[0].mDataByteSize <= inNumberOfFrames * sizeof(float));
XCTAssert(abl->mBuffers[1].mDataByteSize <= inNumberOfFrames * sizeof(float));
return abl;
}];
XCTAssert(success);
[engine startAndReturnError:&error];
XCTAssertNil(error);
auto outputBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format
frameCapacity:1024];
XCTAssertNotNil(outputBuffer);
XCTAssert(engine.isInManualRenderingMode);
auto status = [engine renderOffline:32 toBuffer:outputBuffer error:&error];
if(status == AVAudioEngineManualRenderingStatusInsufficientDataFromInputNode) {
printf("manual rendering failed: AVAudioEngineManualRenderingStatusInsufficientDataFromInputNoden");
}
XCTAssertEqual(status, AVAudioEngineManualRenderingStatusSuccess);
if (error) {
NSLog(@"error: %@", error);
}
XCTAssertNil(error);
}
我得到的是AVAudioEngineManualRenderingStatusInsufficientDataFromInputNode
,但输入缓冲区有很多数据。我错过了什么?查找此示例代码时遇到问题。
我已经考虑了几个下午,经过一番讨论,我想我已经为你的问题找到了解决方案,并回答了为什么这没有像你预期的那样奏效。
AVAudioEngine
的inputNode
默认为AVAudioInputNode
或系统音频接口的输入。当引擎处于手动渲染模式时,输入和输出节点都会断开与音频接口的连接,但许多设置都保持不变。所以hwFormat.sampleRate
必须==format.sampleRate
,否则[engine connect]
调用将失败。
由于音频接口断开,我们无法从输入中获取数据,因此我们可以使用setManualRenderingInput
来确定如何提供数据。我们可以要求[engine renderOffline]
呈现一个帧Capacity,但显然,这不能保证是在我们的inputBlock
内传递的==inNumberOfFrames
值,它显然会根据所选的音频设备及其相应的sampleRate
而变化。为了使renderOffline
调用成功,AudioBuffer.mDataByteSize
必须==inNumberOfFrames * sizeof(float)
,否则renderOffline
调用将抛出不足的数据。由于我们无法在调用renderOffline
之前确保inNumberOfFrames
的值,因此AudioBuffer的分配必须等到inputBlock
。
我肯定有一些东西我错过了。。。但无论如何。。。试试下面的更改,看看这是否适合你。
- (void)testManualRendering {
auto engine = [[AVAudioEngine alloc] init];
auto hwFormat = [engine.inputNode inputFormatForBus:0];
NSLog(@"HW Format: %@", hwFormat);
///Note: SampleRate will have to match the systems audio interface input sample rate or engine connect will throw an error
auto format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:[hwFormat sampleRate] channels:2];
NSLog(@"Input Node: %@", [engine inputNode]);
auto frameCapacity = 1024;
AudioBufferList *abl = (AudioBufferList *)calloc(sizeof(AudioBufferList) + sizeof(AudioBuffer), 1);
XCTAssert(NULL != abl, @"Unable to allocate memory");
abl->mNumberBuffers = [format channelCount];
XCTAssertEqual(abl->mNumberBuffers, [format channelCount]);
[engine connect:engine.inputNode to:engine.mainMixerNode format:format];
NSError* error;
[engine enableManualRenderingMode:AVAudioEngineManualRenderingModeOffline
format:format
maximumFrameCount:frameCapacity error:&error];
XCTAssertNil(error);
XCTAssert([format isEqual:engine.manualRenderingFormat]);
NSLog(@"manualRenderingFormat: %@", engine.manualRenderingFormat);
auto success = [engine.inputNode setManualRenderingInputPCMFormat:format
inputBlock:^const AudioBufferList * _Nullable(AVAudioFrameCount inNumberOfFrames) {
auto byteSize = UInt32((inNumberOfFrames) * sizeof(float));
unsigned i, j;
for(i=0; i<abl->mNumberBuffers; ++i){
abl->mBuffers[i].mData = calloc(sizeof(float), inNumberOfFrames);
XCTAssert(NULL != abl->mBuffers[i].mData, @"Unable to allocate memory.");
abl->mBuffers[i].mDataByteSize = byteSize;
abl->mBuffers[i].mNumberChannels = 1;
XCTAssert( abl->mBuffers[i].mDataByteSize > 0);
XCTAssert( abl->mBuffers[i].mDataByteSize >= inNumberOfFrames * sizeof(float));
float *ptr = (float *)abl->mBuffers[i].mData;
//possibly load in some random data? WHY NOT!
for(j=0; j<inNumberOfFrames; ++j){
*ptr = (rand() / float(RAND_MAX)) * 0.8;
ptr++;
}
}
NSLog(@"Number of input frames: %i, size of frames (bytes): %lu, buffer capacity: %i", inNumberOfFrames, inNumberOfFrames * sizeof(float), abl->mBuffers[0].mDataByteSize);
return abl;
}];
XCTAssert(success);
[engine startAndReturnError:&error];
XCTAssertNil(error);
auto outputBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format
frameCapacity:frameCapacity];
XCTAssertNotNil(outputBuffer);
XCTAssert(engine.isInManualRenderingMode);
auto status = [engine renderOffline:frameCapacity toBuffer:outputBuffer error:&error];
if(status == AVAudioEngineManualRenderingStatusInsufficientDataFromInputNode) {
printf("manual rendering failed: AVAudioEngineManualRenderingStatusInsufficientDataFromInputNoden");
}
XCTAssertEqual(status, AVAudioEngineManualRenderingStatusSuccess);
if (error) {
NSLog(@"error: %@", error);
}
XCTAssertNil(error);
//free buffers
unsigned i;
for(i=0; i<abl->mNumberBuffers; ++i){
free(abl->mBuffers[i].mData);
}
free(abl);
}