yunomuのブログ

趣味のこと

JPEGを読む(4) ストリームを読む準備

 本格的に復号していく前に、デコーダのインタフェースと補助関数を作る。既にちょっとコードに書いているけど。

外部インタフェース

 外部インタフェースは一旦こういう感じで。出力はまだどういう形式にするか考えていないので一旦無し。

import (
    "bufio"
    "io"
)

type Decoder struct {
    r *bufio.Reader
}

func NewDecoder(r io.Reader) *Decoder {
    return &Decoder{
        r: bufio.NewReader(r),
    }
}

func (d *Decoder) Decode() error {
    // TODO

    return nil 
}

 Decoderには*bufio.Readerを持たせている。おそらく入力ストリームをバイト単位で読むことが多くなると思われるので、bufio.Reader#ReadByteを使いたい。

マーカー

 JPEGファイルのマーカーも定義しておく。これはもうTable B.1を根性で書き写す。

import "fmt"

type Marker byte

const (
    Marker_SOF0   Marker = 0xC0
    Marker_SOF1          = 0xC1
    Marker_SOF2          = 0xC2
    Marker_SOF3          = 0xC3
    Marker_SOF5          = 0xC5
    Marker_SOF6          = 0xC6
    Marker_SOF7          = 0xC7
    Marker_JPG           = 0xC8
    Marker_SOF9          = 0xC9
    Marker_SOF10         = 0xCA
    Marker_SOF11         = 0xCB
    Marker_SOF13         = 0xCD
    Marker_SOF14         = 0xCE
    Marker_SOF15         = 0xCF
    Marker_DHT           = 0xC4
    Marker_DAC           = 0xCC
    Marker_RST_n         = 0xD0
    Marker_SOI           = 0xD8
    Marker_EOI           = 0xD9
    Marker_SOS           = 0xDA
    Marker_DQT           = 0xDB
    Marker_DNL           = 0xDC
    Marker_DRI           = 0xDD
    Marker_DHP           = 0xDE
    Marker_EXP           = 0xDF
    Marker_APP_n         = 0xE0
    Marker_JPG_n         = 0xF0
    Marker_COM           = 0xFE
    Marker_TEM           = 0x01

    Marker_FF            = 0x00
    Marker_Prefix        = 0xFF
)

func (m Marker) String() string {
    return fmt.Sprintf("0xFF%02X", int(m))
}

 ここでもおそらく入力ストリームをバイト単位で読むことを想定して、マーカーの値は先頭の0xFFを取り除いた1バイトだけを記録しておく。最後の2つは厳密にはマーカーではないけど、Prefix部分を表すMarker_Prefix = 0xFFと、データの中に出現する0xFFを表現するためのエスケープ用の疑似マーカー0xFF00のための値Marker_FF = 0x00も用意しておく。デバッグやエラー出力に便利なのでMarker#Stringも一応書いておく。というかこれが書きたくてtype Markerを定義した。なにかと便利なので。

マーカーと1バイトのデータを読む

 次はストリームのデータを読む補助メソッドを作る。マーカーの定義により、1バイトのデータを読んだ時の挙動は以下のようになる。

  1. 1バイトを読む
  2. 読んだ値が0xFFでない場合は、値をそのままデータとして返却する
  3. 1バイトを読む
  4. 読んだ値が0x00の場合は、0xFFをデータとして返却する
  5. それ以外の場合は、読んだ値をマーカーとして返却する

 実装するとこうなる。

func (d *Decoder) readByteMarker() (byte, Marker, error) {
    b, err := d.r.ReadByte()
    if err != nil {      
        return 0, 0, err 
    }
    
    if b != Marker_Prefix {
        return b, 0, nil
    }

    m, err := d.r.ReadByte()
    if err != nil {
        return 0, 0, err
    }

    if m == Marker_FF {
        return b, 0, nil
    }

    return 0, Marker(m), nil
}

 返却値が0, 0, nilになる場合が複数あるように見えるが、0xFF00となるマーカーは存在しないのでこれで成立している。最後のマーカーを返却する時にマーカーとして有効な値になっているかどうかをチェックした方が良いかもしれない。しなくても後でありえない位置にありえないマーカーが来るとエラーになるはずなので、特にここでチェックする必要も無いような予感はする。

 マーカーは突然出現するわけではなく、読むべきタイミングというのは予想できる。つまり基本的には「次はマーカーがくるはず」「次はマーカーは来ないはず」と思ってストリームを読む。なので以下の2つのメソッドを用意しておく。

import "errors"

var (
    ErrUnexpectedByte = errors.New("unexpected byte")
    ErrUnexpectedMarker = errors.New("unexpected marker")
)

func (d *Decoder) readByte() (byte, error) {
    b, m, err := d.readByteMarker()
    if err != nil {
        return 0, err
    }

    if m != 0 {
        return 0, ErrUnexpectedMarker
    }

    return b, nil
}

func (d *Decoder) readMarker() (Marker, error) {
    b, m, err := d.readByteMarker()
    if err != nil {
        return 0, err
    }

    if m == 0 {
        return 0, ErrUnexpectedByte
    }

    return m, nil
}

複数のバイト値や数値

 複数のバイトを一気に読むメソッド。io.Reader#Readとかで一気に読みたいところだけど、マーカーや0xFF00が絡んでくる可能性があるので結局1バイトずつ読むことになる。

func (d *Decoder) readBytes(n int) ([]byte, error) {
    var ret []byte

    for i := 0; i < n; i++ {
        b, err := d.readByte()
        if err != nil {
            return nil, err
        }

        ret = append(ret, b)
    }

    return ret, nil
}

 Annex Fを見たところ、テーブルのほとんどのフィールドは8ビットか16ビットの数値として解釈されるようなので、uint8, uint16の単位で読むメソッドも作っておく。さっそくDecoder#readBytesが役に立った。

func (d *Decoder) readUint8() (uint8, error) {
    b, err := d.readByte()
    if err != nil {
        return 0, err
    }

    return uint8(b), nil
}


func (d *Decoder) readUint16() (uint16, error) {
    bs, err := d.readBytes(2)
    if err != nil {
        return 0, err
    }

    return (uint16(bs[0]) << 8) | uint16(bs[1]), nil
}

読み戻す

 こういうパーサを書く時はトークンを1つ読まなかったことにしたり(unread, unget)、ストリームを消費せずに先頭のトークンを読む(peek)処理があると、最終的に必要かどうかはわからないけどデバッグ中とかでは役に立つ。というか実際に後で役に立ったので作ろう。peekはあとでまた読むのが面倒なので一旦unreadにしておこう。最初に書いたDecoderDecoder#readByteMarkerに手を入れる。

 まずDecoderに状態を追加する。

type Decoder struct {
    r *bufio.Reader

    // unread用のフィールド
    prevByte   byte
    prevMarker Marker
    unreaded   bool
}

 Decoder#readByteMarkerでは、値を返す時に返却値をprevByte,prevMarkerに保存し、unreadedtrueの時は保存した値を返すようにする。

func (d *Decoder) readByteMarker() (byte, Marker, error) {
    if d.unreaded {
        d.unreaded = false
        return d.prevByte, d.prevMarker, nil
    }

    b, err := d.r.ReadByte()
    if err != nil {
        return 0, 0, err
    }

    if b != Marker_Prefix {
        d.prevByte = b
        d.prevMarker = 0
        return b, 0, nil
    }

    m, err := d.r.ReadByte()
    if err != nil {
        return 0, 0, err
    }

    if m == Marker_FF {
        d.prevByte = b
        d.prevMarker = 0
        return b, 0, nil
    }

    d.prevByte = 0
    d.prevMarker = Marker(m)
    return 0, Marker(m), nil
}

 unreadの処理本体はこれでいい。

func (d *Decoder) unread() {
    d.unreaded = true
}

 これで今のところ必要な素材は揃ったはず。