5

i had to convert big file size song from iTunes library to a smaller 8K song file.

As i did the converting async, the bool always return true even though writing to doc folder are not completed. At the moment i'm using a delay of 10sec before i called the function again and it works fine on the interim for iPhone 5s, but i would like to cater on the slower devices.

kindly give me some pointer / recommendation on my code.

-(void)startUploadSongAnalysis
{
     [self updateProgressYForID3NForUpload:NO];

    if ([self.uploadWorkingAray count]>=1)
    {
        Song *songVar = [self.uploadWorkingAray objectAtIndex:0];//core data var
        NSLog(@"songVar %@",songVar.songName);
        NSLog(@"songVar %@",songVar.songURL);
        NSURL *songU = [NSURL URLWithString:songVar.songURL]; //URL of iTunes Lib
       // self.asset = [AVAsset assetWithURL:songU];
       // NSLog(@"asset %@",self.asset);
        NSError *error;
        NSString *subString = [[songVar.songURL componentsSeparatedByString:@"id="] lastObject];
        NSString *savedPath = [self.documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"audio%@.m4a",subString]];//save file name of converted 8kb song
        NSString *subStringPath = [NSString stringWithFormat:@"audio%@.m4a",subString];

        if ([self.fileManager fileExistsAtPath:savedPath] == YES)
            [self.fileManager removeItemAtPath:savedPath error:&error];
        NSLog(@"cacheDir %@",savedPath);

        //export low bitrate song to cache
        if ([self exportAudio:[AVAsset assetWithURL:songU] toFilePath:savedPath]) // HERE IS THE PROBLEM, this return true even the writing is not completed cos when i upload to my web server, it will say song file corrupted
        {
           // [self performSelector:@selector(sendSongForUpload:) withObject:subStringPath afterDelay:1];
            [self sendRequest:2 andPath:subStringPath andSongDBItem:songVar];
        }
        else
        {
            NSLog(@"song too short, skipped");
            [self.uploadWorkingAray removeObjectAtIndex:0];
            [self.songNotFoundArray addObject:songVar];
            [self startUploadSongAnalysis];
        }
    }
    else //uploadWorkingAray empty
    {
        NSLog(@"save changes");
        [[VPPCoreData sharedInstance] saveAllChanges];
    }
}





#pragma mark song exporter to doc folder
- (BOOL)exportAudio:(AVAsset *)avAsset toFilePath:(NSString *)filePath
{
    CMTime assetTime = [avAsset duration];
    Float64 duration = CMTimeGetSeconds(assetTime);
    if (duration < 40.0) return NO; // if song too short return no

    // get the first audio track
    NSArray *tracks = [avAsset tracksWithMediaType:AVMediaTypeAudio];
    if ([tracks count] == 0) return NO;

    NSError *readerError = nil;
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:avAsset  error:&readerError];
   //AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:avAsset error:&readerError]; // both works the same ?

    AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput
                                         assetReaderAudioMixOutputWithAudioTracks:avAsset.tracks
                                         audioSettings: nil];

    if (! [reader canAddOutput: readerOutput])
    {
        NSLog (@"can't add reader output...!");
        return NO;
    }
    else
    {
        [reader addOutput:readerOutput];
    }

    // writer AVFileTypeCoreAudioFormat AVFileTypeAppleM4A
    NSError *writerError = nil;
    AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:filePath]
                                                      fileType:AVFileTypeAppleM4A
                                                         error:&writerError];
    //NSLog(@"writer %@",writer);
    AudioChannelLayout channelLayout;
    memset(&channelLayout, 0, sizeof(AudioChannelLayout));
    channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

    // use different values to affect the downsampling/compression
    //    NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
    //                                    [NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
    //                                    [NSNumber numberWithFloat:16000.0], AVSampleRateKey,
    //                                    [NSNumber numberWithInt:2], AVNumberOfChannelsKey,
    //                                    [NSNumber numberWithInt:128000], AVEncoderBitRateKey,
    //                                    [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey,
    //                                    nil];

    NSDictionary *outputSettings = @{AVFormatIDKey: @(kAudioFormatMPEG4AAC),
                                     AVEncoderBitRateKey: @(8000),
                                     AVNumberOfChannelsKey: @(1),
                                     AVSampleRateKey: @(8000)};

    AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:outputSettings];


    //\Add inputs to Write
    NSParameterAssert(writerInput);
    NSAssert([writer canAddInput:writerInput], @"Cannot write to this type of audio input" );

    if ([writer canAddInput:writerInput])
    {
        [writer addInput:writerInput];
    }
    else
    {
        NSLog (@"can't add asset writer input... die!");
        return NO;
    }
    [writerInput setExpectsMediaDataInRealTime:NO];
    [writer startWriting];
    [writer startSessionAtSourceTime:kCMTimeZero];
    [reader startReading];

    __block UInt64 convertedByteCount = 0;
    __block BOOL returnValue;
    __block CMSampleBufferRef nextBuffer;

    dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);

    [writerInput requestMediaDataWhenReadyOnQueue:mediaInputQueue usingBlock:^{

        // NSLog(@"Asset Writer ready : %d", writerInput.readyForMoreMediaData);

        while (writerInput.readyForMoreMediaData)
        {
            nextBuffer = [readerOutput copyNextSampleBuffer];

            if (nextBuffer)
            {
                [writerInput appendSampleBuffer: nextBuffer];
                convertedByteCount += CMSampleBufferGetTotalSampleSize (nextBuffer);
                //NSNumber *convertedByteCountNumber = [NSNumber numberWithLong:convertedByteCount];
                //NSLog (@"writing");
                CFRelease(nextBuffer);
            }
            else
            {
                [writerInput markAsFinished];

                [writer finishWritingWithCompletionHandler:^{

                    if (AVAssetWriterStatusCompleted == writer.status)
                    {
                        NSLog(@"Writer completed");
                        returnValue = YES; //I NEED TO RETURN SOMETHING FROM HERE AFTER WRITING COMPLETED

                        dispatch_async(mediaInputQueue, ^{
                            dispatch_async(dispatch_get_main_queue(), ^{
                                // add this to the main queue as the last item in my serial queue
                                // when I get to this point I know everything in my queue has been run

                                NSDictionary *outputFileAttributes = [[NSFileManager defaultManager]
                                                                      attributesOfItemAtPath:filePath
                                                                      error:nil];
                                NSLog (@"done. file size is %lld",
                                       [outputFileAttributes fileSize]);
                            });
                        });
                    }
                    else if (AVAssetWriterStatusFailed == writer.status)
                    {
                        [writer cancelWriting];
                        [reader cancelReading];
                        NSLog(@"Writer failed");
                        return;
                    }
                    else
                    {
                        NSLog(@"Export Session Status: %d", writer.status);
                    }
                }];
                break;
            }
        }
    }];
    tracks = nil;
    writer = nil;
    writerInput = nil;
    reader = nil;
    readerOutput=nil;
    mediaInputQueue = nil;
      return returnValue;
    //return YES;
}
4

2 回答 2

2

您的方法exportAudio:toFilePath:实际上是一种异步方法,需要进行一些修复才能成为正确的异步方法。

首先,您应该提供一个完成处理程序,以便向调用站点发出底层任务已完成的信号:

- (void)exportAudio:(AVAsset *)avAsset 
         toFilePath:(NSString *)filePath 
         completion:(completion_t)completionHandler;

请注意,该方法的结果是通过完成处理程序传递的,其签名可能如下:

typedef void (^completion_t)(id result);

其中参数结果是方法的最终结果。在方法中设置各种对象时,当出现任何问题时,您应该始终返回一个NSError对象——即使该方法可能会立即返回指示错误的结果。

接下来,如果您查看文档,您可以阅读:

requestMediaDataWhenReadyOnQueue:usingBlock:

- (void)requestMediaDataWhenReadyOnQueue:(dispatch_queue_t)queue 
                              usingBlock:(void (^)(void))block

讨论

该块应该将媒体数据附加到输入,直到输入的 readyForMoreMediaData 属性变为 NO 或直到没有更多的媒体数据可供提供(此时它可以选择使用 markAsFinished 将输入标记为已完成)。然后该块应该退出。块退出后,如果输入没有被标记为完成,一旦输入处理了它收到的媒体数据并准备好再次接收更多媒体数据,它将再次调用块以获得更多。

您现在应该非常确定您的任务何时真正完成。您在传递给方法的块中确定一点requestMediaDataWhenReadyOnQueue:usingBlock:

任务完成后,您调用方法中提供的完成处理程序completionHandlerexportAudio:toFilePath:completion:

当然,您需要修复您的实现,例如让方法以

    tracks = nil;
    writer = nil;
    writerInput = nil;
    reader = nil;
    readerOutput=nil;
    mediaInputQueue = nil;
      return returnValue;
    //return YES;
}

毫无意义。当异步任务实际完成时,进行清理并返回结果。除非在设置过程中发生错误,否则您需要在传递给方法的块中确定这一点requestMediaDataWhenReadyOnQueue:usingBlock:

在任何情况下,为了将结果信号通知调用站点调用完成处理程序completionHandler并传递一个结果对象,例如,如果它成功了保存它的URL,否则是一个NSError对象。

现在,由于我们的方法startUploadSongAnalysis调用了一个异步方法,这个方法也不可避免地变成了异步的!

如果我正确理解了您的原始代码,那么您将递归调用它以处理大量资产。为了正确实现这一点,您需要一些修复,如下所示。生成的“构造”不是递归方法,而是迭代调用异步方法(“异步循环”)。

您可能会或可能不会提供完成处理程序 - 与上述相同。这取决于您 - 但我会推荐它,知道何时处理所有资产不会有什么坏处。它可能如下所示:

-(void)startUploadSongAnalysisWithCompletion:(completion_t)completionHandler
{
    [self updateProgressYForID3NForUpload:NO];

    // *** check for break condition: ***
    if ([self.uploadWorkingAray count]>=1)
    {
        ... stuff

        //export low bitrate song to cache
        [self exportAudio:[AVAsset assetWithURL:songU] 
               toFilePath:savedPath
               completion:^(id urlOrError)
         {
             if ([urlOrError isKindOfClass[NSError class]]) {

                 // Error occurred:

                 NSLog(@"Error: %@", urlOrError);
                 // There are two alternatives to proceed: 
                 // A) Ignore or remember the error and proceed with the next asset.
                 //    In this case, it would be best to have a result array 
                 //    containing all the results. Then, invoke   
                 //    startUploadSongAnalysisWithCompletion: in order to proceed 
                 //    with the next asset.
                 // 
                 // B) Stop with error.
                 //    Don't call startUploadSongAnalysisWithCompletion: but
                 //    instead invoke the completion handler passing it the error. 

                 // A:
                 // possibly dispatch to a sync queue or the main thread!
                 [self.uploadWorkingAray removeObjectAtIndex:0];
                 [self.songNotFoundArray addObject:songVar];

                 // *** next song: ***
                 [self startUploadSongAnalysisWithCompletion:completionHandler];
             }
             else {
                 // Success:
                 // *** next song: ***
                 NSURL* url = urlOrError;
                 [self startUploadSongAnalysisWithCompletion:completionHandler];
             }
         }];
    }
    else //uploadWorkingAray empty
    {
        NSLog(@"save changes");
        [[VPPCoreData sharedInstance] saveAllChanges];

        // *** signal completion ***
        if (completionHandler) {
            completionHandler(@"OK");
        }
    }
}
于 2013-12-20T14:22:04.317 回答
1

我不确定,但是您不能向以下方法发送调用吗

dispatch_async(mediaInputQueue, ^{
                        dispatch_async(dispatch_get_main_queue(), ^{
                            // add this to the main queue as the last item in my serial queue
                            // when I get to this point I know everything in my queue has been run

                            NSDictionary *outputFileAttributes = [[NSFileManager defaultManager]
                                                                  attributesOfItemAtPath:filePath
                                                                  error:nil];
                            NSLog (@"done. file size is %lld",
                                   [outputFileAttributes fileSize]);

                            //calling the following method after completing the queue
                            [self printMe];

                        });
                    });

-(void)printMe{
NSLog(@"queue complete...");

//Do the next job, may be the following task !!!

if ([self exportAudio:[AVAsset assetWithURL:songU] toFilePath:savedPath]) // HERE IS THE PROBLEM, this return true even the writing is not completed cos when i upload to my web server, it will say song file corrupted
    {
       // [self performSelector:@selector(sendSongForUpload:) withObject:subStringPath afterDelay:1];
        [self sendRequest:2 andPath:subStringPath andSongDBItem:songVar];
    }
    else
    {
        NSLog(@"song too short, skipped");
        [self.uploadWorkingAray removeObjectAtIndex:0];
        [self.songNotFoundArray addObject:songVar];
        [self startUploadSongAnalysis];
    }
}
于 2013-12-17T15:28:50.170 回答