使用AVAssetReader从远程资源读取(流式传输)

30

我的主要目标是从服务器流式传输视频,并在流式传输时逐帧截取它(以便OpenGL可以使用)。为此,我使用了我在互联网上找到的这段代码(据我回忆它来自苹果的GLVideoFrame示例代码):

NSArray * tracks = [asset tracks];
NSLog(@"%d", tracks.count);

for(AVAssetTrack* track in tracks) {

    NSLog(@"type: %@", [track mediaType]);

    initialFPS = track.nominalFrameRate;
    width = (GLuint)track.naturalSize.width;
    height = (GLuint)track.naturalSize.height;


    NSError * error = nil;

    // _movieReader is a member variable
    @try {
        self._movieReader = [[[AVAssetReader alloc] initWithAsset:asset error:&error] autorelease];
    }
    @catch (NSException *exception) {
        NSLog(@"%@ -- %@", [exception name], [exception reason]);
        NSLog(@"skipping track");

        continue;
    }


    if (error)
    {
        NSLog(@"CODE:%d\nDOMAIN:%@\nDESCRIPTION:%@\nFAILURE_REASON:%@", [error code], [error domain], error.localizedDescription, [error localizedFailureReason]);                                          
        continue;
    }

    NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey;
    NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA];
    NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; 
    [_movieReader addOutput:[AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track
                                                                       outputSettings:videoSettings]];
    [_movieReader startReading];
    [self performSelectorOnMainThread:@selector(frameStarter) withObject:nil waitUntilDone:NO];
}

但是我总是在[[AVAssetReader alloc] initWithAsset:error:]处收到这个异常。

NSInvalidArgumentException -- *** -[AVAssetReader initWithAsset:error:] Cannot initialize an instance of AVAssetReader with an asset at non-local URL 'http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8'

因此,我的两个问题是:

  1. 这个异常是否真的告诉我 AVAssetReader 必须使用本地 URL?它能像其他 AVFoundation 类一样用于流式传输吗?
  2. 如果 AVFoundation 方案行不通,有什么其他建议可以同时流式传输视频并分割其帧?

非常感谢您的帮助。


1
这里有一个类似的问题。但是在我的情况下,AVAssetReader不会初始化,也不会抛出异常,而是会抛出“未知错误”(AVFoundationErrorDomain -11800)。我还尝试过获取视频轨道,将其馈送到新的AVMutableComposition和相关的AVMutableCompositionTrack中,然后尝试从其中创建AVAssetReader。但那也行不通。编辑:顺便说一句,这种方法对于本地mp4/mov文件是有效的。 - Dev Kanchen
iOS中有一些新的API可以帮助您实现最终目标。请参见https://dev59.com/g2cs5IYBdhLWcg3w437H#12500409。 - Matt Wilding
2个回答

38

AVFoundation似乎在本地和非本地文件之间没有太大区别,而是根据使用的文件或协议类型进行区分。使用mp4 / mov与使用通过m3u8的HTTP Live流媒体协议之间存在非常明显的区别,但是使用本地或远程mp4的差异有点模糊。

以下是详细说明:

a)如果您的“远程”资产是M3U8(即,您正在使用HTTP“实时”流),则完全不可能。无论M3U8是在本地文件系统还是在远程服务器上,由于多种原因,AVAssetReader和所有与AVAsset相关的功能都不起作用。但是,AVPlayer,AVPlayerItem等将正常工作。

b)如果它是MP4 / MOV,则需要进行进一步调查。本地MP4 / MOV可以完美运行。而对于远程MP4 / MOV,我能够创建(或从AVPlayerItem或AVPlayer或AVAssetTracks中检索)具有可初始化AVAssetReader成功的AVURLAsset(稍后我也会详细介绍“有时”的情况)。但是,对于远程MP4,copyNextSampleBuffer始终返回nil。由于在调用copyNextSampleBuffer之前有几件事情一直在进行,我并不确定是否:

i)在其他所有步骤成功后,copyNextSampleBuffer不适用于远程mp4是有意或预期的功能。

ii)'其他步骤'似乎对远程MP4的工作仅是苹果实现的偶然事件,并且当我们触及copyNextSampleBuffer时,这种不兼容性正在显现..............这些'其他步骤'是什么,我会很快详细介绍。

iii)在尝试为远程MP4调用copyNextSampleBuffer时我做错了些什么。

所以@Paula,您可以尝试进一步研究远程MOV / MP4。

以下是我尝试从视频中捕获一帧的方法:

a)

直接从视频URL创建AVURLAsset。

使用 [asset tracksWithMediaType:AVMediaTypeVideo] 检索视频轨道。

使用视频轨道作为源,准备一个AVAssetReaderTrackOutput。

使用AVURLAsset创建一个AVAssetReader。

将AVAssetReaderTrackOutput添加到AVAssetReader并开始读取。

使用copyNextSampleBuffer获取图像。

b)

从视频URL创建AVPlayerItem,然后从中创建AVPlayer(或直接从URL创建AVPlayer)。

检索AVPlayer的“asset”属性,并使用“loadValuesAsynchronouslyForKeys:”加载其“tracks”。

将类型为AVMediaTypeVideo的轨道分离出来(或者在加载轨道后在资产上调用tracksWithMediaType:),并使用视频轨道创建AVAssetReaderTrackOutput。

使用AVPlayer的“asset”创建AVAssetReader,“startReading”然后使用copyNextSampleBuffer检索图像。

c)

从视频URL创建AVPlayerItem+AVPlayer或AVPlayer。

KVO监视AVPlayerItem的'tracks'属性,一旦轨道加载完成,将类型为AVMediaTypeVideo的AVAssetTracks分离出来。

使用AVPlayerItem / AVPlayer / AVAssetTrack的“asset”属性检索AVAsset。

剩余步骤与方法(b)类似。

d)

从视频URL创建AVPlayerItem+AVPlayer或AVPlayer。

对AVPlayerItem的“tracks”属性进行KVO,一旦轨道加载完成,就将其分离出AVMediaTypeVideo类型的轨道。

创建一个AVMutableComposition,并初始化一个相关联的AVMutableCompositionTrack,类型为AVMediaTypeVideo。

将先前检索到的视频轨道中适当的CMTimeRange插入此AVMutableCompositionTrack中。

类似于(b)和(c),现在创建您的AVAssetReader和AVAssetReaderTrackOutput,但不同之处在于使用AVMutableComposition作为基本AVAsset来初始化AVAssetReader,以及使用AVMutableCompositionTrack作为基本AVAssetTrack用于AVAssetReaderTrackOutput。

'startReading'并使用copyNextSampleBuffer从AVAssetReader获取帧。

附注:我在这里尝试了方法(d),以规避直接从AVPlayerItem或AVPlayer检索的AVAsset无法正常工作的事实。因此,我想从手头已有的AVAssetTracks中创建一个新的AVAsset。必须承认是hacky的,也许是无意义的(如果不是原始AVAsset,哪里还会最终检索到轨道信息!),但无论如何都值得一试。

以下是不同类型文件的结果摘要:

1)本地MOV / MP4-所有4个方法都可以完美运行。

2)远程MOV / MP4-方法(b)到(d)中正确检索资产和轨道,AVAssetReader也初始化了,但是copyNextSampleBuffer始终返回nil。在(a)的情况下,创建AVAssetReader本身会失败,并显示“未知错误”NSOSStatusErrorDomain-12407。

3)本地M3U8(通过应用程序/本地HTTP服务器访问)-尝试以任何形式获取通过M3U8流式传输的文件的AVURLAsset / AVAsset都是愚蠢的行为,因此方法(a),(b)和(c)都失败了。

(a)情况下,资产根本没有创建,AVURLAsset的initWithURL:调用失败,并出现“未知错误”AVFoundationErrorDomain -11800。

在(b)和(c)情况下,从AVPlayer/AVPlayerItem或AVAssetTracks检索AVURLAsset将返回某些对象,但是在其上访问“tracks”属性始终返回空数组。

在(d)情况下,我能够成功检索和隔离视频轨道,但是在尝试将源轨道的CMTimeRange插入到AVMutableCompositionTrack时失败,出现“未知错误”NSOSStatusErrorDomain -12780。

4)远程M3U8与本地M3U8的行为完全相同。

我并不完全了解为什么存在这些差异,或者为什么苹果无法缓解这些差异。但是这就是事实。


0

您可以在AVMutableCompositionTrack上获取远程文件

AVURLAsset* soundTrackAsset = [[AVURLAsset alloc]initWithURL:[NSURL URLWithString:@"http://www.yoururl.com/yourfile.mp3"] options:nil];

AVMutableCompositionTrack *compositionAudioSoundTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[compositionAudioSoundTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset.duration) 
                               ofTrack:[[soundTrackAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] 
                                atTime:kCMTimeZero error:nil];

然而,这种方法在像MP4这样具有更高压缩率的文件上效果并不好。

1
我需要访问现场m3u8 URL的音频和视频轨道数据。你能帮我吗?我们可以获取现场m3u8 URL的AVAssets吗? - Punita

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