讨论Gif的一些问题

gif是我们平时非常常见的图片格式,同时也是一种非常古老的动画图片格式。在我们平时使用的过程中也会有相应的问题,这里我们来看看Gif所带来的一些问题以及解决方式。

压缩率

gif的压缩率其实是非常低的,一小段动画图片远比一般的视频格式要大很多。

这里我分别对gif以及h264编码的视频进行对比

Gif 视频
496KB 192KB
4.4MB 1.4MB

由此可见两者之间的差距还是非常大的。

内存占用

如果你使用的是UIImage自身的animatedImage来展示Gif,那将是会非常恐怖的。假设我们有一个100帧400*300的Gif,那么我们完全解析成位图放在内存中将是100*400*300*4 = 45MB。而目前主流的设备分辨率都在1080级别的了,所以如果遇到了非常大的gif图,这种方式肯定是承受不住的。

目前处理gif比较有名的开源库是FLAnimatedImage。但是在内存占用过高的时候,不会缓存所有帧,有需要的时候再去载入,如果播放速度和载入速度不能匹配,那么就会丢弃该帧,导致掉帧的现象发生。这种方案对于帧数多,但是像素低的图片比较好,如果是非常大的图片,那么这种方案的效果不会特别好。

新的方案

基于以上的分析,这里提出一种新的方案,来解决大型gif的播放问题。那就是将gif转换为视频格式,由于视频播放是由系统优化的,所以不会产生性能方面的问题。这里来简单描述下。

gif由ImageIO来实现读取操作,视频采用AVFoundation来实现写入。

AVAssetWriter *assertWriter = [[AVAssetWriter alloc] initWithURL:url fileType:AVFileTypeMPEG4 error:nil];
NSDictionary *writerSettings = @{
                                  AVVideoCodecKey: AVVideoCodecTypeH264,
                                  AVVideoWidthKey: @(gif.width),
                                  AVVideoHeightKey: @(gif.height)
                                  };
AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:writerSettings];
[assertWriter addInput:writerInput];

NSDictionary *pixelBufferAttributes = @{
                                        (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
                                        (id)kCVPixelBufferWidthKey: @(gif.width),
                                        (id)kCVPixelBufferHeightKey: @(gif.height)
                                        };
AVAssetWriterInputPixelBufferAdaptor *writerInputAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:writerInput sourcePixelBufferAttributes:pixelBufferAttributes];


NSEnumerator<CIImage *> *enumerator = gif.CIImageEnumerator;
CIContext *ctx = [CIContext contextWithOptions:@{ kCIContextPriorityRequestLow: @YES }];
double frameHZ = gif.delayTime == 0 ? 10 : 1/gif.delayTime;
__block CMTime time = CMTimeMake(0, frameHZ);
CMTime frameTime = CMTimeMake(1, frameHZ);
NSInteger loopCount = gif.loopCount;

if ([assertWriter startWriting]) {
    [assertWriter startSessionAtSourceTime:time];
    [writerInput requestMediaDataWhenReadyOnQueue:dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT) usingBlock:^{
        while (writerInput.isReadyForMoreMediaData) {
            CIImage *ciImage = [enumerator nextObject];
            if (ciImage == nil) {
                [writerInput markAsFinished];
                [assertWriter finishWritingWithCompletionHandler:^{
                    [[DDVideoCache defaultCache] setLoopCount:loopCount forKey:name];
                    DDVideoData *video = [[DDVideoData alloc] initWithPath:path];
                    complete(video);
                }];
                return ;
            }
            
            CVPixelBufferRef pixelBuffer;
            CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(NULL, writerInputAdaptor.pixelBufferPool, &pixelBuffer);
            if (ret != kCVReturnSuccess) {
                DDDebugInfo(@"[Error] CVPixelBuffer create error with code (%zd)!", ret);
                complete(nil);
                if (pixelBuffer) CVPixelBufferRelease(pixelBuffer);
                return;
            }
            if (pixelBuffer) {
                [ctx render:ciImage toCVPixelBuffer:pixelBuffer];
                
                if (![writerInputAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:time]) {
                    DDDebugInfo(@"[Error] Assert write error!");
                    complete(nil);
                    CVPixelBufferRelease(pixelBuffer);
                    return;
                }
                CVPixelBufferRelease(pixelBuffer);
                time = CMTimeAdd(time, frameTime);
            }
        }
    }];
}

这里使用CIImage来实现图片的转换,如果需要裁剪缩放,或者其他滤镜处理,都可以在这里处理。

这种方案有一个缺点,那就是转换过程比较花时间,需要一定的转换时间,那么我们就需要缓存转换后的视频文件。

这种方案可以处理非常大型的gif文件,但仅仅是一种退而求其次的方法,如果是小型gif文件,完全没有必要使用这样的方法。这里只是提出一种优化的思考方式。

这里我实现了一套简单的带有缓存的方案,具体参考DDGif2Video

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,375评论 25 707
  • 目录:风起长林(目录) 上一章节:风起长林(29) 皇宫西北角,这里有一处荒废的宫殿,平时是没什么人过来的,除了枯...
    褚褚一阅读 616评论 6 3
  • 1、创建项目 (1)在命令窗口:ng new 项目名(2)在WebStrom中File=>New=>project...
    小新子666阅读 502评论 0 0
  • 今年冬天堵车特别严重,在红绿灯路口,注意力被车里的广播吸引了过去,主持人问到一个问题,当我们看到“流年”这两个字的...
    Lyn小恩阅读 467评论 2 0