我正在编写一些代码来解析 Java 中的 RIFF 容器(出于教育目的,我知道有大量的 Java 库可以加载.wav
或其他 RIFF 文件)。我的设计目标是尽可能地坚持格式。
根据维基百科,基本的 RIFF 格式非常简单:
- 4 个字节:该块的 ASCII 标识符(例如“fmt”和“data”;注意“fmt”中的空格)。
- 4 个字节:一个无符号、小端序的 32 位整数,具有该块的长度(除了该字段本身和块标识符)。
- 可变大小字段:块数据本身,大小在前一个字段中给出。
- 如果块的长度不均匀,则填充字节。
我目前正在努力处理可变大小字段的表示。到目前为止,我想出的最好的看起来是这样的。首先是 abstract class BaseFormat
,它是所有格式解析类的基础:
abstract class BaseFormat {
BaseFormat(MyStream stream) {
this.stream = stream;
}
MyStream stream;
public MyStream stream() { return stream; }
abstract public void read() throws IOException;
}
它只是存储对流的引用,并充当read
要实现的方法的接口。然后有两个主要的格式类:
public class Riff extends BaseFormat {
public Riff(MyStream stream) {
super(stream);
}
@Override
public void read() throws IOException {
chunks = new ArrayList<Chunk>();
while (!stream.eof()) {
Chunk c = new Chunk(stream);
c.read();
chunks.add(c);
}
}
private ArrayList<Chunk> chunks;
public ArrayList<Chunk> chunks() { return chunks; }
}
public class Chunk extends BaseFormat {
Chunk(MyStream stream) {
super(stream);
}
public void read() throws IOException {
tag = stream.getAsciiString(4);
len = stream.getInt();
MyStream substream = stream.getBytesAsSubstream(len);
switch (tag) {
case "FMT1":
Fmt1Body b1 = new Fmt1Body(stream);
b1.read();
body = b1;
break;
case "FMT2":
Fmt2Body b2 = new Fmt2Body(stream);
b2.read();
body = b2;
break;
// etc, etc, many various subformats
}
if (len % 2 == 1)
stream.skipBytes(1);
}
private String tag;
public String tag() { return tag; }
private int len;
public int len() { return len; }
private Object body;
public Object body() { return body; }
}
那里没有什么花哨的东西。为简洁起见,我没有发布MyStream
课程,它是一个非常简单的包装器InputStream
,具有有用的实用功能,如getInt
, getByte
,getAsciiString
等。
每一种 RIFF 格式都有自己的类别。整个 RIFF 文件是Riff
,它依次尝试将块读取为Chunk
类,然后读取块头 + 正文,然后魔术发生:一个大switch
内部Chunk.read()
选择要应用块内的哪个格式类 - 即Fmt1Body
,Fmt2Body
等。
它可以工作,您实际上可以用它读取 RIFF 文件。我遇到的问题是所有不同的块格式都由没有共同点的不同类表示。因此,当处理在内存中解析的文件时,我最终会进行大量类型检查和向上转换,例如:
for (Chunk chunk : riff.chunks()) {
Object cbody = chunk.body();
if (cbody instanceof Fmt1Body) {
Fmt1Body body = (Fmt1Body) cbody;
// do something with FMT1 chunk body using `body`, i.e.
System.out.println("title = " + body.getTitle());
} else if (cbody instanceof Fmt2Body) {
Fmt2Body body = (Fmt2Body) cbody;
// do something with FMT2 chunk body using `body`, i.e.
System.out.println(body.getWidth() + "x" + body.getHeight());
}
}
等等等等。这在许多函数式语言中是正常的,但我想这是在 Java 中做事的一种不好的方式。有没有更好的方法来表示内存中的 RIFF 容器而不诉诸Object
+ 类型检查 + 向上转换(或者像BaseFormat
这样的一些等效无用的接口无论如何都需要转换)?
子格式类的来源
对于那些对子格式类感兴趣的人,这里有一些示例来证明它们除了使用stream
andread()
方法之外没有任何共同点:
public class Fmt1Body extends BaseFormat {
Fmt1Body(MyStream stream) {
super(stream);
}
@Override
public void read() throws IOException {
title = stream.getAsciiString(64);
artist = stream.getAsciiString(64);
album = stream.getAsciiString(64);
}
private String title;
private String artist;
private String album;
public String getTitle() { return title; }
public String getArtist() { return artist; }
public String getAlbum() { return album; }
}
public class Fmt2Body extends BaseFormat {
Fmt2Body(MyStream stream) {
super(stream);
}
@Override
public void read() throws IOException {
width = stream.getInt();
height = stream.getInt();
bitmap = stream.getBytes(width * height);
}
private int width;
private int height;
private byte[] bitmap;
public int getWidth() { return width; }
public int getHeight() { return height; }
public byte[] getBitmap() { return bitmap; }
}