有人知道如何正确实现AVAssetResourceLoaderDelegate方法吗?

12

我正在尝试让AVFoundation从自定义URL读取内容。自定义URL已经可用。下面的代码会创建一个包含电影文件的NSData:

NSData* movieData = [NSData dataWithContentsOfURL:@"memory://video"];

我使用以下代码设置了一个AVAssetResourceLoader对象:

NSURL* url = [NSURL URLWithString:@"memory://video"];
AVURLAsset* asset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVAssetResourceLoader* loader = [asset resourceLoader];
[loader setDelegate:self queue:mDispatchQueue];

调度队列是并发的。
然后我尝试从电影中提取第一帧:
AVAssetImageGenerator* imageGen = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
CMTime time = CMTimeMakeWithSeconds(0, 600);
NSError* error = nil;
CMTime actualTime;
CGImageRef image = [imageGen copyCGImageAtTime:time
                                    actualTime:&actualTime
                                         error:&error];
if (error) NSLog(@"%@", error);

但是当我运行这段代码时,会得到以下结果:
2013-02-21 10:02:22.197 VideoPlayer[501:907] Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo=0x1f863090 {NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x1e575a90 "The operation couldn’t be completed. (OSStatus error 268451843.)", NSLocalizedFailureReason=An unknown error occurred (268451843)}

委托方法的实现如下:
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
    NSData* data = [NSData dataWithContentsOfURL:loadingRequest.request.URL];
    [loadingRequest finishLoadingWithResponse:nil data:data redirect:nil];
    return YES;
}

现在,我的问题是,我是否正确实施了该方法?有人知道我做的是否正确吗?
谢谢。
编辑:我完整获取的电影是一部单帧电影。

现在看起来,copyCGImageAtTime:actualTime:error:调用不会等待委托方法完成(委托方法正在调度队列中运行)。因此问题变成了如何确保在进行copyCGImageAtTime:actualTime:error调用之前数据已经加载? - Cthutu
请参阅 http://developer.apple.com/library/mac/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/01_UsingAssets.html 上的“为使用准备资产” - 您的缩略图生成代码应该在完成处理程序块中。 - Aaron Brager
谢谢您的评论,但是当我不需要更改AVAssetResourceLoader对象并且从HTTP或FILE类型URL获取时,我的代码是正确的。我可以获取视频帧。我正在尝试解决的问题是如何从自定义URL获取它。有人告诉我AVAssetResourceLoader是关键,但我在使用它时遇到了麻烦。 - Cthutu
我尝试使用自定义URL解决方案的原因是为了避免写入磁盘。目前我唯一可行的使用硬件H.264解码器的解决方案是抓取一帧,将其包装在mp4包装器中,并将其写入磁盘。这个过程非常慢!!!我正在尝试避免磁盘写入。 - Cthutu
loadValuesAsynchronouslyForKeys:completionHandler: 强制调用委托方法 resourceLoader:shouldWaitForLoadingOfRequestedResource: 完全完成后,再调用 copyCGImage 方法,但是 copyCGImage 方法仍然失败 :( - Cthutu
显示剩余10条评论
4个回答

8
我已经实现了这种方法的工作版本。我花了一些时间才弄清楚。但是,最终的应用程序现在可以正常工作。这表明代码没有问题。
我的应用程序包括一个媒体文件,我不想以未加密的方式在包中发布它。我想动态解密文件(逐块进行)。
该方法必须同时响应内容请求(告诉播放器正在加载什么内容)和数据请求(提供一些数据)。第一次调用该方法时,总会有一个内容请求。然后会有一系列数据请求。
播放器很贪婪。它总是要求整个文件。你没有义务提供那个。它要求整个蛋糕。你可以给它一片蛋糕。
我将数据块交给媒体播放器。通常每次交付1 MB。还有一个特殊情况来处理较小的最终块。块通常按顺序请求。但是您也需要能够处理乱序请求。
- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
    NSURLRequest* request = loadingRequest.request; 
    AVAssetResourceLoadingDataRequest* dataRequest = loadingRequest.dataRequest;
    AVAssetResourceLoadingContentInformationRequest* contentRequest = loadingRequest.contentInformationRequest;

    //handle content request
    if (contentRequest)
    {
        NSError* attributesError;
        NSString* path = request.URL.path;
        _fileURL = request.URL;
        if (_fileHandle == nil)
        {
            _fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
        }

        // fire up the decryption here..
        // for example ... 
        if (_decryptedData == nil)
        {
            _cacheStart = 1000000000;
            _decryptedData = [NSMutableData dataWithLength:BUFFER_LENGTH+16];
            CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [sharedKey cStringUsingEncoding:NSISOLatin1StringEncoding], kCCKeySizeAES256, NULL, &cryptoRef);
        }

        NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&attributesError];

        NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
        _fileSize = [fileSizeNumber longLongValue];

        //provide information about the content
        _mimeType = @"mp3";
        contentRequest.contentType = _mimeType;
        contentRequest.contentLength = _fileSize;
        contentRequest.byteRangeAccessSupported = YES;
    }

    //handle data request
    if (dataRequest)
    {
        //decrypt a block of data (can be any size you want) 
        //code omitted

        NSData* decodedData = [NSData dataWithBytes:outBuffer length:reducedLen];
       [dataRequest  respondWithData:decodedData];
    [loadingRequest finishLoading];
    }


    return YES;
}

嗨,我有类似的实现,但有时在浏览视频时会遇到问题。有时候当我们来回切换时,视频会出现像素化或变成绿色。你是否遇到过类似的问题? - Javal Nanda
我成功让我的应用程序变得稳定了。但是该应用程序并不需要擦洗。我对如何驱动CCCrypto代码有些不确定。 - Glyn Williams

1
使用NSURLComponent并将scheme设置为“enc”,以调用AVAssetResourceLoaderDelegate方法。
let urlComponents = NSURLComponents(url: video_url, resolvingAgainstBaseURL: false)
    urlComponents?.scheme = "enc"
let avAsset = AVURLAsset(url: (urlComponents?.url)!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
    avAsset.resourceLoader.setDelegate(self, queue: DispatchQueue(label: "AVARLDelegateDemo loader"))

1
我刚刚浪费了两个小时尝试做类似的事情。
结果只能在设备上运行,而不能在iOS模拟器上运行!
我猜想模拟器中的AVFoundation与主机Mac的AVFoundation有所关联。不幸的是,这个API在OS X 10.8上不可用(根据WebCore上的一些提交,它将在OS X 10.9上可用),因此目前它不能在模拟器中工作。

我被代理卡住了。我已经实现了它,但出现了“失败”状态。你有什么想法吗? - Geraud.ch
我的代理实现如下:
  • (BOOL)resourceLoader:... { NSData* data = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"sample.m4v" withExtension:@""]]; NSURLResponse* response = [[NSURLResponse alloc]initWithURL:self.url MIMEType:@"video/x-m4v" expectedContentLength:[data length] textEncodingName:nil]; [loadingRequest finishLoadingWithResponse:response data:data redirect:nil]; return YES; }
- Geraud.ch

0

你需要创建一个NSURLResponse对象来传递回去。你正在传递nil。没有它,AVAssetResourceLoader不知道如何处理你交给它的数据(也就是说,它不知道这是什么类型的数据——错误消息、jpeg等)。你还应该真正使用-[NSData dataWithContentsOfURL:options:error:]并在假定成功之前检查错误。


我尝试过了,但它也没有起作用。也许这仍然是我需要的东西。但问题在于,在委托方法完成之前,获取帧时会出现错误。没有任何阻塞委托方法的结果。为了创建响应,我使用:[[NSURLResponse alloc] initWithURL:loadingRequest.request.URL MIMETYPE:@"video/mp4" expetedContentLength:data.length textEncodingName:nil] - Cthutu
我认为问题不在委托方法上(至少不是我必须解决的直接问题),因为它在copyCGImageAtTime:actualTime:error:调用返回之前没有完成。两个线程之间存在同步问题,我不知道如何解决。 - Cthutu
Cthutu,你有找到任何解决方案吗? - Ahmed Ebaid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接