前言

事情是这样的,出于节省流量的目的,最近在研究如何在iOS上将相机输出的JPEG编码成WebP,用于后续的图片上传。WebP由于其优势,可以达到既节省流量又能拥有不错的图像质量,所带来的好处也又很多,譬如加快了加载时间、节约服务器带宽。具体的WebP介绍可以看看这篇文章:WebP 极限压缩及ios实现。由于在研究过程中发现,WebP在移动端大多数场景为解码,也就是利用其体积小的特性节省下载资源部分的流量,但对于在移动端编码的资料就不多。故想通过本文记录一下此次研究。

移动端上的WebP编码

Android

总所周知,WebP是Google提出的,所以在Android4.0以后,其Bitmap的api就已经提供了WebP的编解码能力。

// Bitmap.java
public boolean compress(CompressFormat format, int quality, OutputStream stream)// pic为一个Bitmap对象
pic.compress(Bitmap.CompressFormat.WEBP, 90, outputStream)

以上代码就是在Android上对于WebP编码的调用。

iOS

在iOS上,由于原生并未对WebP进行相应支持,所以需要借助第三方库。libwebp就是Google提供出来能够跨平台提供WebP编解码能力的库。通过对iOS两个比较受欢迎的图片加载库:SDWebImage、YYImage的调研得知,这两个库都提供了支持WebP编解码能力的插件,而通过声明的podspec可以得知,他们都是依赖libwebp。

  • SDWebImage提供的WebP插件:SDWebImageWebPCoder

  • YYImage提供的插件

ps: 值得注意的是,在YYImage的podspec上,声明了一个采用WebP.framework的subspec,这个问题将会是在后面会提到。

这里先贴一下两个库对于WebP编码的简单调用

// SDWebImage
SDImageWebPCoder.shared.encodedData(with: image, format: .webP, options: [.encodeCompressionQuality: 0.9])// YYImage
let webpEncoder = YYImageEncoder.init(type: .webP)
webpEncoder?.loopCount = 0
webpEncoder?.quality = 0.9
webpEncoder?.addImage(with: jpegData, duration: 0)
let yyWebpData = webpEncoder?.encode()

基于libwebp实现的WebP编码

libwebp是Google提供的一个用于进行WebP格式编解码的支持库。libwebp仓库镜像地址。libwebp官方api文档。 从文档中可以得知,编码的api可分为简单api、高级api。通过查阅libwebp的源码发现,简单api的实现其实就是库中对于高级api的封装,可以理解为高级api的可自定义性会比较强。

简单api

先贴一下我采用高级api的部分代码实现

WebPConfig config;
if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, 90.f)) {CFRelease(webPImageDatRef);return nil;
}config.thread_level = 1;WebPPicture pic;
if (!WebPPictureInit(&pic)) {CFRelease(webPImageDatRef);return nil;
}pic.use_argb = 0;
pic.width = (int)webPImageWidth;
pic.height = (int)webPImageHeight;WebPMemoryWriter writer;
WebPMemoryWriterInit(&writer);
pic.writer = WebPMemoryWrite;
pic.custom_ptr = &writer;int result = WebPPictureImportRGB(&pic, rgb, (int)webPBytesPerRow);
if (!result) {WebPMemoryWriterClear(&writer);CFRelease(webPImageDatRef);return nil;
}WebPEncode(&config, &pic);NSData *webPFinalData = [NSData dataWithBytes:writer.mem length:writer.size];WebPPictureFree(&pic);
CFRelease(webPImageDatRef);
WebPMemoryWriterClear(&writer);
  • 可以看到最后的WebPEncode函数其实是需要一个WebPConfig对象及一个WebPPicture对象。

  • WebPConfig对象

    该对象主要是配置一些在编码过程中压缩算法的参数,具体的解释可参考上方Google给出的官方文档,WebPConfigPreset函数最终会调用源码config_enc.c中的WebPConfigInitInternal函数,这里是该对象被初始化并赋予默认值的地方。上述设置的thread_level是打开libwebp的多线程编码能力,主要是想尝试能否在其多线程的情况下提高编码效率。

  • WebPPicture对象

    该对象可以理解为是webp的一个交换结构,即输入、输出及一些有关图像的信息。

    • 输入指的就是rgb这个对象,它是一个uint8_t*,是通过传入的UIImage实体转换过来的,这个在后面会提到。它是通过WebPPictureImportRGB函数转换成WebPPicture的信息的。 ps: 需要注意的是,在Import之前,需要判断好UIImage的通道类型,譬如如果是RGBA则需要调用WebPPictureImportRGBA,否则可能会出现编码后的图像失真。 这里由于是从相机获取的,所以调用WebPPictureImportRGB
    • 输出是一个libwebp提供的WebPMemoryWriter对象,其中mem属性代表转换后的数据,size属性代表转换后数据的大小。我们可以通过这两个属性调用原生方法初始化一个NSData。
  • WebPEncode

    最终会调用到WebPEncode函数进行WebP的编码。经过测试发现该方法是最为耗时也最占用CPU资源的。编码结束后还需要调用相应的函数释放内存,防止内存泄漏。

关于编码时的通道数与相机出图的通道类型不对称问题

以下是相机的参数设置

测试的设备为iPhone7 系统:iOS 13.1,我们尝试输出一张在该设备上的最大分辨率图片,以下是输出结果

这里需要关注的点是,尽管我们认为相机输出的会是RGB,但其还是有32位,且具有kCGImageAlphaNoneSkipLast的标志位。所以实际上它的通道类型为RGBX。这个明显和上述所说的在编码之前调用WebPPictureImportRGB函数不符的(当然,这里应该是可以调用WebPPictureImportRGBX函数。这个没有深入研究,因为考虑到到了8位可能对编码速度有影响,可以作为思考扩展。)。所以在本次研究中,参考了SDWebImage的处理方案,下面是通过参考SDWebImage方案所进行的编码前预处理(相关源码可以在SDImageWebPCoder.m中sd_encodedWebpDataWithImage方法中找到)。

CGImageRef webPImageRef = image.CGImage;
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(webPImageRef);
size_t webPBytesPerRow = CGImageGetBytesPerRow(webPImageRef);size_t webPImageWidth = CGImageGetWidth(webPImageRef);
size_t webPImageHeight = CGImageGetHeight(webPImageRef);CGDataProviderRef webPDataProviderRef = CGImageGetDataProvider(webPImageRef);
CFDataRef webPImageDatRef = CGDataProviderCopyData(webPDataProviderRef);uint8_t *rgb = NULL;
// Convert all other cases to target color mode using vImage
vImageConverterRef convertor = NULL;
vImage_Error error = kvImageNoError;vImage_CGImageFormat srcFormat = {.bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(webPImageRef),.bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(webPImageRef),.colorSpace = CGImageGetColorSpace(webPImageRef),.bitmapInfo = bitmapInfo
};
vImage_CGImageFormat destFormat = {.bitsPerComponent = 8,.bitsPerPixel = 24,.colorSpace = CGColorSpaceCreateDeviceRGB(),.bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/RGBA8888 (Non-premultiplied to works for libwebp)};convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, &destFormat, NULL, kvImageNoFlags, &error);
if (error != kvImageNoError) {CFRelease(webPImageDatRef);return nil;
}vImage_Buffer src = {.data = (uint8_t *)CFDataGetBytePtr(webPImageDatRef),.width = webPImageWidth,.height = webPImageHeight,.rowBytes = webPBytesPerRow
};
vImage_Buffer dest;error = vImageBuffer_Init(&dest, webPImageHeight, webPImageWidth, destFormat.bitsPerPixel, kvImageNoFlags);
if (error != kvImageNoError) {vImageConverter_Release(convertor);CFRelease(webPImageDatRef);return nil;
}// Convert input color mode to RGB888/RGBA8888
error = vImageConvert_AnyToAny(convertor, &src, &dest, NULL, kvImageNoFlags);
vImageConverter_Release(convertor);
if (error != kvImageNoError) {CFRelease(webPImageDatRef);return nil;
}rgb = dest.data; // Converted buffer
webPBytesPerRow = dest.rowBytes; // Converted bytePerRow
CFRelease(webPImageDatRef); // Use CFData to manage bytes for free, the same code path for error handling
webPImageDatRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, rgb, webPBytesPerRow * webPImageHeight, kCFAllocatorDefault);

这里用到的是iOS的Accelerate框架,最终实现的效果是将原来在相机输出的图片进行转换,生成一个24位的、bitmapInfo标志位为kCGImageAlphaNone的图像数据。再利用此数据流进行WebP的编码,这样就能与编码时的WebPPictureImportRGB函数对齐。

编码耗时

从官方提供的数据来看,WebP与JPG相比较,编码速度慢10倍。这里利用上述的方案,选用一张预先在相机输出的结果图(图片大小为2448*3264),进行循环10次的编码,最终计算出10次耗时的平均值。 ps:这里设置的编码质量为90%

机型/系统 循环10次平均耗时/s 平均大小/bytes
iPhone5s/iOS12 2.529741811752319 488398
iPhone5s/iOS11.2.6 2.5465808153152465 488398
iPhone8P/iOS 13.4 0.965844702720642 488398
iPad mini4/iOS13.1.3 1.944874358177185 488398
iPhone7/iOS13.1 1.0465802907943726 488398
  • 该图片在iPhone7设备上JPEG的编码时间为0.11402392387390137s, 同样是这是编码质量为90%。这里可以基本验证编码时间为10倍左右。
  • 上述设备中有两台5s,平均耗时都在2.5s多,这个也能初步证明编码的耗时和iOS系统关系不大,与设备本身硬件性能关系较大。
  • 关于编码产物的大小,这里测试发现基本平均都在488398bytes,产物大小可能只与编码算法配置相关(这个可能需要做大量的兼容性测试)。

题外话

  • 这个插播一个在研究过程中碰到的问题:直接使用libwebp源码在debug模式下耗时很严重,远远超过上述所说的10倍。

    libwebp的源码可以通过pod拉取(pod ‘libwebp’),这个在上述SDWebImage的podspec声明中也有体现。虽然在上述提供的镜像仓库内未找到相关的podspec文件,但我在拉取之后的json文件中找到了对应的git仓库地址,对应的是Google的git仓库。libwebp

    如果我们直接使用源码的方式,进行导入。在debug模式下,其编码的耗时会比上述统计的耗时高出几十倍之多。这个也是在研究耗时过程中碰到的最主要问题,后续是通过将libwebp提供的iosbuild.sh编译成framework,才最终发现这个问题。目前怀疑是debug模式下,libwebp会调用一些debug的逻辑,导致总体速度变慢,目前未在源码中找到。这个可能也是开头提到的YYImage会提供两个subspec的原因。

  • 还有一个是比较严重的问题是cpu占用率。

    在测试发现,在编码的过程中设备的cpu会飙升到90%以上,采用工具查看主要的占用来源于WebPEncode函数,可能对于手机性能的损耗会比较大。

扩展

在文章开头提到的Android自带的WebP编码方法,这就引出了一个问题:Android本身也是使用libwebp进行编解码的,是否里面也是通过高级api实现?iOS是否可以参考其WebPConfig的设置来实现效果的对齐?

通过查看Android源码得知,文章开头提到的compress方法其实里面真实调用的是一个native方法

通过查阅Android源码得知,此方法最终调用的是在/external/skia/src/images/SkWebpEncoder.cpp的

bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts)

源代码地址:SkWebpEncoder,这里参考的是Android api 28。

可以看出,Android原生对于WebPConfig的定制化也是不高的。

最后

本文主要介绍了在iOS利用libwebp进行WebP编码的研究方案,以及利用此方案的一些性能研究。

作者:Cy13er
链接:https://juejin.im/post/6893833647372107789

在iOS上进行WebP编码是一种怎样的体验?相关推荐

  1. 回京火车上编码是一种怎样的体验?

    今天第一天上班,记录一下昨天回京的笔录.相信你也有一样的感受.自己的笔记,勿喷! 正文 今天是2018年10月7号,长达7日的假期生活已经结束.行走在火车上瞬感无聊,于是拿起了自己手中的电脑包瞎捣鼓了 ...

  2. 在BF561上实现h264编码的几种方案

    快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com 本文适用于 ADSP-BF561 优视BF561EVB开发板 uclinux-2008r1. ...

  3. 每日一笑 | 在地铁上被老奶奶让座是一种什么样的体验?

    全世界只有3.14 % 的人关注了 数据与算法之美 (图源网络,侵权删)

  4. Flutter在iOS上的表现就是一坨屎

    Flutter在iOS上的表现就是一坨屎: 用户体验差到了极点: 目前来说不值得投入大量精力去研究: 了解一下原理可以. 转载于:https://www.cnblogs.com/feng9exe/p/ ...

  5. iOS上文本处理之简史

    iOS 文字简史 iPhone OS 2 UILabel UITextField UITextView iPhone OS 3 New Feature: 复制 && 粘贴 iOS 3. ...

  6. IOS 最新邓白氏编码申请

    iOS开发邓白氏编码申请流程: 前言: 邓白氏编码不是所有账号都需要的,如果你只是申请个人账号,不需要显示公司的信息,那就不需要邓白氏编码,直接 $99美元那个个人的就可以,如果你 App需要显示的是 ...

  7. iOS 客户端基于 WebP 图片格式的流量优化(下)

    在iOS 客户端基于 WebP 图片格式的流量优化(上)这篇文章中,已经介绍了WebP格式图片的下载使用,仅仅只有这样还远远不够,还需要对已经下载的图片数据进行缓存. 曾经有句名言『计算机世界有两大难 ...

  8. 部分mp4视频在ios上无法播放问题

    1.问题描述 mp4视频在安卓上全部能播放,部分mp4视频在ios不能播放.表现为刚开始缓冲加载几秒钟后,就显示加载失败的图标了,或者直接显示加载失败图标 2.问题分析 2.1 什么问题导致的 这篇文 ...

  9. 在IOS上实现二维码扫描

    如今二维码随处可见,无论是实物商品还是各种礼券都少不了二维码的身影.而手机等移动设备又成为二维码的一个很好的应用平台,不管是生成二维码还是扫码二维码. 下面介绍一下如何在苹果iOS设备上使用二维码: ...

最新文章

  1. 阿里大神分享API网关在微服务架构中的应用!
  2. 调用iframe中的函数
  3. vue3源码中的最长递增子序列
  4. mipi协议_MIPI物理层一致性测试:D-PHY一致性测试
  5. 深入理解Java泛型
  6. boot sprint 项目结构_完美起航-【知识】SpringBoot项目结构目录
  7. Rocketmq中Topic、Tag、GroupName的设计思想
  8. webserver总结
  9. 集成第三方SDK之支付宝支付
  10. 如何爬取ajax网页之爬取雪球网文章
  11. python bytes转str_Python3中bytes类型转换为str类型
  12. centOS6.5中静默安装oracle 11gR2
  13. “存算”协同,让存储发挥极致性能
  14. docker运行yyets_docker 使用教程1
  15. Google AdSense广告赚钱之技巧篇!
  16. J2ME移动开发平台搭建
  17. 使用tarjan算法和fleury算法求解中国邮递员问题
  18. Mac 本地下载安装Nginx
  19. 职称计算机ppt2003考点,【职称计算机《Powerpoint2003》考点:PPT的启动和退出】- 环球网校...
  20. python应用: GUI界面设计之JPG转ico工具编辑(PythonMagic)

热门文章

  1. Docker:windows7系统环境下安装docker:Manifest extraction failed: 找不到Windows运行时类型Windows.Data.Json.JsonObject
  2. PTA | 喊山 (30 分) BFS 拼题A
  3. 【2018.05.07学习笔记】【linux基础知识10.6-10.10】
  4. 华为官方出品:首本HMS Core技术解析图书问世
  5. Allegro文件导入SIwave仿真的三种方法
  6. android开发方向知乎,Android开发者必看:知乎开源的图片选择库
  7. Android动画学习笔记(二)——动画插值器Interpolator
  8. android 文件存储位置,安卓各文件存储路径汇总(Android file path)
  9. powermill程序单html模板,3-PowerMill程序单模板定制.pdf
  10. 软考初级程序员上午单选题(19)