iOS:AVFoundation视频-快放、慢放、倒放

效果图:


正常.gif

慢放.gif

快放.gif

倒放.gif

视频预览播放

倒入框架#import <AVKit/AVKit.h>
使用AVPlayer播放视频、rate属性进行加速、减速
使用AVPlayerItem中的方法判断加速、减速
canPlaySlowReverse:支持到放
canPlaySlowForward:支持慢放
canPlayFastForward:支持快放

核心代码:

注意:倒放需要seek到视频结尾。

- (void)clickAction:(UIButton*)sender{
    
    [self.avplayer seekToTime:kCMTimeZero];
    [self.avplayer play];
    
    switch (sender.tag) {
        case 0:
            if ([self.avplayer.currentItem canPlaySlowReverse]) {
                self.avplayer.rate = -1.0;
                [self.avplayer seekToTime:self.avplayer.currentItem.duration];
            }else{
                [self showAlert:@"不支持倒叙播放"];
            }
            break;
        case 1:
            self.avplayer.rate = 1;
            break;
        case 2:
            if ([self.avplayer.currentItem canPlaySlowForward]) {
                self.avplayer.rate = 0.5;
            }else{
                [self showAlert:@"不支持0.5倍速播放"];
            }
            break;
        case 3:
            if ([self.avplayer.currentItem canPlayFastForward]) {
                self.avplayer.rate = 2.0;
            }else{
                [self showAlert:@"不支持2倍速播放"];
            }
            break;
            
        default:
            break;
    }
}

生成资源视频

如果对视频资源做速度处理,我思考的原理:AVAssetReader+AVAssetWriter

倒放:我本来是想着用AVAssetWriter从后往前写入CMSampleBufferRef,苹果应该是不支持这种方式,必须要从0点开始写入。
所以需要使用resetForReadingTimeRanges方法,这个方法能够加载一个时间节点数组,倒序便利,copyNextSampleBuffer,就能取出后->前CMSampleBufferRef时间点。

使用resetForReadingTimeRanges,必须设置AVAssetReaderTrackOutput的supportsRandomAccess为YES:不按照顺序读取

获取到时间点数组、时间节点范围数组

- (void)setclipTimeRangeArray{
    CMSampleBufferRef sample;
    CMTime presentationTime = kCMTimeZero;
    CMTime startTime = kCMTimeZero;
    CMTime endTime = kCMTimeZero;
    NSUInteger processIndex = 0;
    
    self.clipTimeRangeArray = [NSMutableArray array];
    self.sampleTimeArray = [NSMutableArray array];
    
    //每10片确定一个帧组范围
    while((sample = [self nextVideoSample])) {
        //时间点
        presentationTime = CMSampleBufferGetPresentationTimeStamp(sample);
        NSValue *presentationValue = [NSValue valueWithBytes:&presentationTime objCType:@encode(CMTime)];
        [self.sampleTimeArray addObject:presentationValue];
        
        CFRelease(sample);
        sample = NULL;
                
        if (processIndex == 0) {
//            startTime = presentationTime;
            processIndex ++;
            
        } else if (processIndex == 9) {
            endTime = presentationTime;
            
            CMTimeRange timeRange = CMTimeRangeMake(startTime, CMTimeSubtract(endTime, startTime));
            NSValue *timeRangeValue = [NSValue valueWithCMTimeRange:timeRange];
            [self.clipTimeRangeArray addObject:timeRangeValue];
            
            processIndex = 0;
            startTime = presentationTime;
            endTime = kCMTimeZero;
            
        } else {
            processIndex ++;
        }
    }
    //处理不够kClipMaxContainCount数量的帧的timerange
    if (CMTIME_COMPARE_INLINE(kCMTimeZero, !=, startTime) && CMTIME_COMPARE_INLINE(kCMTimeZero, ==, endTime)) {
        
        endTime = presentationTime;
        
        //单独处理最后只剩一帧的情况
        if (CMTIME_COMPARE_INLINE(endTime, ==, startTime) &&
            processIndex == 1) {
            startTime = CMTimeSubtract(startTime, CMTimeMake(1, self.videoAsset.tracks[0].nominalFrameRate));
        }
        
        CMTimeRange timeRange = CMTimeRangeMake(startTime, CMTimeSubtract(endTime, startTime));
        NSValue *timeRangeValue = [NSValue valueWithCMTimeRange:timeRange];
        [self.clipTimeRangeArray addObject:timeRangeValue];
    }
}

针对每一个时间节点范围,获取CMSampleBufferRef,再依据元素位置获取时间点。
CMSampleBufferRef:前->后
pts:后->前

//倒序读取
- (void)nextReverseVideoSample:(void(^)(CMSampleBufferRef buffer,CMTime pts_reverse))block{
    
    __block NSInteger index = 0;
    __block NSMutableArray* bufferCaches = [NSMutableArray array];
    __block NSMutableArray<NSValue*>* ptsCaches = [NSMutableArray array];
    
    [self.clipTimeRangeArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

        CMSampleBufferRef buffer;
        [_readerTrackOutput_video resetForReadingTimeRanges:@[obj]];

        while ((buffer = [self nextVideoSample])) {

            [bufferCaches addObject:(__bridge id _Nonnull)(buffer)];
            [ptsCaches addObject:self.sampleTimeArray[index]];
            index++;
        }
        [bufferCaches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

            CMTime pts = ptsCaches[ptsCaches.count - idx - 1].CMTimeValue;
            NSLog(@"==%f",CMTimeGetSeconds(pts));

            block((__bridge CMSampleBufferRef)(obj),pts);
        }];
        [bufferCaches removeAllObjects];
        [ptsCaches removeAllObjects];
    }];
}

插入视频

// 开始写入
- (void)pushVideoBuffer:(CVPixelBufferRef)buffer pts:(CMTime)pts{
    
    [NSThread sleepForTimeInterval:0.005];
    if (self.assetWriterInput_video.readyForMoreMediaData) {
        
        NSLog(@"插入图片:%f",CMTimeGetSeconds(pts));
          if (buffer) {
              [_adaptor appendPixelBuffer:buffer withPresentationTime:pts];
              CFRelease(buffer);
              buffer = NULL;
          }
    }else{
        NSLog(@"!!!!!无法插入图片:%f---%ld",CMTimeGetSeconds(pts),(long)self.writer.status);
    }
}

慢速、快速
对视频实现快速、慢速,无非是改变帧的时间点。
慢速:延长帧时间点
快放:缩小帧时间点

- (void)nextSpeedChangeFromValue:(float)slowValue VideoSample:(void(^)(CMSampleBufferRef buffer,CMTime pts_reverse))block{
    __block NSInteger index = 0;
    __block NSMutableArray* bufferCaches = [NSMutableArray array];
    __block NSMutableArray<NSValue*>* ptsCaches = [NSMutableArray array];
    
    [self.clipTimeRangeArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        CMSampleBufferRef buffer;
        [_readerTrackOutput_video resetForReadingTimeRanges:@[obj]];

        while ((buffer = [self nextVideoSample])) {

            [bufferCaches addObject:(__bridge id _Nonnull)(buffer)];
            [ptsCaches addObject:self.sampleTimeArray[index]];
            index++;
        }
             
        [bufferCaches enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            CMTime pts = ptsCaches[idx].CMTimeValue;
            
            pts = CMTimeMultiplyByFloat64(pts, 1/slowValue);
            block((__bridge CMSampleBufferRef)(obj),pts);
            
        }];
        [bufferCaches removeAllObjects];
        [ptsCaches removeAllObjects];
    }];
}

GitHub:
https://github.com/qw9685/rateVideo

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

推荐阅读更多精彩内容