使用 AUHAL 音频单元将字节写入音频文件
Posted
技术标签:
【中文标题】使用 AUHAL 音频单元将字节写入音频文件【英文标题】:Writing bytes to audio file using AUHAL audio unit 【发布时间】:2016-01-11 05:38:24 【问题描述】:我正在尝试根据从我的 macbook 的默认输入设备(内置麦克风)获得的声音输入创建一个 wav 文件。但是,当作为原始数据导入 Audacity 时,生成的文件完全是垃圾。
首先我初始化音频文件引用,以便稍后在音频单元输入回调中写入它。
// struct contains audiofileID as member
MyAUGraphplayer player = 0;
player.startingByte = 0;
// describe a PCM format for audio file
AudiostreamBasicDescription format = 0 ;
format.mBytesPerFrame = 2;
format.mBytesPerPacket = 2;
format.mChannelsPerFrame = 1;
format.mBitsPerChannel = 16;
format.mFramesPerPacket = 1;
format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat;
format.mFormatID = kAudioFormatLinearPCM;
CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("./test.wav"), kCFURLPOSIXPathStyle, false);
//CFShow (myFileURL);
CheckError(AudioFileCreateWithURL(myFileURL,
kAudioFileWAVEType,
&format,
kAudioFileFlags_EraseFile,
&player.recordFile), "AudioFileCreateWithURL failed");
这里我分配了一些缓冲区来保存来自 AUHAL 单元的音频数据。
UInt32 bufferSizeFrames = 0;
propertySize = sizeof(UInt32);
CheckError (AudioUnitGetProperty(player->inputUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&bufferSizeFrames,
&propertySize), "Couldn't get buffer frame size from input unit");
UInt32 bufferSizeBytes = bufferSizeFrames * sizeof(Float32);
printf("buffer num of frames %i", bufferSizeFrames);
if (player->streamFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved)
int offset = offsetof(AudioBufferList, mBuffers[0]);
int sizeOfAB = sizeof(AudioBuffer);
int chNum = player->streamFormat.mChannelsPerFrame;
int inputBufferSize = offset + sizeOfAB * chNum;
//malloc buffer lists
player->inputBuffer = (AudioBufferList *)malloc(inputBufferSize);
player->inputBuffer->mNumberBuffers = chNum;
for (UInt32 i = 0; i < chNum ; i++)
player->inputBuffer->mBuffers[i].mNumberChannels = 1;
player->inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes;
player->inputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes);
为了检查数据是否实际合理,我渲染了音频单元,然后在每个回调中记录每组帧的前 4 个字节 (4096)。原因是检查这些值是否与进入麦克风的值一致。当我对着麦克风说话时,我注意到这个内存位置中的注销值对应于输入。因此,这方面的事情似乎正在发挥作用:
// render into our buffer
OSStatus inputProcErr = noErr;
inputProcErr = AudioUnitRender(player->inputUnit,
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
player->inputBuffer);
// copy from our buffer to ring buffer
Float32 someDataL = *(Float32*)(player->inputBuffer->mBuffers[0].mData);
printf("L2 input: % 1.7f \n",someDataL);
最后,在输入回调中,我将音频字节写入文件。
UInt32 numOfBytes = 4096*player->streamFormat.mBytesPerFrame;
AudioFileWriteBytes(player->recordFile,
FALSE,
player->startingByte,
&numOfBytes,
&ioData[0].mBuffers[0].mData);
player->startingByte += numOfBytes;
所以我还没有弄清楚为什么数据出来时听起来有问题、失真或根本不存在。一件事是生成的音频文件大约与我实际录制的时间一样长。 (点击返回停止音频单元并关闭音频文件)。
我不确定接下来要看什么。有没有人尝试从 AUHAL 回调写入音频文件并得到类似的结果?
【问题讨论】:
AudioFileWriteBytes 是异步的吗? Apple 的文档说“在大多数情况下,您应该使用 AudioFileWritePackets 而不是这个函数。”developer.apple.com/library/ios/documentation/MusicAudio/… 那么你能听到你对着麦克风说的话吗?还是音频完全是垃圾? 有人告诉我,缺乏异步性是问题所在。我无法使用 audioFileWriteBytes,因为它访问文件系统,我将其调用到实时线程回调中。我的选择是使用异步版本 ExtAudioFileWriteAsync 或将传入的样本传递到环形缓冲区并将它们卸载到其他地方的文件中。我仍在努力。 【参考方案1】:您正在格式请求中设置(32 位)浮点标志:
format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat
然而 WAVE 文件通常包含 16 位整数样本。将 32 位浮点样本写入 16 位整数音频文件通常会产生垃圾。
【讨论】:
【参考方案2】:如果您可以确认同步文件写入是您的问题,则可以使用 GCD 异步写入文件。这是一个队列,因此它保留了顺序。它一次处理一件事情。您还可以查看剩余的物品数量、完成时间等...
dispatch_queue_t fileWritingQueue;
-(void) setup
// be sure to initialize this
fileWritingQueue = dispatch_queue_create("myQueue", NULL);
YourInputCallaback
// replace this with your synchronous file writer
dispatch_async(fileWritingQueue, ^
// write file chunk here
);
-(void) cleanUp
dispatch_release(fileWritingQueue);
【讨论】:
我尝试了这种方法,但没有奏效。要记住的一件事是,每次调用音频单元的输入回调时,我都会调用 AudioUnit 渲染,这(我相信)用当前帧填充提供的音频缓冲区列表的音频缓冲区。除非调度 audiofileWrite(异步与否)函数在堆栈上创建缓冲区的副本,否则我看不到调度队列会改变问题。【参考方案3】:我认为您的 AudioStreamBasicDescription 有问题: 你有:
format.mBytesPerFrame = 2;
format.mBytesPerPacket = 2;
format.mChannelsPerFrame = 1;
format.mBitsPerChannel = 16;
format.mFramesPerPacket = 1;
format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat
但你应该有:
format.mBytesPerFrame = format.mBytesPerPacket = 4;
和
format.mBitsPerChannel=4
使用浮点数时。 我记得那些 AudioStreamBasicDescription 有问题,因为当描述没有意义时,你永远不会收到有意义的错误,所以仔细检查总是值得的。
【讨论】:
我修改了代码,但它仍然崩溃。在这一点上,我不是手动创建 ASBD。相反,我是从 AUHAL 输入范围获取的,因此不应该有这些人为错误。可以看到当前代码github.com/AlexanderBollbach/AUHAL_Recorder/blob/master/… 请让我觉得这个问题有点奇怪。为什么“写字节”很重要?特别是 .wav 文件与“字节”有什么关系?为什么是 .wav 文件?我在 github 上查看了 OP 的代码,我发现它非常对于它的含义来说太复杂了。为什么是.mm?它必须是AU Graph吗?格式假设从何而来?如何正确初始化图及其节点?恕我直言,单个 AU 总共可以在不超过 250 行源代码中完成。【参考方案4】:为了简单和保持主题,这似乎是使用 AUHAL 音频单元将字节写入音频文件,我将尽量不讨论看起来过于复杂或过于宽泛的假设因此很难在本回复的范围内进行跟踪和调试。
如问题中所问,为了使事情顺利进行,不需要 AUGraph。一个 HAL 音频组件就可以完成这项工作。
当使用 ExtAudioFileWriteAsync() 将线性 PCM 写入文件时,是否写入 bytes 或 packets 或其他任何 AFAIK 都无关紧要。它可以只从 bufferList 中写入 inNumberFrames 成员元素。
为简单起见,我假设每帧一个输入通道,Float32数据格式,无格式转换。
假设一切都已正确声明和初始化(文档、教科书、教程和示例代码中对此进行了很好的介绍),以下 30 行纯 C 渲染回调在 单个 AU 的 kAudioUnitSubType_HALOutput 可以完成这项工作,我相信它可以变得更简单:
static OSStatus inputRenderProc (void * inRefCon,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData)
Float64 recordedTime = inTimeStamp->mSampleTime/kSampleRate;
Float32 samples[inNumberFrames];
memset (&samples, 0, sizeof (samples));
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = samples;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mDataByteSize = inNumberFrames*sizeof(Float32);
myPlayer* player = ( myPlayer *)inRefCon;
CheckError(AudioUnitRender(player->mAudioUnit,
ioActionFlags,
inTimeStamp,
kInputBus,
inNumberFrames,
&bufferList),
"Couldn't render from device");
// Write samples from bufferList into file
ExtAudioFileWriteAsync(player->mAudioFileRef, inNumberFrames, &bufferList);
return noErr;
额外的用户级提示是创建和选择由内置麦克风和内置的所谓聚合设备 Audio MIDI Setup.app 中的输出 以正确渲染单个 HAL AU。
一旦确定这样一个简单的代码按预期运行,就可以构建更复杂的程序,包括图形,但图形不是 OSX 中低级音频处理的先决条件。希望这能有所帮助。
【讨论】:
以上是关于使用 AUHAL 音频单元将字节写入音频文件的主要内容,如果未能解决你的问题,请参考以下文章
我可以使用 AVAudioEngine 从文件中读取,使用音频单元处理并写入文件,比实时更快吗?