【问题标题】:Music convert and how to know if writing are completed音乐转换以及如何知道写作是否完成
【发布时间】:2013-12-13 02:07:19
【问题描述】:

我不得不将大文件大小的歌曲从 iTunes 库转换为较小的 8K 歌曲文件。

当我进行异步转换时,即使没有完成对 doc 文件夹的写入,布尔值也始终返回 true。目前我在再次调用该函数之前使用了 10 秒的延迟,它在 iPhone 5s 的过渡期间工作正常,但我想迎合速度较慢的设备。

请给我一些关于我的代码的指针/建议。

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

【问题讨论】:

    标签: ios objective-c nsdocument avassetwriter avassetexportsession


    【解决方案1】:

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

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

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

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

    typedef void (^completion_t)(id result);
    

    其中参数result 是方法的最终结果。当在方法中设置各种对象时任何事情出错时,您应该总是返回一个NSError对象 - 即使该方法可能返回一个立即 表示错误的结果。

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

    requestMediaDataWhenReadyOnQueue:usingBlock:

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

    讨论

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

    你现在应该很确定什么时候你的任务真的完成了。您在传递给方法requestMediaDataWhenReadyOnQueue:usingBlock: 的块中确定

    当任务完成时,您调用提供的完成处理程序completionHandler 方法exportAudio: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");
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我不确定,但你能不能像下面这样调用方法

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-12
        相关资源
        最近更新 更多