ExtAudioFileGetProperty fails with 0xffffffda if file opened with AudioFileOpenURL and ExtAudioFileWrapAudioFileID

Hi there.

I have to load sound data from a buffer instead of a file, so I looked into AudioFileOpenWithCallbacks. All went well, I converted from an AudioFileID handle to an ExtAudioFileRef, but then it fails when I request the number of frames from that handle, with ExtAudioFileGetProperty.

For some reason, it does not fail if I open the file directly with ExtAudioFileOpenURL.


I wrote this snippet of code to demonstrate my problem.


    CFURLRef fileURL = nil;
    NSString* nsPath = [CDUtilities fullPathFromRelativePath:[NSString stringWithUTF8String:FilePath.c_str()]];
    if (nsPath)
    {
        fileURL = (CFURLRef)[[NSURL fileURLWithPath:nsPath] retain];
    }

#if 0
    status = AudioFileOpenURL (fileURL, kAudioFileReadPermission, kAudioFileMP3Type, &AudioFileHandle );
    ExtAudioFileWrapAudioFileID ( AudioFileHandle, false, &ExtAudioFileHandle );

    // Close the AudioFile handle. It's no longer needed
    AudioFileClose ( AudioFileHandle );
    AudioFileHandle = nullptr;

#else
    status = ExtAudioFileOpenURL ( fileURL, &ExtAudioFileHandle );
#endif

    // Get the audio data format
    UInt32 PropertySize = sizeof ( InputDataFormat );
    status = ExtAudioFileGetProperty ( ExtAudioFileHandle, kExtAudioFileProperty_FileDataFormat, &PropertySize, &InputDataFormat);
    if (status != noErr)
    {
        DecodeResult = false;
        goto function_exit;
    }

    if ( InputDataFormat.mChannelsPerFrame > 2 )
    {
        DecodeResult = false;
        goto function_exit;
    }

    // Set the client format to 16 bit signed integer (native-endian) data
    // Maintain the channel count and sample rate of the original source format
    OutputDataFormat.mSampleRate = InputDataFormat.mSampleRate;
    OutputDataFormat.mChannelsPerFrame = InputDataFormat.mChannelsPerFrame;

    OutputDataFormat.mFormatID = kAudioFormatLinearPCM;
    OutputDataFormat.mBytesPerPacket = 2 * InputDataFormat.mChannelsPerFrame;
    OutputDataFormat.mFramesPerPacket = 1;
    OutputDataFormat.mBytesPerFrame = 2 * InputDataFormat.mChannelsPerFrame;
    OutputDataFormat.mBitsPerChannel = 16;
    OutputDataFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;

    // Set the desired client (output) data format
    status = ExtAudioFileSetProperty ( ExtAudioFileHandle, kExtAudioFileProperty_ClientDataFormat, sizeof ( OutputDataFormat ), &OutputDataFormat );
    if (status != noErr)
    {
        DecodeResult = false;
        goto function_exit;
    }

    // Get the total frame count
    PropertySize = sizeof ( FrameCount );
    status = ExtAudioFileGetProperty ( ExtAudioFileHandle, kExtAudioFileProperty_FileLengthFrames, &PropertySize, &FrameCount );
    if (status != noErr)
    {
        DecodeResult = false;
        goto function_exit;
    }


This code runs ok, but if I use the loading code from inside the #if 0, if fails on the last ExtAudioFileGetProperty with 0xffffffda. Can anyone tell me why, and how to fix it? I really need to use the callback method.

Thanks.

Accepted Reply

I've not used ExtAudioFileWrapAudioFileID however the docs for this API say that you are required to keep the audio file object open until you're done with the ExtAudioFile object. Looking at your example it does not seem like you're doing this, so that would be something to look at.


https://developer.apple.com/reference/audiotoolbox/1486852-extaudiofilewrapaudiofileid?language=objc

"Your application is responsible for keeping the audio file object open until the extended audio file object is disposed."


The header expands on this:


"The client is responsible for keeping the AudioFileID open until the ExtAudioFileRef is disposed. Disposing the ExtAudioFileRef will not close the AudioFileID when this Wrap API call is used, so the client is also responsible for closing the AudioFileID when finished with it."

Replies

I've not used ExtAudioFileWrapAudioFileID however the docs for this API say that you are required to keep the audio file object open until you're done with the ExtAudioFile object. Looking at your example it does not seem like you're doing this, so that would be something to look at.


https://developer.apple.com/reference/audiotoolbox/1486852-extaudiofilewrapaudiofileid?language=objc

"Your application is responsible for keeping the audio file object open until the extended audio file object is disposed."


The header expands on this:


"The client is responsible for keeping the AudioFileID open until the ExtAudioFileRef is disposed. Disposing the ExtAudioFileRef will not close the AudioFileID when this Wrap API call is used, so the client is also responsible for closing the AudioFileID when finished with it."

Thank you. I had figured it out later on, after I posted this question, but it still doesn't make any sense to me... Why did the rest of the calls work, then? I managed to read the file, get some properties, just not THAT specific one....

Just weird apple stuff, I guess...

I had a chance to look at the code and as you know ExtAudioFile wraps an AudioFile object -- the first two properties appear to be cached and available so there's no need to touch the invalid AudioFile object. However, when you attempt to ask for the FrameLength we need to call out to the underlying AudioFile and ask for the PacketTableInfo property. This fails because you've closed the AudioFile so we return 0xffffffda which is a -38 or kAudioFileNotOpenError.


So not wierd, just how the API works.