美文网首页
讨论Gif的一些问题

讨论Gif的一些问题

作者: 码农苍耳 | 来源:发表于2017-12-07 22:58 被阅读43次

    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

    相关文章

      网友评论

          本文标题:讨论Gif的一些问题

          本文链接:https://www.haomeiwen.com/subject/nqksixtx.html