1

我正在编写一些代码来解析 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这样的一些等效无用的接口无论如何都需要转换)?

子格式类的来源

对于那些对子格式类感兴趣的人,这里有一些示例来证明它们除了使用streamandread()方法之外没有任何共同点:

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; }
}
4

0 回答 0