AVFoundation - 媒体的组合和编辑

1.AVFoundation有关资源组合的功能源于AVAsset的子类AVComposition. 一个组合就是将其他几种媒体资源合成一个自定义临时排列, 再将临时排列视为一个可以呈现或处理的独立媒体项目. 就比如AVAsset对象, 组合相当于包含一个或多个给定类型的媒体轨道容器. AVComposition中的轨道都是AVAssetTrack的子类AVCompositionTrack. 一个组合本身有一个或者多个媒体片段组成. AVComposition没有遵循NSCoding协议, 说以不能归档到磁盘, 需要自定义模型来保存这个状态. AVComposition和AVCompositionTrack都是不可变对象, 提供对资源的只读操作. 当创建自己的组合时, 需要使用AVMutableComposition和AVMutableCompositionTrack所提供的可变子类.

2. 时间处理, 由于浮点型和双精度类型的天然不精确性, 无法应用于更多的高级时基媒体开发, 比如一个单一的舍入错误就会导致丢帧或音频丢失. 苹果使用Core Media框架定义CMTime, CMTime实例可以标记特定的时间点或用于表示持续时间.

typedef struct {

    CMTimeValue value;    //64位有符号整形变量(分子)

    CMTimeValue timescale;    //32位有符号整形变量(分母)

    CMTimeFlags flags;    //表示时间的指定状态, 比如数据是否有效, 不确定或是否出现舍入.

    CMTimeEpoch epoch;

} CMTime;

3. CMTime的创建方式

CMTime t1 = CMTimeMake(3, 1);

CMTimeShow(t1);   // --> {3/1 = 3.000}

4. 时间相加减

CMTime t1 = CMTimeMake(5,1);

CMTime t2 = CMTimeMake(3,1);

CMTime result = CMTimeAdd(t1, t2);        //相加

CMTimeShow(result);    // --> {8/1 = 8.000}

result = CMTimeSubstract(t1, t2);            //相减

CMTimeShow(result);    // --> {2/1 = 2.000}        

5. CMTimeRange, 时间范围, 由两个CMTime组成, 第一个值定义时间的起点, 第二个值定义时间的持续时间.

typedef struct {

    CMTime start;

    CMTime duration;

}

CMTime t = CMTimeMake(5, 1);

CMTime t2 = CMTimeMake(10,1);

CMTimeRange timeRange = CMTimeRangeMake(t, t);

CMTimeRangeShow(timeRange);    // --> {{5/1 == 5.000}, {5/1 = 5.000}};

timeRange = CMTimeRangeFromTimeToTime(t, t2); // --> {{5/1 == 5.000}, {5/1 == 5.000}};

6. 时间范围计算.

交叉时间范围

CMTimeRange r1 = CMTimeRangeMake(kCMTimeZero, CMTimeMake(5,1));

CMTimeRange r2 = CMTimeRangeMake(CMTimeMake(2,1), CMTimeMake(5, 1));

CMTimeRange range = CMTimeRangeGEtIntersection(r1, r2); // --> {{2/1=2.000}, {3/1 = 3.000}};

两个时间范围总和

range = CMTimeRangeGetUnion(r1, r2);

CMTimeRangeShow(range);    // --> {{0/1 = 0.000}, {7/1 = 7.000}};

7. 基础方法. 当创建一个资源用于组合时, 应该直接使用URLAssetWithURL:options方法实例化一个AVURLAsset. options参数允许通过传递一个带有一个或多个初始化选项的NSDictionary来自定义资源初始化的方式. 载入mp4的示例

NSURL *url = [[NSBundle mainBundle] URLForResource:@"video", withExtentsion:@"mp4"];

NSDictionary *options = @{AVURLAssetPreciseDurationAndTimingKey: @YES}; //使用AVAsynchronousKeyValueLoading协议载入时可以计算出准确的时长和时间信息. 会造成一些额外开销. 

AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];

NSArray *keys = @[@"tracks", @"duration", @"commonMEtadata"];

[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{

}];

8. 创建组合资源, 当创建组合轨道时, 必须指明所能支持的媒体类型, 并给出一个轨道标识符. 这个标识符在我们之后需要返回轨道时会用到, 不过一般来说都赋给他一个kCMPersistentTrackID_Invalid常量, 意思是将创建一个合适轨道的ID的任务委托给框架, 标识符会以1...n排列.

AVAsset *goldenGateAsset = //prepared golden gate asset

AVAsset *teaGardenAsset = //prepared tea garden asset

AVAsset *soundTrackAsset = //prepared sound track asset

AVMutableComposition *composition = [AVMutableComposition composition];

AVMutableCompositionTrack *videoTack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];        //添加Video轨道

AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];        //添加Audio轨道

9. 将独立的媒体片段添加到组合轨道内

CMTime cursorTime = kCMTimeZero;

CMTime videoDuration = kCMTimeMake(5, 1);

CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoDuration);

AVAssetTrack *assetTrack = [[goldenGateAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];

[videoTrack insertTimeRange:videoTimeRange ofTrack:assetTrack atTime:cursorTime error:nil];

cursorTime = CMTimeAdd(cursorTime, videoDuration);    //add cursor time

assetTrack = [[teaGardenAsset trackWithMediaType:AVMediaTypeVideo] firstObject];

[videoTrack insertTimeRange:videoTimeRange ofTrack:assetTrack atTime:cursorTime error:nil];

cursorTime = kCMTimeZero;    //reset cursor time

CMTime audioDuration = composition.duration;

CMTimeRange audioTimeRange = CMTimeRAngeMake(kCMTimeZero, audioDuration);

assetTrack = [[soundtrackAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];

[audioTrack insertTimeRange:audioTimeRange ofTrack:assetTrack atTime:cursorTime error:nil];

这个组合资源现在与其他AVAsset一样, 可以播放, 导出或处理.

10. 15 Seconds实例应用. (包括视频播放, 读取元数据信息, 图片提取)

//这个接口用来创建一个组合的可播放版本和可导出版本.

@protocol THComposition <NSObject>

- (AVPlayerItem *)makePlayable;

- (AVAssetExportSession *)makeExportable;

@end

//创建一个基础composition

@interface THBasicComposition : NSObject <THComposition>

@property (nonatomic, readonly, strong) AVComposition *composition;

@property (nonatomic, strong) AVAudioMix *audioMix;

+ (instancetype)compositionWithComposition:(AVComposition *)composition;

- (instancetype)initWithComposition:(AVCompostion *)composition;

@end

@implementation THBasicCompostion

- (instancetype)initWithComposition:(AVCompostion *)compostion audioMix:(AVAudioMix *)audioMix {

    self = [super init];

    if (self) {

        _composition = compostion;

        _audioMix = audioMix;

    }

    retrun self;

}

- (AVPlayerItem *)makePlayable {

    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:[self.composition copy]];

    playerItem.audioMix = self.audioMix;

    return  playerItem;

}

- (AVAssetExportSession *)makeExportable {

    NSString *preset = AVAssetExportPresetHigestQuality;

    AVAssetExportSession *session = [AVAssetExportSession exportSessionWithAsset:[self.composition copy] presetName:preset];

    session.audioMix = self.audioMix;

    return session;

}

@end

11. 创建一个组合composition, 对于composition实现, 应用程序内都会有一个相应的THCompositionBuilder负责构建实例. 

@protocol THCompositionBuilder <NSObject>

- (id <THComposition>)buildComposition;    //构建AVComposition以及相关的轨道

@end

@interface THBasicCompositionBuilder : NSObject <THCompositionBuilder>

@property (strong, nonatomic) THTimeline *timeline;

@property (strong, nonatomic) AVMutableComposition *composition;

- (id)initWithTimeline:(THTimeline *)timeline;

@end

@implementation THBasicCompositionBuilder 

- (id)initWithTimeline:(THTimeline *)timeline {

    self = [super init];

    if (self) {

        _timeline = timeline;

    }

    return self;

}

- (id <THComposition>)buildComposition {

    self.composition = [AVMutableComposition composition];

    [self addCompositionTrackOfType:AVMediaTypeVideo withMediaItems:self.timeline.videos];

    [self addCompositionTrackOfType:AVMediaTypeAudio withMediaItems:self.timeline.voiceOvers];    

    [self addCompositionTrackOfType:AVMediaTypeAudio withMediaItems:self.timeline.musicItems];

    return [THBasicComposition compositionWithComposition:self.composition];

}

- (void)addCompositionTrackOfType:(NSString *)mediaType withMediaItems:(NSArray *)mediaItems {

    if (!THIsEmpty(mediaItems)) {

        CMPersistentTrackID trackID = kCMPersistentTrackID_Invalid;

        AVMutableCompositionTrack *compositionTrack = [self.composition addMutableTrackWithMediaType:mediaType preferredTrackID:trackID];

        CMTime cursorTime = kCMTimeZero;

        for (THMediaItem *item in mediaItems) {

            if (CMTIME_COMPARE_INLINE(item.startTimeInTimeline != kCMTimeInvalid)) {

                cursorTime = item.startTimeInTimeline;

            }

            AVAssetTrack *assetTrack = [[item.asset tracksWithMediaType:mediaType]                           firstObject];

            [compositionTrack insertTimeRange:item.timeRange ofTrack:assetTrack atTime:cursorTime error:nil];

            cursorTime = CMTimeAdd (cursorTime, item.timeRange.duration);

        }

    }

}

@end

12. 导出组合

@interface THCompositionExporter : NSObject

@property (nonatomic) BOOL exporting;

@property (nonatomic) BOOL progress;

- (instancetype)initWithComposition:(id<THComposition>)composition;

- (void)beginExport;    //负责实际的导出过程

@end

@interface THCompositionExporter ()

@property (strong, nonatomic) id<THComposition>composition;

@property (strong, nonatomic) AVAssetExportSession *exportSession;

@end

@implementation THCompositionExporter 

- (instancetype)initWithComposition:(id<THComposition>)composition {

    self = [super init];

    if (self) {

        _composition = compostion;

    }

    return self;

}

- (void)beginExport {        //处理导出语句

    self.exportSession = [self.composition makeExportable];

    self.exportSession.outputURL = [self exportURL];

    self.exportSession.outputFileType = AVFileTypeMPEG4;

    [self.exportSesssion exportAsynchronouslyWithCompletionHandler:^{

        dispatch_async(dispatch_get_main_queue(), ^{

            AVAssetExportSessionStatus status = self.exportSession.status;

            if (status == AVAssetExportSessionStatusCompleted) {

                [self writeExportedVideoToAssetsLibraray];

            }else {

                [UIAlertView showAlertWithTitle:@"Export Failed" message:@"The requested export failed."];

            }

        });

    }];

    self.exporting = YES;

    [self monitorExportProgress];

}

- (void)monitorExportProgress {        //监视导出过程

    double delayInSecond = 0.1;

    int64_t delta = (int64_t)delayInSeconds * NSEC_PRE_SEC;

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delta);

    dispatch_after(popTime, dispatch_get_main_queue(), ^{

        AVAssetExportSessionStatus status = self.exportSession.status;

        if (status == AVAssetExportSessionStatusExporting) {

            self.progress = self.exportSession.progress;

            [self monitorExportProgress];            

        }else {

            self.exporting = NO;

        }

    });

    [self monitorExportProgress];

}

- (NSURL *)exportURL {

    NSString *filePath = nil;

    NSUInteger count = 0;

    do {

        filePath = NSTemporaryDirectory();

        NSString *numberString = count > 0 ? [NSString stringWithFormat:@"-%li", cout] : @"";

        NSString fileNameString = [NSString stringWithFormat:@"m-%@.m4v", numberString];

        filePath = [filePath stringByAppendingPathComponent:fileNameString];

        count++;

    }while ([[NSFileManager defaultManager] fileExistsAtPath:filePath]);

    return [NSURL fileURLWithPath:filePath];

}

- (void)writeExoprtedVideoToAssetLibrary {

    NSURL *exportURL = self.exportSession.outputURL;

    ALAssetsLibrary *library = [[ALAssetLibrary alloc] init];

    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:exportURL]) {

        [library writeVideoAtPathToSavedPhotosAlbum:exportURL completionBlock:^(NSURL *assetURL, NSError *error){

            if (error) {

                //

            }    

            [[NSFileManager defaultManager] removeItemAtURL:exportURL error:nil];

        }];

    }else {

        NSLog(@"Video could not be exported to the assets library");

    }

}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容