版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.09.01 |
前言
AVFoundation
框架是ios中很重要的框架,所有与视频音频相关的软硬件控制都在这个框架里面,接下来这几篇就主要对这个框架进行介绍和讲解。感兴趣的可以看我上几篇。
1. AVFoundation框架解析(一)—— 基本概览
2. AVFoundation框架解析(二)—— 实现视频预览录制保存到相册
3. AVFoundation框架解析(三)—— 几个关键问题之关于框架的深度概括
4. AVFoundation框架解析(四)—— 几个关键问题之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 几个关键问题之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 视频音频的合成(一)
7. AVFoundation框架解析(七)—— 视频组合和音频混合调试
8. AVFoundation框架解析(八)—— 优化用户的播放体验
9. AVFoundation框架解析(九)—— AVFoundation的变化(一)
10. AVFoundation框架解析(十)—— AVFoundation的变化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的变化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的变化(四)
13. AVFoundation框架解析(十三)—— 构建基本播放应用程序
14. AVFoundation框架解析(十四)—— VAssetWriter和AVAssetReader的Timecode支持(一)
AVAssetReader Reading Timecode
AVAssetReader
对象用于获取资产的媒体数据。 读取存储在时间码轨道中的时间码媒体样本以与用于使用AVAssetReader
读取任何其他媒体(如音频或视频媒体)相同的方式执行。
将AVAssetReader对象与要读取的资产分配后,为时间码轨道创建一个AVAssetReaderTrackOutput
,然后调用 - (BOOL)startReading
来准备读取器从资产读取样本缓冲区。 然后将- (CMSampleBufferRef)copyNextSampleBuffer
方法发送到轨道输出对象以接收时间码采样。
包含时间码示例的返回的CMSampleBufferRef
可以被解释为应用程序所需的,例如,返回的帧号可以转换为CVSMPTETime
表示。 有关实用程序功能,请参阅本文档的“时间码实用程序函数”部分,该功能允许您执行kCMTimeCodeFormatType_TimeCode32
时间码样本格式类型的转换。 检索描述时间码示例调用CMSampleBufferGetFormatDescription
的格式详细信息的格式说明。
下面代码展示了如何为时间码媒体轨道创建AVAssetReader
对象和AVAssetReaderTrackOutput
对象。
...
// Create asset reader
assetReader = [[AVAssetReader alloc] initWithAsset:localAsset error:&localError];
success = (assetReader != nil);
// Create asset reader output for the first timecode track of the asset
if (success) {
AVAssetTrack *timecodeTrack = nil;
// Grab first timecode track, if the asset has them
NSArray *timecodeTracks = [localAsset tracksWithMediaType:AVMediaTypeTimecode];
if ([timecodeTracks count] > 0)
timecodeTrack = [timecodeTracks objectAtIndex:0];
if (timecodeTrack) {
timecodeOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:timecodeTrack outputSettings:nil];
[assetReader addOutput:timecodeOutput];
} else {
NSLog(@"%@ has no timecode tracks", localAsset);
}
}
...
下面提供了两种方法。 第一个演示如何从AVAssetReaderTrackOutput
读取采样缓冲区,第二个演示如何检索采样数据并进行解释。 在这种情况下,从本文档的Timecode Utility
功能部分调用其中一个时间码实用程序函数,将采样数据转换为CVSMPTETime
,然后简单地输出值。
// Read a Timecode Sample Buffer and print out the CVSMPTETime.
- (BOOL)startReadingAndPrintingOutputReturningError:(NSError **)outError
{
BOOL success = YES;
NSError *localError = nil;
// Instruct the asset reader to get ready to do work
success = [assetReader startReading];
if (!success) {
localError = [assetReader error];
} else {
CMSampleBufferRef currentSampleBuffer = NULL;
while ((currentSampleBuffer = [timecodeOutput copyNextSampleBuffer])) {
[self outputTimecodeDescriptionForSampleBuffer:currentSampleBuffer];
}
if (currentSampleBuffer) {
CFRelease(currentSampleBuffer);
}
}
if (!success && outError)
*outError = localError;
return success;
}
- (void)outputTimecodeDescriptionForSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
if (blockBuffer && formatDescription) {
size_t length = 0;
size_t totalLength = 0;
char *rawData = NULL;
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData);
if (status != kCMBlockBufferNoErr) {
NSLog(@"Could not get data from block buffer");
}
else {
CMMediaType type = CMFormatDescriptionGetMediaSubType(formatDescription);
if (type == kCMTimeCodeFormatType_TimeCode32) {
int32_t *frameNumberRead = (int32_t *)rawData;
CVSMPTETime timecode = timecodeForFrameNumber32UsingFormatDescription(*frameNumberRead, formatDescription);
BOOL dropFrame = CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_DropFrame;
char separator = dropFrame ? ',' : '.';
NSLog(@"%@",[NSString stringWithFormat:@"HH:MM:SS%cFF => %02d:%02d:%02d%c%02d (frame number: %d)", separator, timecode.hours, timecode.minutes, timecode.seconds, separator, timecode.frames, (int)Endian32_Swap(*frameNumberRead)]);
}
}
}
}
时间码实用函数
下面代码提供了实用程序函数,演示了如何将CVSMPTETime
转换为帧编号,并将帧编号转换为kCMTimeCodeFormatType_TimeCode32
时间代码媒体样本格式的CVSMPTETime
。
enum {
tcNegativeFlag = 0x80 /* negative bit is in minutes */
};
//CVSMPTETime to Frame Number (kCMTimeCodeFormatType_TimeCode32 Media Sample)
int32_t frameNumber32ForTimecodeUsingFormatDescription(CVSMPTETime timecode, CMTimeCodeFormatDescriptionRef formatDescription)
{
int32_t frameNumber = 0;
if (CMTimeCodeFormatDescriptionGetFormatType(formatDescription) == kCMTimeCodeFormatType_TimeCode32) {
int32_t frameQuanta = CMTimeCodeFormatDescriptionGetFrameQuanta(formatDescription);
frameNumber = timecode.frames;
frameNumber += timecode.seconds * frameQuanta;
frameNumber += (timecode.minutes & ~tcNegativeFlag) * frameQuanta * 60;
frameNumber += timecode.hours * frameQuanta * 60 * 60;
int32_t fpm = frameQuanta * 60;
if (CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_DropFrame) {
int32_t fpm10 = fpm * 10;
int32_t num10s = frameNumber / fpm10;
int32_t frameAdjust = -num10s*(9*2);
int32_t numFramesLeft = frameNumber % fpm10;
if (numFramesLeft > 1) {
int32_t num1s = numFramesLeft / fpm;
if (num1s > 0) {
frameAdjust -= (num1s-1)*2;
numFramesLeft = numFramesLeft % fpm;
if (numFramesLeft > 1)
frameAdjust -= 2;
else
frameAdjust -= (numFramesLeft+1);
}
}
frameNumber += frameAdjust;
}
if (timecode.minutes & tcNegativeFlag) {
frameNumber = -frameNumber;
}
}
return EndianS32_NtoB(frameNumber);
}
// Frame Number (kCMTimeCodeFormatType_TimeCode32 Media Sample) to CVSMPTETime
CVSMPTETime timecodeForFrameNumber32UsingFormatDescription(int32_t frameNumber, CMTimeCodeFormatDescriptionRef formatDescription)
{
CVSMPTETime timecode = {0};
if (CMTimeCodeFormatDescriptionGetFormatType(formatDescription) == kCMTimeCodeFormatType_TimeCode32) {
frameNumber = EndianS32_BtoN(frameNumber);
short fps = CMTimeCodeFormatDescriptionGetFrameQuanta(formatDescription);
BOOL neg = FALSE;
if (frameNumber < 0) {
neg = TRUE;
frameNumber = -frameNumber;
}
if (CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_DropFrame) {
int32_t fpm = fps*60 - 2;
int32_t fpm10 = fps*10*60 - 9*2;
int32_t num10s = frameNumber / fpm10;
int32_t frameAdjust = num10s*(9*2);
int32_t numFramesLeft = frameNumber % fpm10;
if (numFramesLeft >= fps*60) {
numFramesLeft -= fps*60;
int32_t num1s = numFramesLeft / fpm;
frameAdjust += (num1s+1)*2;
}
frameNumber += frameAdjust;
}
timecode.frames = frameNumber % fps;
frameNumber /= fps;
timecode.seconds = frameNumber % 60;
frameNumber /= 60;
timecode.minutes = frameNumber % 60;
frameNumber /= 60;
if (CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_24HourMax) {
frameNumber %= 24;
if (neg && !(CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_NegTimesOK)) {
neg = FALSE;
frameNumber = 23 - frameNumber;
}
}
timecode.hours = frameNumber;
if (neg) {
timecode.minutes |= tcNegativeFlag;
}
timecode.flags = kCVSMPTETimeValid;
}
return timecode;
}
关于TimeCode64格式类型
在构建基于AVFoundation
的媒体应用程序时,建议使用kCMTimeCodeFormatType_TimeCode64('tc64')
格式,而使用本文档中讨论的kCMTimeCodeFormatType_TimeCode32
格式应被视为具有与旧版基于QuickTime
的媒体应用程序具有特定互操作性要求的应用程序的解决方案, 支持'tmcd'时间码采样格式。
kCMTimeCodeFormatType_TimeCode64
格式的媒体采样存储为Big-Endian SInt64
。
注意:'tc64'
时间采样码数据格式。
CMTimeCodeFormatType_TimeCode64 ('tc64') Timecode Sample Data Format.
The timecode media sample data format is a big-endian signed 64-bit integer representing a frame number that is typically converted to and from SMPTE timecodes representing hours, minutes, seconds, and frames, according to information carried in the format description.
Converting to and from the frame number stored as media sample data and a CVSMPTETime structure is performed using simple modular arithmetic with the expected adjustments for drop frame timecode performed using information in the format description such as the frame quanta and the drop frame flag.
The frame number value may be interpreted into a timecode value as follows:
Hours
A 16-bit signed integer that indicates the starting number of hours.
Minutes
A 16-bit signed integer that contains the starting number of minutes.
Seconds
A 16-bit signed integer indicating the starting number of seconds.
Frames
A 16-bit signed integer that specifies the starting number of frames. This field’s value cannot exceed the value of the frame quanta value in the timecode format description.
后记
未完,待续~~~