Attention! For Japanese customers, my Japanese blog might help.
Showing posts with label Mac. Show all posts
Showing posts with label Mac. Show all posts

Oct 16, 2017

Steps to make your homemade V3 AudioUnit support in-process loading

NOTE: This is the English version of my Japanese article.

To make my homemade AudioUnit available in GarageBand in the last article,
It seems necessary to support in-process loading.

As a result of trial and error, I was able to release GarageBand compatible version.
After all, I imitated AudioUnitV3Example's implementation.
Here is a rough modification procedure.

Steps:
1: Make the new cocoa framework target for AU.
2: Move all implementation files of AU (.appex) target to the framework target. However, .xib and image resources need to be included in both targets.
3: Prepare a .m file and implement a dummy function. Include it in the .appex target.
4: Edit Info.plist of .appex. Add a key named "AudioComponentBundle" to NSExtension -> NSExtensionAttributes and set the bundle identifier of the framework target as a value.
5: Override the init of the AU view controller with reference to the sample code FilterDemoViewController.m. In my case, it is as follows:
- (id)init {
    self = [super initWithNibName:@"AudioUnitViewController"
                           bundle:[NSBundle bundleForClass:NSClassFromString(@"AudioUnitViewController")]];
    return self;
}
Without this we could not display the view with GarageBand.
I think that handling AU of GarageBand may be different from that of sample code.

Other tips:
If you use external frameworks, which were already used before supporting in-process loading,
it seems better to embed them in both the .app and the .framework.
If the external frameworks are not embeded in .appex, GarageBand may not be able to load your AU with an error like the following:
Error loading {...snipped...}/SYVibratoFramework:  dlopen({...
snipped...}/SYVibratoApp.app/Contents/Frameworks/SYVibratoFramework.framework/SYVibratoFramework, 265): Library not loaded: @rpath/SYFW.framework/Versions/A/SYFW
  Referenced from: {...
snipped...}/SYVibratoApp.app/Contents/Frameworks/SYVibratoFramework.framework/SYVibratoFramework
  Reason: image not found
Perhaps you can solve it without embedding if you can set @rpath.


--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit my web site.

[Solved] My homemade V3 AudioUnit could not be loaded on GarageBand on macOS 10.13

NOTE: This is the English version of my Japanese article.

- 2017/10/14 NOTE -
I have posted a solution.
- 2017/10/14 End of NOTE -

summary:
It seems that my AU needs to support "in-process loading" to use in GarageBand.
For that, the all of AU implementation should be moved to a homemade cocoa framework.

Details:
When I was testing my homemade AU on macOS 10.13, I found that GarageBand can not load my AU normally.
Although the AU was displayed in the list, a surprise mark was displayed when my AU was selected.



Even if you click it, AU's view was not displayed.
And, the effect functionality was also worked.

Although I can not identify the cause yet, it is probably because it is because my original AU can not handle in-process reading.

Reading logs in Console.app, I confirmed that GarageBand was outputting an error:
254: Extension <private> advertises loadable bundle <private>, but we couldn't find it
To solve this error, I rechecked build settings, Info.plist, Sandbox related settings, etc...
But I could not solve it.

In subsequent investigation with my AU containing app, if kAudioComponentInstantiation_LoadInProcess is specified as the second argument of + [AVAudioUnit instantiateWithComponentDescription: options: completionHandler:],
I found that similar errors are displayed.
Apparently, GarageBand seems to be trying to load my AU in-process.

Supporting in-process loading:
It seems necessary to provide all the implementation of self-made AU as .framework in order to make homemade AU support in-process loading.
The crew is commented out in FilterDemoExtension.m in the sample code "AudioUnitV3ExampleABasicAudioUnitExtensionandHostImplementation":
To facilitate loading in-process and into a separate extension process, the .appx main binary cannot contain any code. Therefore, all the plugin functionality is contained in the FilterDemo.framework and the .appx Info.plist contains an AudioComponentBundle entry specifying the frameworks bundle identifier.
In fact, FilterDemoExtension.m has only a function called dummy (), and all AU implementations seem to be thrust into FilterDemoFramework.framework.

From now on, when we created the new V3 AudioUnit project, it may be better to use this sample code as a template.
I confirmed that the sample AU (FilterDemo) could be used in GarageBand without problems.

Also, as you can see in this comment, bundle identifier seems to need a bit of ingenuity,
I do not know well so I would like to investigate in the future.
It seems that bundle identifier in the sample code was changed compared to older one.
So, it may be necessary change to support in-process loading.

To be continued...

--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit my web site.

[Solved] I could not setup Xcode Server on Macmini

NOTE: This is the English version of my Japanese article.

-- Edit 2017/11/12 --
After I deleted "/Library/Developer/XcodeServer",  I could finally setup Xcode Server without errors.
xcode9 - Xcode 9 enable Server fails because "xcsd" keychain password - Stack Overflow will be helpful.
(By the way, deleting xcsd on Keychain Access.app did not work for me.)

I appreciate the anonymous who have left a comment on my article.
-- Edit end --


When I tried to set up Xcode Server on macOS 10.13 on Macmini(I was using for CI with Xcode 8), a dialog comes out:


xcsreciperunnerは、キーチェーンに含まれるキー"Xcode Server Root Certificate Authority"を使用して署名しようとしています。
In English, this means "xcsreciperunner is trying to sign using the key "Xcode Server Root Certificate Authority" contained in the key chain."

And, I was asked for the password of the user called xcsd.

Passwords for an admin and a user for CI on Xcode 8 were both not accepted.
I do not understand well, so I chose "Deny".
Then, a user cancel error was displayed.




After that, I attempted to turn on Xcode Server again, but the password input dialog was not displayed this time.
Instead, the following error was displayed immediately:

Could not create client certificate authority: Error Domain=Security Code=-25293 "The user name or passphrase you entered is not correct." UserInfo={NSLocalizedDescription=The user name or passphrase you entered is not correct.}



After restarting the machine and when turning on Xcode Server,  the password input dialog display again.
After all I do not know the password, so I decided to give up CI on this machine.

After that, on the development Macbook, I was able to set up Xcode Server without any problems.
Even if you search the net with "xcsreciperunner" or "xcsd", since it did not show such a solution, it may be an environment dependent error.

By the way, after doing the above procedure, the item "xcsd" was arbitrarily added to Keychain Access.app.



In the future, if you really need CI on Macmini
I think I will re-install the system cleanly.

--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit my web site.

Homemade V3 AudioUnit did not work on a third party host application.

NOTE: This is the English version of my Japanese article.

While verifying a homemade AudioUnit on macOS 10.13, an error was displayed with auval command.
The error looks like the following:

2017-10-10 11:45:27.172 auvaltool[508:5335] Error: Session 091F49F0-C167-4CFB-820E-4DBCC049FD3B was unable to communicate with the remote service: Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 512 was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid." UserInfo={NSDebugDescription=The connection from pid 512 was interrupted, but the message was sent over an additional proxy and therefore this proxy become invalid.}


As a result of the investigation, the error is because my AU did not link to my own framework.
 I solved the problem by linking AU to my framework(adding it to [Build Phases]-[Link Binary With Library] on AU's target setting on Xcode).



I have no idea why this problem did not occur in the past build.

--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit my web site.

Could not create the view of homemade V3 AudioUnit

NOTE: This is the English version of my Japanese article.

As mentioned in this article, although I could make an AVAudioUnit instance,  the view controller for the AU  was not created this time.

I have used -[AUAudioUnit requestViewControllerWithCompletionHandler:] to create the view controller, but "viewController" in completionHandler was always nil.

By referring to the implementation of -[ViewController embedPlugInView] in an Apple's sample code "AudioUnitV3Example", I fixed my code to make a view directly from a bundle.
Finally, I could create a view controller.

My implementation of embedPlugInView is as follows:

- (void)embedPlugInView {
    NSURL *builtInPlugInURL = [[NSBundle mainBundlebuiltInPlugInsURL];
    NSURL *pluginURL = [builtInPlugInURL URLByAppendingPathComponent@"SYVibrato.appex"];
    NSBundle *appExtensionBundle = [NSBundle bundleWithURL: pluginURL];
  
    self.vibratoViewController = [[AudioUnitViewController allocinitWithNibName:@"AudioUnitViewController"                                                                           bundle:appExtensionBundle];
  
    [self.vibratoProgressIndicator stopAnimation:self];    NSRect r = self.vibratoView.frame;
    [self.vibratoViewController view].frame = NSMakeRect(00NSWidth(r), NSHeight(r));    [self.vibratoView addSubview:self.vibratoViewController.view];  
    [self.vibratoViewController connectAU:self.vibratoUnit.AUAudioUnit];}

Note that, if your AU can be recognized with a third party host apps, you can create a view with the conventional method requestViewControllerWithCompletionHandler: as well.

Because AU cannot be recognized(installed) immediately on macOS 10.13, it may be that such a correction has become necessary.

To avoid wasting time, we had better use projects in "AudioUnitV3Example" as a template.

--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit my web site.

Recognizing homemade V3 AudioUnit on third-party AU host apps on macOS 10.13

NOTE: This is the English version of my Japanese article.

To recognize v3 AudioUnits on macOS 10.13, it seemed that it is not enough to just launch an AU containing application.
To be recognized,

  1. Confirm that your AU is not listed by a command 'auval -a'
  2. Build and launch your AU containing application
  3. Log out or restart the machine
  4. log in
  5. Confirm that your AU is listed by 'auval -a'
Re-login is probably important and there may be no need to launch your application.(more survey is needed)
Conversely, if you do not want to recognize your AU any more,

  1. Confirm that your AU is listed by 'auval -a'
  2. Delete your AU containing application (delete also from trash box)
  3. Log out or restart the machine
  4. log in
  5. Confirm that your AU is not listed by 'auval -a'
If there are multiple same applications, it seems necessary to delete them all.
For example, Debug Build, Release Build, and Archive Build are generated in a separate directory (~/Library/Xcode/DerivedData/...)
So, you have to delete all of them.
(It should be noted that even if Clean is executed on Xcode, the build of other configurations will not be deleted.)


--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit my web site.

Oct 15, 2017

Homemade V3 AudioUnit did NOT work on macOS 10.13

NOTE: This is the English version of my Japanese article.

My development environment is Xcode 9.0 on macOS 10.13.

Can't instantiate AVAudioUnit

My AU seemed to fail instantiating an AVAudioUnit at +[AVAudioUnit instantiateWithComponentDescription:options:completionHandler:].

An 'error' variable in completionHandler was
Error Domain=NSOSStatusErrorDomain Code=-3000 "invalidComponentID"
After a struggle, I found the error could be removed by turning off Sandboxing of my containing app.
FYI,  Apple's sample code "AudioUnitV3Example" also disables Sandbox for host apps.

Note that, even if Sandbox is enabled, I could make AUAudioUnit instance using +[AUAudioUnit instantiateWithComponentDescription:options:completionHandler].
So, if Sandbox environment is required and AVAudioUnit is not, the method will be better.
And more, I tried AudioComponentInstanceNew(), but failed on Sandbox environment.

Can't make AUViewController

Next to the problem above, there was another problem that an AU view was not displayed.

After a call of -[AUAudioUnit requestViewControllerWithCompletionHandler:],
'viewController' variable in completionHandler was nil.

It seemed that the controller instance was never created.

In the sample code "AudioUnitV3Example",  the controller seems to be created by using a bundle directly in -[ViewController embedPlugInView].
So, I will imitate that implementation. (not started yet)

Remarks

On 10.13, even if an AU containing app is activated, the AU is not shown in the list by a command 'auval -a'.
This means other host apps can't use my AU. So I need to resolve the problem too.

It's a bother!

To be continued...

--
I'm looking for job for macOS/iOS application development by outsourcing.
For details, please visit http://shakeyama.com/about/index_e.php.

Sep 5, 2016

Convert non-LPCM(such as MP3 or AAC) to LPCM using AudioToolbox

NOTE: This is the English version of my Japanese article.
NOTE: You can read non-LPCM files with ExtAudioFile functions more easily than with AudioFile functions.

I wrote a snippet code because I could not read .mp3 files on my app somehow.
As a result, I got an expected result finally.

Through a workaround, in case of non-LPCM, I found that we should care for ASPD(AudioStreamPacketDescription) and magic cookies.

I believe I cloud have read non-LPCM files with more lazy code... (in OS X 10.3 panther)

A file reading process is done in an input callback of AudioConverter, and the ASPD acquired in the callback should be returned with wave data.

The snippet code is as follows. Next, I want to check the code can be run in worker-thread.
#include <CoreServices/CoreServices.h>
#include <AudioToolbox/AudioToolbox.h>

#define _ERR_RETURN(err)  {if(noErr != err){printf("%d - err:%d\n", __LINE__, err); return err;}}

OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath);
OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData);
void SetStandardDescription(AudioStreamBasicDescription *descPtr);

typedef struct MyConverterData {
    AudioFileID fileID;
    UInt64 totalPacketCount;
    UInt64 packetOffset;
} MyConverterData;

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    OSStatus err = DecodeFileAtPath((const char *)(argv[1]), (const char *)argv[2]);
    printf("done. err:%d\n", err);
    
    return 0;
}

/*
   read an audio file specified by inputFilePath and write as CD-DA format(big-endian) binary data to outputFilePath. 
  This method is mainly intended to verify how to convert non-LPCM data such as MP3 or AAC to LPCM data. (.wav or .aiff are also readable)
   When failed, this method returns an OSStatus that is returned by internal CoreAudio functoins.
 */
OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath) {
    OSStatus err = noErr;
    AudioFileID fileID;
    MyConverterData myConverterData = {000};
    
    // open a file and get its ASBD and the number of packets.
    AudioStreamBasicDescription fileDesc;
    {
        CFURLRef url;
        url = CFURLCreateWithBytes(NULL,
                                   (const UInt8 *)inputFilePath,
                                   strlen(inputFilePath),
                                   kCFStringEncodingUTF8,
                                   NULL);
        
        err = AudioFileOpenURL(url,
                               fsRdPerm,
                               0,
                               &fileID);
        _ERR_RETURN(err);
        
        myConverterData.fileID = fileID;
        UInt32 size = sizeof(AudioStreamBasicDescription);
        memset(&fileDesc, 0, size);
        
        err = AudioFileGetProperty(fileID,
                                   kAudioFilePropertyDataFormat,
                                   &size,
                                   &fileDesc);
        _ERR_RETURN(err);
        size = sizeof(myConverterData.totalPacketCount);
        err = AudioFileGetProperty(fileID,
                                   kAudioFilePropertyAudioDataPacketCount,
                                   &size,
                                   &myConverterData.totalPacketCount);
        _ERR_RETURN(err);
    }
    
    // sets output format(ASBD)
    AudioStreamBasicDescription outDesc;
    SetStandardDescription(&outDesc);
    
    // creates an AudioConverter using file's ASBD and output ASBD.
    AudioConverterRef converter;
    {
        err = AudioConverterNew(&fileDesc, &outDesc, &converter);
        _ERR_RETURN(err);
    }

    // set magic cookie if exits.
    // (otherwise !dat will be returned by AudioConverterFillComplexBuffer() 
    {
        UInt32 size = 0;
        err = AudioFileGetPropertyInfo(fileID,
                                       kAudioFilePropertyMagicCookieData,
                                       &size,
                                       NULL);
        if(noErr == err){
            void *magic = calloc(1, size);
            err = AudioFileGetProperty(fileID,
                                       kAudioFilePropertyMagicCookieData,
                                       &size,
                                       magic);
            if(noErr == err){
                printf("magic cookie info found.\n");
                err = AudioConverterSetProperty(converter,
                                                kAudioConverterDecompressionMagicCookie,
                                                size,
                                                magic);
                _ERR_RETURN(err);
            }
            err = noErr;
            if(NULL != magic){
                free(magic);
            }
            _ERR_RETURN(err);
        }else{
            printf("magic cookie info not found.\n");
            
        }
    }
    
    const UInt32 maxNumPacketsInACycle = 100// the number of packets to read in a loop
    FILE *fp = fopen(outputFilePath, "w");
    if (fp == NULL) {
        printf"fopen failed.");
        return writErr;
    }
    
    err = noErr;
    
    // repeat until reading and decoding are finished.
    while(err == noErr){
        // exit loop if the last packet was processed
        if (myConverterData.packetOffset == myConverterData.totalPacketCountbreak;
        
        UInt32 numPackets = maxNumPacketsInACycle; // the max number of packets to read
        UInt32 numFrames = fileDesc.mFramesPerPacket * numPackets; // the max number of frames to read
        
        AudioBufferList list;
        // preparation for list is needed (otherwise, -50 will be returned immediately without calling inputProc)
        list.mNumberBuffers = 1;
        list.mBuffers[0].mNumberChannels = 2;
        list.mBuffers[0].mDataByteSize = numFrames * outDesc.mBytesPerFrame// maximum byte size after decoding.
        list.mBuffers[0].mData = malloc(list.mBuffers[0].mDataByteSize);
        err = AudioConverterFillComplexBuffer(converter,
                                              MyACComplexInputProc,
                                              &myConverterData,
                                              &numPackets,
                                              &list,
                                              NULL);
        
        if (err == noErr) {
            // read decoded data
            fwrite(list.mBuffers[0].mData, list.mBuffers[0].mDataByteSize1, fp);
        }
        free(list.mBuffers[0].mData);
    }
    
    // finalize
    fclose(fp);
    err = AudioConverterDispose(converter);
    _ERR_RETURN(err);
    
    err = AudioFileClose(fileID);
    return err;
}

/*
 A callback for AudioConvereter.
 This read required packets from file and set them to ioData.
 If these ASBDs are acquired, this returns them via outDataPacketDescription.
 An inUserData must be the pointer to a MyConverterData variable.
 */
OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData) {
    // fileIDを取得する
    MyConverterData *myConverterDataPtr = (MyConverterData *)inUserData;
    AudioFileID fileID = myConverterDataPtr->fileID;
    
    // determine a data size to read from file
    // the size is calculated by multiplying the probable max packet size and the number of packets.
    // FIXME: the max packet size is NOT needed to be acquired in every callback.
    UInt32 maxPacketSize;
    UInt32 dataSize = sizeof(maxPacketSize);
    OSStatus err = AudioFileGetProperty(fileID,
                                        kAudioFilePropertyMaximumPacketSize,
                                        &dataSize,
                                        &maxPacketSize);
    _ERR_RETURN(err);
    
    UInt32 numBytes = maxPacketSize * *ioNumberDataPackets; // the probable maximum data size
    
    // allocate buffer
    static void *buffer = NULL;
    if (buffer != NULL)
        free(buffer);
    buffer = malloc(numBytes);
    
    // allocate memory for ASPD.
    static AudioStreamPacketDescription *descs = NULL;
    if (descs != NULL)
        free(descs);
    descs = malloc(sizeof(AudioStreamPacketDescription) * *ioNumberDataPackets);
    
    // reading from a file
    err = AudioFileReadPacketData(fileID,
                                  false,
                                  &numBytes,
                                  descs,
                                  myConverterDataPtr->packetOffset,
                                  ioNumberDataPackets,
                                  buffer);
    _ERR_RETURN(err);
    
    // sets acquired wave data to ioData
    ioData->mBuffers[0].mDataByteSize = numBytes;
    ioData->mBuffers[0].mData = buffer;
    
    // sets acquired ASPDs. For .wav or .aiff files, the code was skipped because NULL was set
    if (outDataPacketDescription != NULL) {
        *outDataPacketDescription = descs;
    }
    
    // update the number of read packets
    myConverterDataPtr->packetOffset += *ioNumberDataPackets;
    
    // output for debug
    if (myConverterDataPtr->packetOffset % 100 == 0) {
        printf("%llu/%llu\n", myConverterDataPtr->packetOffset, myConverterDataPtr->totalPacketCount);
    }
    
    return err;
}

/*
 sets CDDA-quality ASBD to descPtr
 Take care for use with little-endian processors because this function returns big-endian format.
 */
void SetStandardDescription(AudioStreamBasicDescription *descPtr) {
    descPtr->mSampleRate = 44100.0;
    descPtr->mFormatID = kAudioFormatLinearPCM;
    descPtr->mFormatFlags = kAudioFormatFlagIsBigEndian |
                            kAudioFormatFlagIsSignedInteger |
                            kAudioFormatFlagIsPacked;
    descPtr->mBytesPerPacket = 4;
    descPtr->mBytesPerFrame = 4;
    descPtr->mFramesPerPacket = 1;
    descPtr->mChannelsPerFrame = 2;
    descPtr->mBitsPerChannel = 16;
}

By the way, you should use an afconvert command is if you are not familiar with programming.