原文:AVFoundation Programming Guide
基于时间的视听数据,例如电影文件或视频流在AV Foundation框架中使用AVAsset表示。AV Foundation用来表示时间和媒体的几个低级数据结构,例如样本缓冲区来自Core Media框架。
资源表示
AVAsset是AV Foundation框架的核心类。 它提供了基于时间的视听数据的格式无关抽象,例如电影文件或视频流。 主要关系如图6-1所示。 在许多情况下,您使用其中一个子类:使用composition子类创建新资源(请参阅Editing),使用AVURLAsset从给定URL创建新的资源实例(包括MPMedia框架或Asset Library框架的资源 - 请参阅Using Assets)。
Figure 6-1 AVAsset provides an abstraction of time-based audiovisual data
资源包含在一起呈现或处理的轨道的集合,每个轨道的媒体类型,包括(但不限于)音频,视频,文本,隐藏字幕和字幕。 资源对象提供关于整个资源的信息,例如其持续时间或标题,以及呈现的提示,例如其自然大小。 资源还可以具有由AVMetadataItem的实例表示的元数据。
轨道由AVAssetTrack的实例表示,如图6-2所示。 在通常的简单情况下,一个轨道表示音频,另一个表示视频; 在复杂的组合中,可能存在多个重叠的音频和视频轨道。
Figure 6-2 AVAssetTrack
轨道具有许多属性,例如其类型(视频或音频),视觉和/或听觉特征,元数据和时间轴。 轨道还具有一系列格式描述。 该数组包含CMFormatDescription对象(请参阅CMFormatDescriptionRef),每个对象描述该轨道引用的媒体样本的格式。 一个轨道如果包含统一的媒体(例如,全部使用相同设置编码)那么这个数组的计数为1。
轨道本身可以分为段,由AVAssetTrackSegment的实例表示。 段是从源到资源轨道时间线的时间映射。
时间表示
AV Foundation中的时间由Core Media框架的原始结构表示。
CMTime代表一段时间
CMTime是一个C结构,它将时间表示为有理数,分子(一个int64_t的值)和分母(一个int32_t时间标度)。 在概念上,时间标度指在每一秒中所占有的比例。 因此,如果时间标度是4,每个单位代表四分之一秒; 如果时间刻度为10,则每个单位表示十分之一秒,依此类推。 您经常使用600的时间尺度,因为这是几种常用的帧速率的倍数:24 fps的电影,30 fps的NTSC(用于北美和日本的电视)和25 fps的PAL(用于电视 欧洲)。 使用600的时间刻度,您可以准确地表示这些系统中的任意数量的帧。
除了简单的时间值之外,CMTime结构可以表示非数值值:+无穷大,-infinity和无限期。 它也可以指示时间是否在某一点被舍入,并且它保持一个epoch(纪元)数字。
使用CMTime
您可以使用CMTimeMake或相关函数如CMTimeMakeWithSeconds(允许您使用float值创建一个时间并指定首选的时间标度)来创建一个时间。 有一些基于时间的算法和比较时间的方法,如以下示例所示:
CMTime time1 = CMTimeMake(200, 2); // 200 half-seconds
CMTime time2 = CMTimeMake(400, 4); // 400 quarter-seconds
// time1 and time2 both represent 100 seconds, but using different timescales.
if (CMTimeCompare(time1, time2) == 0) {
NSLog(@"time1 and time2 are the same");
}
Float64 float64Seconds = 200.0 / 3;
CMTime time3 = CMTimeMakeWithSeconds(float64Seconds , 3); // 66.66... third-seconds
time3 = CMTimeMultiply(time3, 3);
// time3 now represents 200 seconds; next subtract time1 (100 seconds).
time3 = CMTimeSubtract(time3, time1);
CMTimeShow(time3);
if (CMTIME_COMPARE_INLINE(time2, ==, time3)) {
NSLog(@"time2 and time3 are the same");
}
可用函数的列表可以参阅 CMTime Reference.
CMTime的特殊值
Core Media提供了一些常量:
kCMTimeZero,kCMTimeInvalid,kCMTimePositiveInfinity和kCMTimeNegativeInfinity。 CMTime结构可以有多种方式,例如,表示无效的时间。 要测试CMTime是否有效或非数值,您应该使用适当的宏,如CMTIME_IS_INVALID,CMTIME_IS_POSITIVE_INFINITY或CMTIME_IS_INDEFINITE。
CMTime myTime = <#Get a CMTime#>;
if (CMTIME_IS_INVALID(myTime)) {
// Perhaps treat this as an error; display a suitable alert to the user.
}
您不应该将任意CMTime结构的值与kCMTimeInvalid进行比较。
将CMTime表示为对象
如果需要在Core Foundation容器中使用CMTime结构,则可以分别使用CMTimeCopyAsDictionary和CMTimeMakeFromDictionary函数将CMTime结构转换为CFDictionary opaque类型(参见CFDictionaryRef)。 您还可以使用CMTimeCopyDescription函数获取CMTime结构的字符串表示形式。
Epochs(纪元)
CMTime结构的epoch通常设置为0,但您可以使用它来区分不相关的时间轴。 例如,可以在循环时通过每个周期递增epoch,以区分循环0中的时间N和循环1中的时间N.
CMTimeRange表示时间范围
CMTimeRange是一个具有开始时间和持续时间的C结构,均表示为CMTime结构。 时间范围不包括开始时间加上持续时间的时间。
您使用CMTimeRangeMake或CMTimeRangeFromTimeToTime创建一个时间范围。 对CMTime的epochs有一些约束:
- CMTimeRange结构不能跨越不同的epochs。
- 表示时间戳的CMTime结构中的epoch可能不为零,但您只能在起始字段具有相同epoch的范围上执行范围操作(例如CMTimeRangeGetUnion)。
- 表示持续时间的CMTime结构中的epoch应始终为0,并且该值必须为非负数。
使用时间范围
Core Media提供了一些函数,可以用于确定时间范围是否包含给定时间或其他时间范围,确定两个时间范围是否相等,计算时间范围的联合和交集,如CMTimeRangeContainsTime,CMTimeRangeEqual,CMTimeRangeContainsTimeRange和CMTimeRangeGetUnion。
鉴于时间范围不包括开始时间加上持续时间的时间,以下表达式总是为false:
CMTimeRangeContainsTime(range, CMTimeRangeGetEnd(range))
其他的一些可用函数, 参阅 CMTimeRange Reference.
CMTimeRange的特殊值
Core Media分别为零长度范围和无效范围提供了常量kCMTimeRangeZero和kCMTimeRangeInvalid。 有许多方式可以使CMTimeRange结构无效,为零或不定式(如果其中一个CMTime结构是不确定的)。如果您需要测试CMTimeRange结构是否有效,零或不确定,您应该使用 一个适当的宏 : CMTIMERANGE_IS_VALID ,CMTIMERANGE_IS_INVALID ,CMTIMERANGE_IS_EMPTY , 或CMTIMERANGE_IS_EMPTY。
CMTimeRange myTimeRange = <#Get a CMTimeRange#>;
if (CMTIMERANGE_IS_EMPTY(myTimeRange)) {
// The time range is zero.
}
您不应将任意CMTimeRange结构的值与kCMTimeRangeInvalid进行比较。
将CMTimeRange结构表示为对象
如果需要在Core Foundation容器中使用CMTimeRange结构,则可以分别使用CMTimeRangeCopyAsDictionary和CMTimeRangeMakeFromDictionary将CMTimeRange结构转换为CFDictionary opaque类型(请参阅CFDictionaryRef)。 您还可以使用CMTimeRangeCopyDescription函数获取CMTime结构的字符串表示形式。
媒体表示
视频数据及其关联的元数据在AV Foundation中由Core Media框架中的不透明对象表示。 Core Media使用CMSampleBuffer表示视频数据(请参阅CMSampleBufferRef)。 CMSampleBuffer是一个Core Foundation风格的不透明类型; 一个实例包含视频数据帧作为核心视频像素缓冲区的样本缓冲区(参见CVPixelBufferRef)。 您可以使用CMSampleBufferGetImageBuffer从样本缓冲区访问像素缓冲区:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(<#A CMSampleBuffer#>);
从像素缓冲区,您可以访问实际的视频数据。 有关示例,请参阅 Converting CMSampleBuffer to a UIImage Object。
除了视频数据之外,您还可以获取视频帧的其他方面:
时间信息 您可以分别使用CMSampleBufferGetPresentationTimeStamp和CMSampleBufferGetDecodeTimeStamp获得原始表示时间和解码时间的时间戳。
格式化信息。 格式信息封装在CMFormatDescription对象中(请参阅CMFormatDescriptionRef)。 从格式描述中,您可以分别使用CMVideoFormatDescriptionGetCodecType和CMVideoFormatDescriptionGetDimensions获取像素类型和视频尺寸。
元数据。 元数据作为附件存储在字典中。 您使用CMGetAttachment获取字典:
CMSampleBufferRef sampleBuffer = <#Get a sample buffer#>;
CFDictionaryRef metadataDictionary =
CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary", NULL);
if (metadataDictionary) {
// Do something with the metadata.
}
将CMSampleBuffer转换为UIImage对象
以下代码显示如何将CMSampleBuffer转换为UIImage对象。 使用前,您应仔细考虑您的要求。 执行转换是比较昂贵的操作。 例如,从每秒钟拍摄的视频数据帧中创建静止图像是适当的。 您不应该使用它作为一种手段来实时地处理来自捕获设备的每一帧视频。
// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}