每天一点点音视频_bmp_像素数据2

像素格式

单位: 每个像素位数(bit per pixel – bpp)

一般的像素位数为: 1bpp, 2bpp, 4bpp, 8bpp, 16bpp, 24bpp, 32bpp

像素的位数有了,但是具体一个像素的里的位数,各位的含义是什么呢?

首先要知道,一个像素可以包括4个通道,分别是: 红, 绿, 蓝, alpha(透明度)

在 DIB 头里, 提供了特定的字段来定义各通道的掩码,通过掩码来从每个像素数据里选择出几个位作为一个组,被解析成一个通道的数据。

一开始没搞明白,是看了 wiki 最后的例子才搞明白的。

wiki 附上

碎碎念

聊聊理解力, 理解力是对一件事物的看法,认知,它是什么。

当我们看到对一个事物的解释后,我们形成对该事物的新的认知,这个认知是我们对它旧的认知加上现在对它的新的认知。这里面,旧认知会影响新认知, 一个人如果太自负,把自己所已有的认知当作真理,看到新认知时,就不好接受,从而表现为难理解。

这里面的问题是,把一个旧认知当成了真理,一个认知,可能真的是真理,但是真理一定是有前提的,接触新认知时,前提可能与旧认知的前提不同,从而那个真理不适用了。

举例来说, 我对像素格式的理解是来自 Android 里的 Bitmap 和 OpenGL, 那里像素格式有 RGBA8888, RGB565等等。名字就说明了像素的格式, 比如 RGBA8888 的意思是四个通道 红绿蓝透明 按顺序,每个顺序8个位, RGB565 的意思是 三个通道 红绿蓝 按顺序,红占了 5 个位, 绿占了 6 个位, 蓝占了 5 个位。这是我对像素格式的旧认知。

当看 BMP 的这个 WIKI 的时候, 像素格式就与之前的不太一样, 这里引入了一种机制, 使用掩码, 从每个像素中选出特定的通道的位数, 这样通过定义不同的通道, 可以定义上千种像素的格式。不过 WIKI 里也说了, 常用的也就那么几种, 也就是旧认知里的那种。所以新认知对旧认知做了一个底层的解释。

明天: 每天一点点音视频_bmp_实践

每天一点点音视频_bmp_像素数据1

之前说了像素数据分行,并且每一行填充对其。

下面一个问题是数据怎么排列的,第一个像素是图片的那个点呢?

通常, 第一个像素是图像的左下角的第一个点,从这一点,向右一行,然后向上。既然是”通常”, 那就有例外, 对于 DIP 版本为 BITMAPCOREHEADER 的, 未压缩的图像,在高度值是负数时,会从上向下一行一行排列。

压缩

  1. 索引颜色的图像(也就是像素的值是 color table 的索引),可以使用 4bit 或 8bit 的 RLE 或者 Huffman 1D 算法
  2. OS/2 的 BITMAPCOREHEADER2 中, 24bpp 可以使用 24-bit 的 RLE 算法
  3. 16 bpp 和 32 bpp 的图像总是非压缩存储
  4. 所有颜色深度的图像都可以非压缩存储

明天: 每天一点点音视频_bmp_像素数据2

每天一点点音视频_bmp_像素数据

BMP 的像素存储的大小如何计算呢,要按照的想的,很简单,反正 DIP 头里已经有图像的宽和高了,而且还有了图像颜色的深度,也就是每个像素占的空间的大小,这样计算呗:

DATASIZE = WIDTH * HEIGHT * PIXEL

但是实际上不然, BMP 为了加速读取, 让每一行通过填充,对齐到 DWORD (4字节)的倍数, 这样一行的所占空间大小如下计算:

ROWSIZE = (WIDTH * PIXELBIT + 31) / 32 * 4

上面的意思是先算出一行像素的所占的位数, 然后填充 31 位,然后除以 32, 这样就得到了补充对奇后的一行的双字数,然后乘以 4 就是一行的字节数。

DATASIZE = ROWSIZE * HEIGHT

计算出所有行的数据。

明天: 每天一点点音视频_bmp_像素数据1

每天一点点音视频_bmp_色盘

color table 或者叫 palette, 是色盘, 调色板, 类似于现实中的这东西

它会列出所有的图片出现的颜色,对于索引颜色的 BMP,每个像素的值是这个表里的一个索引;对于非索引颜色的 BMP,这个表只是列出出现的颜色,一边设备显示的优化处理。

这里要注意的是, 颜色索引所占的大小可能是 1 位, 4 位, 8 位, 这个数量有点小呀。索引的位图只是适合颜色比较单一的图片。调色板里的颜色的所占大小就是颜色的格式了, RGBA32, RGB24 这个样的。

这块区域的偏移是在 文件头, DIP头 后, 在它之前还可能会有一个可选的区域, 是在压缩算法为 BI_BITFIELDS 时, 有RGB三个通道的颜色掩码。

这块区域的大小,正如 DIP 头里声明的,如果那里声明了数量,这里就是多大,如果声明0,则是 2^n, n 为每个像素的索引所占内存大小。

至此, 就知道了这色盘是什么, 也可以读出这块区域了。

如果要写出兼容行强的应用,还要去看格式的规格书呀。

明天: 每天一点点音视频_bmp_像素数据

每天一点点音视频_bmp_dip头

内存中的 DIB

一个 BMP 文件加载到内存中,变成了一个 DIB 数据结构。DIB 的格式与 BMP 的结构相似,但是没有之前讲的 14 个字节的 BMP 文件头。

DIB 头

DIB 有很多版本,微软对它扩展了好多次。这些版本都有名字,比如 BITMAPCOREHEADER, OS22XBITMAPHEADER, BITMAPINFOHEADER 等等,每一个版本都有特定的大小, 所以可以通过这个大小来区分不同的版本,所有的版本,他们的第一个字段都是大小,所以就可以区分不同的版本,来解析剩下的字段了。

这里我们以 BITMAPINFOHEADER 为例子来看一看他的字段:

偏移(十六进制) 偏移(10进制) 大小(bytes) BITMAPINFOHEADER 对它的声明作用
0E 14 4 这个头的大小
12 18 4 bitmap 的宽, 单位像素, 有符号整数
16 22 4 bitmap 的高, 单位像素, 有符号整数
1A 26 2 色盘的数量,固定为1
1C 28 2 每个像素的位数, 是图像的颜色深度,通常值为 1, 4, 8, 16, 24, 32
1E 30 4 使用的压缩方法
22 34 4 图像大小, 这是原始图像数据的大小,当压缩算法是 BI_RGB 时,值为0
26 38 4 图像水平的分辨率, 每公尺的像素数,符号整数
2A 42 4 图像垂直的分辨率, 每公尺的像素数,符号整数
2E 46 4 色盘中颜色的数量, 或者 0 表示默认 2^n
32 50 4 重要颜色的数量, 0表示每一种颜色,通常忽略这个字段

通常,我们提到 BMP 都是原始数据,就是没有压缩的,而提到压缩的图像格式就是 JPG, GIF, PNG 等等。但是, BMP 也有压缩算法, 下面举几个例子:

标志 压缩算法 注释
0 BI_RGB 不压缩 这就是最经常的情况了
4 BI_JPGE
5 BI_PNG

还有其他很多, 不想列了,自行 wiki, 竟然,还支持 JPGE, PNG, 高的这么复杂,这么乱。

我看了一个 wiki 下面的 C++ 实现的位图加载程序, 发现这种这种东西的确是 C/C++ 更适合, 定义一个结构,一下就可以将文件里面的结构映射到结构的对应字段上。仔细想了想, Java 好像不可以呀

明天: 每天一点点音视频_bmp_色盘

每天一点点音视频_bmp_文件头

文件头

BMP文件头在 BMP 文件的开始,用来识别该文件。典型的应用会读取首先这部分数据来确定这个文件是 BMP 格式, 并确定没有破坏。开头两个字节是 ASCII 码的 “BM”。所有整型值都是小端的,即低位数据在前。

偏移(16进制) 偏移(10进制) 大小 作用
00 0 2 bytes BM, 在 OS/2 系统还可以是其他值,但可以不考虑了
02 2 4 bytes BMP 文件的大小
06 6 2 bytes 保留,创建 BMP 的应用可以自己定义它的作用
08 8 2 bytes 保留,创建 BMP 的应用可以自己定义它的作用
0A 10 4 bytes 图像数据的偏移(像素点的数据)

我用 kotlin 写了个读取这一部分的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
File("/sdcard/test.bmp").also {
val reader = it.reader()
val charArray = CharArray(4)
reader.read(charArray, 0, 2)
Log.e(TAG, "id: ${charArray[0]}${charArray[1]}")
reader.read(charArray, 0, 4)
Log.e(
TAG,
"file size: ${charArray[3].toInt() * pow(16.0, 3.0)
+ charArray[2].toInt() * pow(16.0, 2.0)
+ charArray[1].toInt() * 16.0
+ charArray[0].toDouble()}"
)
Log.e(TAG, "file size: ${it.length() / 8}")
reader.read(charArray, 0, 2)
Log.e(TAG, "reserved: ${charArray[1].toInt() * 16.0
+ charArray[0].toDouble()}")
reader.read(charArray, 0, 2)
Log.e(TAG, "reserved: ${charArray[1].toInt() * 16.0
+ charArray[0].toDouble()}")
reader.read(charArray, 0, 4)
Log.e(TAG, "offset: ${charArray[3].toInt() * pow(16.0, 3.0)
+ charArray[2].toInt() * pow(16.0, 2.0)
+ charArray[1].toInt() * 16.0
+ charArray[0].toDouble()}")
}

读取的文件大小和直接用 File 读取的文件大小不一致,之后要研究一下。

明天: 每天一点点音视频_bmp_dip头

五月碎碎念

如果做教程

一个教程,应该像武功一样,一重一重的来。

这句话不是别人说的,正是我自己说的。不过,仔细思考这句话,除了感觉废话外,还有点故弄玄虚。什么叫一重一重的。想到这个,我觉的我应该去看一本武功秘籍,你可能因为我在看玩笑,但是我的确记着好像看过一本武功秘籍,除了那本李小龙截拳道,还有一本,忘了,可能,那就给了我一个武侠梦吧。找了一下,知乎上有资源,选了一本《打穴十二功》。我想干嘛来,我想看看是否分重,这个一重一重的概念,是从武侠片里看来的,武林群侠传里也有升级武功,一重一重的。

扯多了,我就是想表达,学习一向技术,可能需要以及以及来,是有步骤的,要慢下来,打好基础。

模型

在编程中,我们经常会说对某个东西建模,这个建模的过程并不是完全的模拟的现实中的某个东西,而是根据我们的需求选取的特定的特征。比如在一个学生管理系统里,我们可能将一个学生建模成拥有名字,成绩,性别等属性的模型,但是在一个社区管理系统里,一个学生的属性可能就可能不必有成绩,甚至,没有学生这个模型,学生只是一个属性。建模与抽象密不可分,两者都与目标有关。

而在科学中,也有着各种模型,而这种模型与以上所说的计算机模型,有类似,但也有不同。科学中的模型,它是为了反应自然,是为了解释我们观察到的现象。同样的,自然中的模型也与实际的并不是完全一样的。比如,化学中的分子结构,可能实际上并不是如我们学得那样,分子中有多个原子,院子形成特定的结构。更确切的说,可能是这样的结构,但是并不是图形这样的:

但是,就是会把所见当作真是存在,所以在教这一课的时候我觉的有必要反复强调,这只是模型。

学英语有感

使用流利说学习英语三个月了吧, 感觉到自己英语的进步,但是同时,也感觉到自己发音这么多年都是错的,真的很惭愧。不过,人嘛,有缺点,很正常,要反思。

一直以来,我觉的我英语不错,我的发音很标准,按照音标都能读出来,但是,最近我发现,s 和 th,我一直是不区分的,虽然当时学过,但是后来发音就是不区分了,不过最近的学习,纠正了这个问题。实际上我至今还听不出这两个音的区别。

最近,我的侄子,过来跟我和妈妈住,他5岁,l 和 n 不分,我教他拼音的时候,很是苦恼呀。不过,我也强调他这些发音,有点作用吧。“老奶奶喝牛奶”,单个还能,说,这样的就说不了了。

奥,还有,通过我的学习英语的经历,我理解了那句话,“做正确的事情,远比正确的做事要重要得多”。一开始学发音,学错了,然后后来反复学习,反复练习,都是错了。当然,可能大多数时候,我们遇到的问题是怎么找到正确的事情。比如,英语老师叫音标,就教错了。不过那是后还是可以通过听磁带纠正的。不过还是现在的时代好,视频的时代,我们可以在视频网站找最标准的发音。

论执念

我对测试驱动开发有一中执念,我相信它能让我的应用更稳定,让我的开发节奏很愉快。之所以有这种执念,一方面是它的确给过我这种感觉,它的鼓吹这也是这样鼓吹的,道理上也是这样的;另一方面,我当前的开发的确不够愉快,应用复杂了就乱。

在 Android 音视频这方面,我尝试去使用测试驱动开发,我花了很多时间,但是就是没有进展,我也知道,对于这种牵扯到框架的,状态复杂的,不好检验输出的,的确不是和测试驱动。但是我就是想用它,原因是我没有其他的救命稻草。这就是执念吧。而且随着我投入在测试驱动开发上的时间越多,我就越想使用它。

有时候,对一个东西投入了越多,就会越舍不得它,这在经济上叫沉默成本,你的投入,与要不要继续使用它,无关。在心理学上,这是一种缺陷。所以,最终,我还是在音视频方面放弃使用它,寄希望于写业务代码的时候使用它。

但是呢,犹太人的智慧会中,有这么一个: 什么是爱,就是我为你付出了,你对我就是特殊的,这就是爱。这个有点类似《小王子》说的: 小王子为玫瑰浇水,为它捉虫,所以那个玫瑰对它来说就是特殊的,是独一无二的。这么说来,人爱的也不过是自己,不过这实在是无可厚非,实在是自然而然。

洗冷水澡

最近看到一个视频,说早上的四个习惯,其中说道早上洗冷水澡,可以让自己有一种信念,觉得自己这一天可以战胜一切。这与《意志力》那本书里的理论矛盾,它里面说自制力是会消耗的,洗个冷水澡可能会消耗很多,但是它里面也说到,做决定才会消耗意志力,养成习惯就不需要做决定了。不过另一方面我也可以理解它, 洗冷水澡会让人产生米之自信,正像我骑行拉萨后觉得信心慢慢一样。

所以,我想说的是,我们需要告诉自己,或者证明自己,让自己相信自己,不怀疑自己,然后才能勇往直前。当然,既要勇往直前,又要在某个时间反思自己。在我自己的情况来看,我总做不好,又时候,写代码写文章的时候,止步不前,总在想这样不行那样不行。但有时候,也会行云流水,但是写完,就不想在返回看了。

这叫话术?

冥想所得,每一篇文章会有一个核心思想,我们将一个实现自己利益的一片文章打散,然后融入到另一篇文章中,而这篇文章是有利于他人的,所以他人看了就吸收了,达到了自己的目的。这很阴谋论呀。

终于把临时记录清理了。舒坦:)

每天一点点音视频_bmp了解

BMP 文件格式,也被称作 位图图像文件(bitmap image file), 设备独立图像(DIB), 或者就是简单的叫做 位图(bitmap)。

BMP 文件格式能够存储二维数字图像,支持单色,彩色,支持几种颜色格式(color depth,每个像素使用多少位表示),可以选择是否压缩,是否有 alpha 通道,是否支持 color profile。

DIP

微软为了解决不同设备,不同应用之间图片数据的交换,定义了设备独立的图像文件格式,就是这个 BMP

文件结构

从大的方面来说, 文件分为两大部分, 头部的固定大小的数据结构,剩下的是可变大小的, 但是是有头部预先决定它的大小。所谓的不变是说所有的 BMP 文件的这部分大小都不变,可变意味着不同的文件这一部分会变。

一个比较蛋疼的问题是,文件的格式在长期使用中是不断在发展的进化的,所以就会有很多不同版本的结构,解析程序需要做兼容,这就高的很复杂了。

下面的表格是 BMP 文件结构的概括:

结构名字 | 是否必须 | 大小 | 用途 | 备注
-|-|-|-|-|-
BMP 文件头 | 是 | 14字节 | 存储图像文件的通用信息 | 在加载到内存后不需要了
DIB 头 | 是 | 固定大小(但是有7个不同的版本) | 存储位图的详细信息,定义像素格式 | 如果有 BMP 文件头,紧随其后,没有的话,就自己打头
额外的位掩码 | 否 | 3或4个双字(即12或16个字节) | 定义像素格式 | 只在DIP头是BITMAPINFOHEADER,并且压缩算法是BI_BITFIELDS 或 BI_ALPHABITFIELDS
颜色标 | 可是可否 | 可变大小 | 定义像素使用到的颜色 | 在颜色深度小于8位时必须
Gap1 | 否 | 可变大小 | 对齐结构 |
像素数据 | 是 | 可变大小 | 定义实际的像素数据 | 每一行的数据会通过留空字节来对齐到4个字节的倍数
Gap2 | 否 | 可变大小 | 对齐结构 |
ICC | 否 | 可变大小 | 为颜色管理定义颜色配置(color manager, 以后会说) | 也可以包含一个包含颜色配置的外部文件的路径

title: 每天一点点音视频_bmp_文件头

每天一点点音视频_Bitmap了解

  • 音视频
  • Bitmap
  • ✮ ✮ ✮
    categories:
  • 编程

提到 Bitmap, 我作为一个 Android 程序员首先想到的是 Android Sdk 里的一个类 Bitmap, 它是一块内存,存储未压缩的图片数据, 这个类可以指定像素的格式,也就是每个像素使用多少位来存储, 比如: ARGB_8888 表示按照 alpha, red, green, blue 的顺序,每个通道 8 位。同时它也提供了压缩图片并保存到存储设备上的方法。

Bitmap 还可以是一种文件格式, 文件后缀是 .bmp, 它是微软定义的, 用来解决图片数据在不同设备和应用间的传递, 就如 .jpg, .png 等图片格式,不过它们对图片进行了压缩, 而 .bmp 文件没有对图片压缩。

但是,当我在维基百科上查它的意思时,我震惊了,不过可能是大惊小怪。

维基百科上对它的解释是: 一个 bitmap 是从某一个领域到 位的映射。

这个解释之所以让我震惊,是因为它让这个概念的抽象层次上升了太多,我从没想过。不过仔细想想,好像也遇到过 bitmap 不表示图片的时候。

实际上,从这个词的字面上来看, bit map, 位图, 也可以理解它的抽象概念,但是因为平时看到的一般都是图片,就把这个词限定为图片内存了。

明天: 每天一点点音视频_bmp了解

每天一点点_Android_Graphics_总结

至此, 把 AOSP 上 图像 部分 的文档看了, 忽略了底层驱动的实现部分,一些我之前没有了解的部分,现在也不想了解的部分。

再来最开始的这个图片:

上面的图像流生产者, 下面的图像流消费者。

中间由 Surface 作为传递者, 同时 windowManager 提供图像现实的一些附加信息,比如位置,格式等等。

Surface 是 BufferQueue 上层的 API, 一个代表。

SurfaceView 将 BufferQueue 一端连接 SurfaceFlinger 作为消费者,另一端作为生产者,绘制。可以通过 SurfaceHolder 传给 CameraPreview, MediaPlayer, EGL 等等。

TextureView 在作用上跟 Surface 类似,只不过实现上不同, SurfaceView 使用了单独的 Window, 单独的 Window 以为着单独的 Surface, 单独的 BufferQueue, 单独的位置参数等, 直接通向 SurfaceFlinger,效率会高,但是与页面其他元素的交互性变差了,因为与他们不再一个 View 树。TextureView 则相反, 与其他 View 在同一个 View 树, 图像数据会合成到 View 树的 Surface 上。

EGLSurface 是属于 OpenGL 的概念, 由 EGL 在 Android 平台实现,从而可以将 OpenGL 绘制的图像传给 Android 系统。EGL 类似两个系统之间搭桥的。

上面的 EGLSurface 负责将 OpenGL 的图像传到 Android。而SurfaceTexture 负责将 Android 的 Surface 传给 OpenGL, 成为了一个 Texture, 从而可以进行 OpenGL 操作。

完工。

换换口味,该研究一下格式,协议啥的了,比较讨厌的这些东西,人家说做自己害怕的事情能让自己更快的提高。但是我觉的,关键是能不能自己害怕的事情变得不那么害怕,有趣一点。

明天: 每天一点点音视频_Bitmap了解