项目中使用Android解析JPEG码流,在此记录一下思路,一切以下方的实例分析图片数据信息编写。
目录
JPEG图片格式
1.SOI(0xFFD8):图片开始段
2.APP0(0xFFE0)
3.APPn(0xFFEn)可选
4.DQT(0xFFDB):量化表段
5.SOF0(0xFFC0):帧开始段
6.DHT(0xFFC4):Huffman表段
7.DRI可选
8.SOS(0xFFDA):扫描开始段
9.压缩图像数据
10.EOI(0xFFD9):图像结束段
实例分析:
具体部分代码如下:
JPEG图片格式
1.SOI(0xFFD8):图片开始段
2.APP0(0xFFE0)
名称 | 字节数 | 值 | 说明 |
段标识 | 1 | FF | |
段类型 | 1 | E0 | |
段长度 | 2 | 00 10 | |
标识符 | 5 | 4A 46 49 46 00 | “JFIF0” |
主版本号 | 1 | 01 | |
次版本号 | 1 | 01 | |
密度单位 | 1 | 01 | 0表示无单位;1表示点数/英寸;2表示点数/厘米 |
X方向像素密度 | 2 | 00 78 | 水平方向像素密度 |
Y方向像素密度 | 2 | 00 78 | 竖直方向像素密度 |
缩略图水平像素数目 | 1 | 00 | |
缩略图垂直像素数目 | 1 | 00 | |
缩略图RGB位图 | | | 3×n,n=缩略图像素总数=缩略图X像素×缩略图Y像素;缩略图X像素和缩略图Y像素的值都大于0,才有该值 |
3.APPn(0xFFEn)可选
名称 | 字节数 | 值 | 说明 |
应用程序保留标记n | 2 | 0xFFE1~0xFFF | n=1~15,记录地点、时间等信息 |
数据段长度 | 2 | | |
4.DQT(0xFFDB):量化表段
名称 | 字节数 | 值 | 说明 |
段标识 | 1 | FF | JPEG文件一般有2个DQT段,Y值(亮度)1个, C值(色度)1个。 |
段类型 | 1 | DB | 一个DQT段可以包含多个QT, 每个都有自己的信息字节 |
段长度 | 2 | 00 43 | |
量化表信息 | 1 | 00 | 量化表信息:0-3位;QT号:4-7位(0=8bit;否则=16bit) |
量化表内容 | 64 | 08 06 ... | n = 64 × QT精度的字节数,即64×1 |
5.SOF0(0xFFC0):帧开始段
名称 | 字节数 | 值 | 说明 |
段标识 | 1 | FF | |
段类型 | 1 | C0 | |
段长度 | 2 | 00 11 | = 8+组件数量×3 |
精度 | 1 | 08 | |
图片高度 | 2 | 00 14 | |
图片宽度 | 2 | 00 14 | |
组件数量 | 1 | 03 | 1=灰度图,3=YCbcr或YIQ彩色图,4=CMYK彩色图 |
//Y颜色分量 | | | |
组件ID | 1 | 01 | 1=Y,2=Cb,3=Cr,4=I,5=Q |
采样系数 | 1 | 22 | 0~3位:垂直采样系数,4~7位:水平采样系数 |
量化表号 | 1 | 00 | |
//Cb颜色分量 | | | |
组件ID | 1 | 02 | |
采样系数 | 1 | 11 | |
量化表号 | 1 | 01 | |
//Cr颜色分量 | | | Y采样是逐点采样,CbCr都是隔点采样,这就是标准的YUV422的数据。 |
组件ID | 1 | 03 | |
采样系数 | 1 | 11 | |
量化表号 | 1 | 01 | |
6.DHT(0xFFC4):Huffman表段
名称 | 字节数 | 值 | 说明 |
段标识 | 1 | FF | |
段类型 | 1 | C4 | |
段长度 | 2 | 00 1F | |
HT信息 | 1 | 00 | 0~3位:HT号;4位:HT类型(0=DC表,1=AC表);5~7位:必须=0 |
HT位表 | 16 | ... | 16个数的和≤256 |
HT值表 | n | ... | n=位表16个数的和 |
HT位表:00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 |
HT值表:00 01 02 03 04 05 06 07 08 09 0A 0B |
数量 | 值 |
00 | |
01 | 00 |
05 | 01 02 03 04 05 |
01 | 06 |
01 | 07 |
01 | 08 |
01 | 09 |
01 | 0A |
01 | 0B |
00 | |
00 | |
00 | |
00 | |
00 | |
00 | |
00 | |
注:此处有四个DHT表,只举一个例子。
7.DRI可选
8.SOS(0xFFDA):扫描开始段
名称 | 字节数 | 值 | 说明 |
段标识 | 1 | FF | |
段类型 | 1 | DA | |
段长度 | 2 | 00 0C | |
扫描组件数量 | 1 | 03 | |
//组件1 | | | |
01 | 1 | 1=Y,2=Cb,3=Cr,4=I,5=Q | |
00 | 1 | Huffman表号 | 0~3位:AC表号,4~7位:DC表号 |
//组件2 | | | |
02 | 1 | | |
11 | 1 | | |
//组件3 | | | |
03 | 1 | | |
11 | 1 | | |
| 3 | 00 3F 00 | 意义不明确,忽略 |
9.压缩图像数据
10.EOI(0xFFD9):图像结束段
实例分析:
使用UltraEdit软件将图片转为16进制形式(随便截取了一张图片,20像素×20像素)
具体部分代码如下:
由于硬件传输至APP中是有包头,包头数据信息等一大堆乱七八糟的东西的,而且我做的这个项目,硬件端协议(如上图所示,SOI后边就是JPEG的格式了)对我来说不太明确的(但是还好我是硬件出身的,是懂硬件怎么传输的),一步一步摸索过来。比如说整个包长度4185字节,包头数据信息89字节,剩下的其实是4096,也就是说硬件每次传输4K字节,算一下就知道了(我真是个大SB),然后数据(FF D8 ...)我还不知道怎么处理,后来才有了前边的JPEG数据结构分析。
还有一个事就是,我以为图片解码是要我自己编程解码,后来是试试看的想法,也是懂了JPEG格式,将数据流写入文件,然后将文件变成JPEG格式,我觉得就可以显示图片了。后来去对应路径去验证,确实可以,只能说手机性能过剩,可能是硬解码牛逼。
//获取数据流
inputStream = socket.getInputStream();
len = inputStream.read(dwMagicCode);//这个是将数据流读取数据到数组,我这个写法是把dwMagicCode读满
while (true) {
if (new String(dwMagicCode).equals("ZAKJ")) {//包头识别
len = inputStream.read(wSizeArray);
wSize = Integer.parseInt(ConvertDataToInt(wSizeArray, 0), 16);//包长度
Log.d("TReqReceive+wSize", String.valueOf(wSize));
len = inputStream.read(wCmdIdArray);
wCmdId = Integer.parseInt(ConvertDataToInt(wCmdIdArray, 0), 16);//命令
buffer = new byte[wSize - 12];
len = inputStream.read(buffer);
if(wCmdId == enNetCmd.CMD_PostSnapFileData){//命令码3024
PictureData = new byte[wSize - 89];
FileName = new String(buffer, 25, 6);//文件名称
System.arraycopy(buffer, 77, PictureData, 0, wSize - 89);
File newfile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/ftp" +"/"+FileName+".JPG");//根据文件名称及路径,创建文件
try {
if (!newfile.exists()){
newfile.createNewFile();
}
FileOutputStream fos = new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/ftp" +"/"+FileName+".JPG",true);
fos.write(PictureData);//数据流输出
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}