AVFoundation编程指南08-时间和媒体表示
写在前面
喜欢AVFoundation资料的同学可以关注我的专题:《AVFoundation》专辑
也可以关注我的简书账号
正文
基于时间的音视频数据,例如电影文件或视频流,由AVAsset
在AV Foundation
框架中表示。它的结构决定了很多框架的工作原理。 AV Foundation
用于表示时间和媒体(如样本缓冲区)的几个底层数据结构来自Core Media
框架。
Assets的表示
AVAsset是AV Foundation
框架中的核心类。它提供基于时间的视听数据的不依赖格式的抽象,例如电影文件或视频流。主要关系如图6-1
所示。在许多情况下,你使用其子类之一:在创建新asset
时使用composition
子类(请参阅Editing),并使用AVURLAsset从给定URL
的媒体创建新asset
实例(包括来自MPMedia
框架的asset
)或Asset Library framework
- 请参阅Using Assets)。
Asset包含旨在一起呈现或处理的track
集合,每个track
均匀的媒体类型,包括(但不限于)音频,视频,文本,隐藏式字幕和字幕。Asset
对象提供有关整个资源的信息,例如其持续时间或标题,以及演示提示,例如其自然大小。asset
还可以具有metadata
,由AVMetadataItem的实例表示。
轨道由AVAssetTrack的实例表示,如图6-2
所示。在典型的简单情况下,一个track
代表音频组件,另一个track
代表视频组件;在复杂的构图中,可能存在多个重叠的音频和视频track
。
track
具有许多属性,例如其类型(视频或音频),视觉和/或听觉特征(视情况而定),元数据和时间线(以其父asset
表示)。track
还有一系列格式描述。该数组包含CMFormatDescription
对象(请参阅CMFormatDescriptionRef),每个对象都描述了轨道引用的媒体样本的格式。包含统一媒体的track
(例如,所有使用相同设置编码的track
)将提供计数为1
的数组。
track
本身可以被划分为段,由AVAssetTrackSegment的实例表示。段是从源到asset
跟踪时间线的时间映射。
时间的表示
AV Foundation
的时间由Core Media
框架中的原始结构表示。
CMTime表示时间长度
CMTime是一种C
语言的数据结构,它将时间表示为有理数,具有分子(int64_t
值)和分母(int32_t
时间刻度)。从概念上讲,时间刻度指定分子中每个单位占秒的分数。因此,如果时间刻度为4
,则每个单位代表四分之一秒;如果时间刻度为10
,则每个单位代表十分之一秒,依此类推。你经常使用600
的时间刻度,因为这是几种常用帧速率的倍数:电影为24 fps
,NTSC
为30 fps
(北美和日本用于电视),PAL
为25 fps
(用于电视)欧洲)。使用600
的时间刻度,你可以精确地表示这些系统中的任意数量的帧。
除了简单的时间值,CMTime
结构还可以表示非数字值:+
无穷大,无穷大和无限期。它还可以指示时间是否在某个时刻被舍入,并且它保持一个纪元数。
使用CMTime
你可以使用CMTimeMake或其中一个相关函数(如CMTimeMakeWithSeconds)创建时间(允许你使用浮点值创建时间并指定首选时间刻度)。基于时间的算术和比较时间有几个函数,如以下示例所示:
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
,但你可以使用它来区分不相关的时间轴。例如,可以使用呈现循环递增每个周期的时期,以区分循环0
中的时间N
和循环1
中的时间N
。
CMTimeRange表示时间范围
CMTimeRange是一个C语言的数据结构,具有开始时间和持续时间,均表示为CMTime
结构。时间范围不包括开始时间加上持续时间的时间。
您可以使用CMTimeRangeMake或CMTimeRangeFromTimeToTime创建时间范围。 CMTime
epochs
的value
会受到限制:
-
CMTimeRange
结构不能跨越不同的epoch
。 -
表示时间戳的
CMTime
结构中的timestamp
可能是nonzero
,但你只能对起始字段具有相同epoch
的范围执行range
操作(例如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
结构的字符串表示形式。
Media的表示
视频数据及其相关元数据在AV Foundation
中由来自Core Media
框架的opaque
对象表示。Core Media
使用CMSampleBuffer
表示视频数据(请参阅CMSampleBufferRef)。 CMSampleBuffer
是Core Foundation
风格的opaque
类型;实例包含视频数据帧的样本缓冲区作为核心视频像素缓冲区(请参阅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);
}
上一章 | 目录 | 下一章 |
---|