惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
Help Net Security
Help Net Security
L
LINUX DO - 最新话题
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
Attack and Defense Labs
Attack and Defense Labs
M
MIT News - Artificial intelligence
Security Archives - TechRepublic
Security Archives - TechRepublic
SecWiki News
SecWiki News
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
AI
AI
PCI Perspectives
PCI Perspectives
L
Lohrmann on Cybersecurity
G
Google Developers Blog
N
News | PayPal Newsroom
Hugging Face - Blog
Hugging Face - Blog
B
Blog RSS Feed
The Hacker News
The Hacker News
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
I
InfoQ
Webroot Blog
Webroot Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
U
Unit 42
A
About on SuperTechFans
Cyberwarzone
Cyberwarzone
Schneier on Security
Schneier on Security
Cisco Talos Blog
Cisco Talos Blog
D
Docker
博客园_首页
The Cloudflare Blog
S
Secure Thoughts
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Last Watchdog
The Last Watchdog
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
Last Week in AI
Last Week in AI
F
Fortinet All Blogs
I
Intezer
Google DeepMind News
Google DeepMind News
Know Your Adversary
Know Your Adversary
Simon Willison's Weblog
Simon Willison's Weblog

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 MediaCodec 硬编码之相机内容编码成 H264 文件 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
图像库 libpng 编译与实践
音视频开发进阶 · 2019-07-12 · via 音视频开发进阶

一个专注音视频领域的小圈子

在之前的文章中介绍了 stb_image 图像库,还顺带提到了 libpng 和 libjpeg ,这篇文章就是介绍如何在 Android 平台上用 CMake 编译 libpng 动态库以及 libpng 使用实践。 【简单易用的图像解码库介绍 —— stb_imagehttps://glumes.com/post/android/stb-image-introduce/

libpng 的官方介绍网站如下:

http://www.libpng.org/pub/png/libpng.html

下载地址网站如下:

https://sourceforge.net/projects/libpng/files/

博客中使用的版本是 1.6.37 ,也是目前最新的版本了。

关于 libpng 的编译网上已经有不少博客教程了,但有的是基于 Linux,有的是基于 Android.mk 的,本文会介绍如何在 Android Studio 上通过 CMake 来编译 Android 的动态库。

CMake 编译 libpng 动态库

neon 相关编译

在 libpng 的源代码中,就提供了 CMakeLists.txt 文件用以说明如何编译,但是却不能直接用在 Android 平台上,不过可以借鉴其源码作为参考。

由于 CMake 跨平台编译的特性,一般大型项目代码编译都会针对平台做适配,常见代码结构如下:

    if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR
            CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64")
        set(libpng_arm_sources
                arm/arm_init.c
                arm/filter_neon.S
                arm/filter_neon_intrinsics.c
                arm/palette_neon_intrinsics.c)
        // 定义宏
        add_definitions(-DPNG_ARM_NEON_OPT=2)
    endif ()

这段代码就是判断系统处理器平台,不同平台所需要编译的代码不一样。而 libpng 会有这样的适配,主要是因为它用到了 neon 相关优化,该优化主要是用在 filter 操作方面。

// libpng 使用 neon 优化加速的方法
void
png_read_filter_row_up_neon(png_row_infop row_info, png_bytep row,
   png_const_bytep prev_row)
{
   png_bytep rp = row;
   png_bytep rp_stop = row + row_info->rowbytes;
   png_const_bytep pp = prev_row;

   png_debug(1, "in png_read_filter_row_up_neon");

   for (; rp < rp_stop; rp += 16, pp += 16)
   {
      uint8x16_t qrp, qpp;

      qrp = vld1q_u8(rp);
      qpp = vld1q_u8(pp);
      qrp = vaddq_u8(qrp, qpp);
      vst1q_u8(rp, qrp);
   }
}

通过查看 libpng 源代码,要启用 neon 优化,还必须通过 add_definitions 方法定义 DPNG_ARM_NEON_OPT 宏的值为 2 ,否则在源码中会认为不需要使用 neon 。

要使用 neon 编译,还需要指定编译器相关参数:

set_property(SOURCE ${libpng_arm_sources}
        APPEND_STRING PROPERTY COMPILE_FLAGS " -mfpu=neon")

不过看到网上一些 libpng 编译文章,基本没提到 neon 相关东西,估计这个优化加速功能用不上吧。

但是,可以在我的 Demo 上看到如何启用 neon 去编译,以后也会写专门的文章来介绍 neon 的使用~~

zlip 库依赖

libpng 动态库编译还依赖 zlip 库,要是在其他平台上需要单独下载这个库,但是 Android 上就不需要了,因为 Android 编译环境本身就提供了这个库,就像我们使用 log 库一样。

// 指定要编译的 so 依赖哪些其他的 so , z 就是 zlib 库
target_link_libraries(png z log )

Android 编译环境中 z 就是 zlip 库了。

源码编译

其他的就是源码编译了,主要是 add_library 方法的使用,要指定好需要编译的源文件。

具体有哪些源文件需要添加到编译中,还是请参考如下链接,就不贴具体代码了,减少文章篇幅。

https://github.com/glumes/InstantGLSL/blob/master/instantglsl/src/main/cpp/libpng/CMakeLists.txt

完成上述三个过程后,就能够编译出 libpng 的动态库了,实际编译过程还是参考项目代码吧。

libpng 的使用实践

编译是小事,重点在使用~~~

以解码 png 图片获取像素内容为例:

linpng 初始化

首先是初始化 libpng ,得到 png_structp 结构体。它可以说是代表了 libpng 上下文,在方法调用时都需要把它作为第一个参数传入。

    // 传 nullptr 的参数是用来自定义错误处理的,这里不需要
    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);

由于是读取,方法名中带有 read ,如果是写入,那就是 png_create_write_struct 方法了。

设置错误返回点

由于在创建 png 变量时,用来自定义错误处理的参数都传了 nullptr,所以需要设置错误返回点,这样当 libpng 发生错误时,程序将回到这个调用点,这时候可以做一些清理工作:

    if (setjmp(png_jmpbuf(png))) {
        png_destroy_read_struct(&png, nullptr, nullptr);
        fclose(fp);
        return;
    }

判断文件是否是 png 格式

libpng 提供了 png_sig_cmp 方法来检查文件是否 png 格式。

    #define PNG_BYTES_TO_CHECK 4
    char buf[PNG_BYTES_TO_CHECK];
    // 读取 buffer
    if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) {
        return;
    }
    // 判断
    if (!png_sig_cmp(reinterpret_cast<png_const_bytep>(buf), 0, PNG_BYTES_TO_CHECK)) {
        // 返回值不等于 0 则是 png 文件格式
    } 

如果调用了该方法,需要通过 png_set_sig_bytes 方法告诉 libpng 该跳过相应的数据,否则会出现黑屏,或者通过 rewind 方法重置文件指针。

获取图像信息

首先创建 png_infop 结构体来代表图像信息:

    png_infop infop = png_create_info_struct(png);

然后是设置图像的数据源,前提是要得到文件路径:

    // 根据文件路径打开文件
    FILE *fp = fopen(mFileName.c_str(), "rb");
    // 设置图像数据源
    png_init_io(png, fp);

接下来是读取信息:

    png_read_png(png, infop,
                 (PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND),

通过如下方法,能得到图像具体某方面信息:

    mWidth = png_get_image_width(png, info);
    mHeight = png_get_image_height(png, info);
    mColorType = png_get_color_type(png, info);
    mBitDepth = png_get_bit_depth(png, info);

当然也可以通过 png_get_IHDR 方法去获得信息

    png_get_IHDR(png, infop, &mWidth, &mHeight, &mBitDepth, &mColorType, &mInterlaceType,
                 &mCompressionType, &mFilterType);

获取像素内容

通过 png_get_rows 方法按行获取所有的数据,然后赋值到像素指针上去。

	// 代表像素内容的指针
    unsigned char *mPixelData;
    // 获取每行的字节数量
    unsigned int row_bytes = png_get_rowbytes(png, infop);
    mPixelData = new unsigned char[row_bytes * mHeight];
    png_bytepp rows = png_get_rows(png, infop);
    // 逐行读取,并填充到像素指针上去
    for (int i = 0; i < mHeight; ++i) {
        memcpy(mPixelData + (row_bytes * i), rows[i], row_bytes);
    }

其实在前面的 png_read_png 方法中就已经得到了所有的像素内容,保存在 infop 变量的 row_pointers 中,具体的实现如下:

通过 png_read_image 方式读取像素内容:

    // row_pointers 当成了一维指针数组
    png_bytep *row_pointers;
    row_pointers = (png_bytep *) malloc(sizeof(png_bytep) * mHeight);
    for (int y = 0; y < mHeight; y++) {
        row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
    }
    png_read_image(png, row_pointers);

最后,别忘了调用 png_read_end 方法结束读取。

有了像素内容,就可以做一个常见的渲染操作了,将像素内容渲染绘制到纹理上。

保存图片

最后介绍如何根据像素内容去保存图片,在 libpng 中也提供了相应的方法调用,流程就是如下方法:

    png = png_create_write_struct()
    infop = png_create_info_struct()
    // 关联数据源,png 和要写入的文件
    png_init_io(png,fp)
    // 设置 infop 相关参数,代表最好要生成的图片文件相关信息
    png_set_IHDR()
    // 写入图片信息
    png_write_info(png, infop);
    // 写入图片像素内容
    png_write_image(png, row_pointers);
    // 结束写入
    png_write_end(png, NULL);

流程和读取像素内容恰好相反。

其中 png 变量要通过 png_create_write_struct 创建。

infop 变量还是 png_create_info_struct 方法创建。

接下来就是设置图片信息,写入图片信息,写入像素内容,具体的代码实践可以参考我的代码示例。

参考

最后,在 libpng 的源代码中,也提供了丰富的示例,一般这种开源库都会提供相应的 test 代码,通过 test 代码基本都能找到相应的函数调用。

libpng 的官网示例地址如下:

http://www.libpng.org/pub/png/libpng-manual.txt

有疑问的话,基本都可以在这个上面找到答案。

原创文章,转载请注明来源:    图像库 libpng 编译与实践