Google+
Showing posts with label iOS audio programming. Show all posts
Showing posts with label iOS audio programming. Show all posts

Saturday, April 18, 2015

Youtube data client library iOS, oauth2, HTTPfetcher

1. Compiling google api

This page shows the method to compile the google api to a project. I used the second way: compiling the source files directly into iOS project.

When I downloaded its source file, the oauth2 and HTTPfetcher are not there. We need to download them separately from here and here. Then just follow the procedure in the first page to finish compiling.

2. Youtube search

To search the data on youtube, firstly, import three headers:

#import "GTLServiceYouTube.h"
#import "GTLQueryYouTube.h"
#import "GTLYouTubeSearchListResponse.h"

Then for example, we search here for the youtube id by giving a searching word string:

GTLServiceYouTube *service = [[GTLServiceYouTube alloc] init];
service.APIKey = yourAPIKey;
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:@"id"];
query.type = @"video";
query.q = aSearchingString;
query.maxResults = aInt;

The code is very clear itself. We declare a youtube service instance, give it the APIKey, declare a query instance. The meaning of type, q and maxResults can be found here. Finally we execute this query which will give us a ticket. In the completionHandler, we receive the returned object.

GTLServiceTicket *ticketSend = [service executeQuery:query
                                   completionHandler:^(GTLServiceTicket *ticketReceive, id object, NSError *error)
    {
        if (!error) {
            GTLYouTubeSearchListResponse *items = object;
            }
        } else {
           
        }
    }];

Tuesday, March 3, 2015

Using AcoustID in IOS

AcoustID is an audio finger print project where Chromaprint allows you to calculate finger print from audio sources, and its web service allows you to search or to submit audio information in its database by Chromaprint.

Chormaprint is the core component of AcoustID project, it is written in C and can be compiled by cmake. However to use it in IOS, one needs a toolchain file.

1. Compile Chromaprint for IOS
1) You need to download the toolchain file here: https://code.google.com/p/ios-cmake/source/browse/

2) unzip the file "iOS.cmake" to Chromaprint source files' root folder. Open it, then change IOS_PLATFORM according to what you need. The default building architecture for "OS" is armv7 and arm64, this is sufficient for nowadays IOS devises.

3) build source files into a static library

1. mkdir build.ios 
2. cd build.ios 
3. cmake -DCMAKE_TOOLCHAIN_FILE=../../../iOS.cmake -GXcode ..
4. open Xcode project, compile chromaprint_p target for "iOS Device"

4) the static library will appear in the folder src/Debug-iphoneos/libchromaprint_p.a

2. Calculate finger print
Chromaprint only receives LinearPCM as the input audio format, so one should do the conversion if his original audio file format is not LinearPCM. The conversion method can be consulted here.

Once we get the URL of the audio file, we can open it by function AudioFileOpenURL, and save the fileID to outAudioFile.

The procedure of the finger print calculation is:

1. define a finger print calculation algorithm, here we use the default one: 
int algo = CHROMAPRINT_ALGORITHM_DEFAULT.

2. declare a finger print context and start calculation:
ChromaprintContext chromaprintCtx = chromaprint_new(algo);
chromaprint_start(chromaprintCtx, samplerate, channels);

3. read data packet, feed audio buffer outBuffer into this context, this could be done in a while loop:
AudioFileReadPackets(outAudioFile, false, &outNumBytes, NULL, inStartingPacket,&ioNumPackets, outBuffer);
chromaprint_feed(chromaprintCtx, outBuffer, ioNumPackets * framesPerPackets * 2)
where ioNumPackets is the packet number of the buffer,  inStartingPacket is the starting packet of the buffer, framesPerPackets = 1 for linearPCM audio format. Notice that AcoustID web service needs only 2 minutes audio finger print for database searching.

4. finish the finger print calculation and finally get the finger print:
chromaprint_finish(chromaprintCtx)
char *fingerPrint;
chromaprint_get_fingerprint(chromaprintCtx, &fingerPrint)

Now we have the finger print, and we can use it with AcoustID API to search its database.

Tuesday, January 13, 2015

Hide UITableView index of empty sections iOS

In iOS, we use "UILocalizedIndexedCollation" to partition music library song objects into sections to display them in table view. The "sectionTitles" array of "currentCollation" instance contains all letters in alphabet from A to Z if our system preferred language is English.

UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSArray *sectionTitles = [collation sectionTitles];

Sometimes, due to lack of song titles which begin with certain letters, we have empty sections. For the sake of redundancy, we don't want display their titles and indexes in table view.

To hide them, we could declare a NSMutableArray to store those sections which are not empty:

// declare this in @interface
@property (nonatomic, strong) NSMutableArray *sectionIndexTitles;

// put this snippet in objects partition function
int i = 0;
for (NSMutableArray *section in unsortedSections) {
    if ([section count] != 0) {
        [sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
        [self.sectionIndexTitles addObject:[[collation sectionIndexTitles] objectAtIndex:i]];
    }
    i++;
}

Finally we set up the table view delegate methods by using our "sectionIndexTitles":

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.sectionIndexTitles count];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [self.sectionIndexTitles objectAtIndex:section];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return self.sectionIndexTitles;
}

// touching the index jump to corresponding section
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [self.sectionIndexTitles indexOfObject:title];

Read this article to know how to configure UITableView by UILocalizedIndexedCollation.

Background download iOS

1. Setup background download session 


To setup a background download session in iOS is quite straightforward. Firstly setup a configuration, and use this configuration to declare a NSURLSession.

NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
_downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];

Then we declare a NSURLSessionDownloadTask, and start this task by calling resume:
NSURLSessionDownloadTask *task = [fh.downloadSession downloadTaskWithURL:aNSURL];
[task resume];

After that, one should also add four delegate methods which are:
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
}
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
}
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
}
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
}

One could collect the download results in the third method "didFinishDownloadingToURL" by using:
 
NSData *aData = [NSData dataWithContentsOfURL:location];

Notice that the fourth method "didCompleteWithError" will always be called. If the task is finished without error, it returns nil. Otherwise, it returns some error code for us to catch the error.

The task started in foreground will continue its downloading after entering to background.

2. Batch background download data from MusicBrainz 


MusicBrainz is a music meta-information database, one can find and download these information by searching song title, artist name, or album title, etc. It offers a public API for developers to query its database.

But there is a query/request rate limit which is about 1 request/second. That is to say if we launch, say , 100 request tasks at the same time, the server will reject them all. To achieve a successful query without being rejected, I schedule the tasks in a queue by "dispatch_after":

for (MPMediaItem *item in songLibrary) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // start background music meta-data download task here.
    });
}

The problem arises when we enter to background, even our download task support background download mode, but our dispatch_after block will stop running. To deal with this problem, I manage to ask for more running time after the app entering to background:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
    [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
    _backgroundTask = UIBackgroundTaskInvalid;
    }];
}

The method "beginBackgroundTaskWithExpirationHandler" will be called when our _backgroundTask remaining time expired. In my iPhone 4, the system leaves 178 seconds for background task executing.

This method is far from good and needs to be improved.