2

I am trying to create a wav file from the sound input I get from the default input device of my macbook (built-in mic). However, the resultant file when imported to audacity as raw data is complete garbage.

First I initialize the audio file reference so I can later write to it in the audio unit input callback.

   // 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");

Here I malloc some buffers to hold the audio data coming in from the AUHAL unit.

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);
      }
   }

To check that the data is actually sensible, I render the audio unit and than log the first 4 bytes of each set of frames (4096) in each callback. The reason was to check that the values were in keeping with what was going into the mic. As I would talk into the mic I noticed the logged out values in this location of memory corresponded to the input. So it seems that things are working in that regard:

// 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);

And finally, in the input callback I write the audio bytes to the file.

 UInt32 numOfBytes = 4096*player->streamFormat.mBytesPerFrame;

   AudioFileWriteBytes(player->recordFile,
                       FALSE,
                       player->startingByte,
                       &numOfBytes,
                       &ioData[0].mBuffers[0].mData);

   player->startingByte += numOfBytes;

So I have not figured out why the data comes out sounding glitchy, distorted or not there at all. One thing is that the resultant audio file is about as long as I actually recorded for. (hitting return stops the audio units and closes the audiofile).

I'm not sure what to look at next. Has anyone attempted writing to an audiofile from the AUHAL callback and had similar results?

  • Is AudioFileWriteBytes asynchronous? Apple's documentation says ?In most cases, you should use AudioFileWritePackets instead of this function." https://developer.apple.com/library/ios/documentation/MusicAudio/Reference/AudioFileConvertRef/#//apple_ref/c/func/AudioFileWriteBytes – jaybers Jan 11 '16 at 21:07
  • So can you hear what you spoke into the mic at all? Or is the audio just completely garbage? – rocky Jan 12 '16 at 00:02
  • 1
    I have been told that the lack of asynchronicity is the issue. I cannot use audioFileWriteBytes as it accesses the file system and i'm calling it into a realtime thread callback. My options are to use the asynchronous version ExtAudioFileWriteAsync or to pass the incoming samples to a ring buffer and offload them to a file elsewhere. I'm still working on that. – Alexander Bollbach Jan 12 '16 at 00:28

4 Answers4

1

You are setting the (32-bit) floating point flag in your format request:

format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat

yet WAVE files usually contain 16-bit integer samples. Writing 32-bit float samples into 16-bit integer audio files will usually produce garbage.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
0

If you can confirm that synchronous file writing is your issue, you can use GCD to write your file asynchronously. It is a queue so it preserves the order. It processes things one at a time. You can also check to see how many items are left, when it finishes etc...

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);
}
jaybers
  • 1,991
  • 13
  • 18
  • i tried this approach but it did not work. one thing to keep in mind is that each time the audio unit's input callback is invoked I call AudioUnit render and this (i believe) fills the provided audiobufferlist's audiobuffer's with the current frames. Unless dispatching the audiofileWrite (async or not) function creates a copy of the buffers on a stack or something I don't see the dispatch queue changing the issue. – Alexander Bollbach Jan 13 '16 at 22:57
0

I think there's a problem with your AudioStreamBasicDescription: You have:

format.mBytesPerFrame = 2;
format.mBytesPerPacket = 2;
format.mChannelsPerFrame = 1;
format.mBitsPerChannel = 16;
format.mFramesPerPacket = 1;
format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat

But you should have:

format.mBytesPerFrame = format.mBytesPerPacket = 4;

and

format.mBitsPerChannel=4

when using floats. I remember having trouble with those AudioStreamBasicDescription's because you never get a meaningful error back when the description doesn't make sense, so it always worth to double check.

user1005265
  • 83
  • 1
  • 4
  • I revised the code and am it is still crashing. At this point I am not creating the ASBD by hand. Rather, I'm getting it from the AUHAL input scope so there should be none of these human errors. you can see the current code https://github.com/AlexanderBollbach/AUHAL_Recorder/blob/master/AUHAL_RecordingStuff/main.mm – Alexander Bollbach Jan 14 '16 at 22:30
  • May I find the question somewhat strangely put, please. Why is it important to "write bytes"? What have the "bytes" to do with a .wav file in particular and why a .wav file? I took a look at OP's code on github, I find it **much** too complicated for what it is meant for. Why .mm? Does it have to be an AU Graph? Where do the format assumptions come from? What about proper initialization of the graph and its **nodes**? A single AU can do it in no more than 250 lines of source code altogether, IMHO. – user3078414 Feb 13 '16 at 00:28
0

For the sake of simplicity and staying on-topic, which seems to be Writing bytes to audio file using AUHAL audio unit, I'll try not to discuss assumptions which seem overly complex or too broad and therefore difficult to trace and debug, within the scope of this reply.

  1. For making things work, as asked in the question, one doesn't need an AUGraph. A single HAL audio component does the job.

  2. When using ExtAudioFileWriteAsync( ) for writing linear PCM to file, it is irrelevant if one is writing bytes or packets or whatsoever, AFAIK. It can just write inNumberFrames member elements from the bufferList.

  3. For simplicity, I presume one input channel per frame, Float32 data format, no format conversion.

Under assumption everything is properly declared and initialized (which is well covered in documentation, textbooks, tutorials and sample code), the following 30-line plain-C render callback on a single AU of kAudioUnitSubType_HALOutput does the job, and I'm sure it can be made even simpler:

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;     
}

Additional user-level tip is to create and select so-called Aggregate Device consisting of Internal microphone and Built-in output in Audio MIDI Setup.app to make a single HAL AU render properly.

Once sure that such a simple code behaves as expected, one can build more complex programs, including graphs, but a graph is not a prerequisite for low-level audio processing in OSX. Hope this can help.

user3078414
  • 1,942
  • 2
  • 16
  • 24