yunomuのブログ

趣味のこと

JPEGを読む

 画像や動画のエンコード/デコードの理屈はわかるけど実際どうなっているかはよくわかってないのでとりあえずJPEGファイルの仕様を調べてみる。

資料 https://www.w3.org/Graphics/JPEG/itu-t81.pdf

 JISの日本語の資料もあったけど(JISX4301)、閲覧が面倒くさすぎて英語で読む方がまだマシであった。

 上から読んでいっても理解できなさそうなのでJPEGファイルのフォーマットから読みつつ、実際にJPEGファイルを読むコードも書いていく。"Annex B: Compressed data formats"から。

 基本的にはデータやパラメータやハフマン符号のテーブルやらのフィールドが並んでいて、それぞれのフィールドは先頭2バイトのMarkerと呼ばれるマジックナンバーで判別できるようになっている。それでフィールドの中にヘッダやペイロードがあったりなかったりして、最後に終端のMarkerがついて終わり。

 このMarkerというのは必ず0xFFから始まる2バイトで、Annex Bの最初の方に全部の定義が書いてある。JPEGファイル内で0xFFから始まる2バイトはMarkerしかなくて、データ内に偶然に出現する0xFF0xFF00エスケープされる。ということらしい。

 それならとりあえずMarkerだけを取り出して並べて見るとなんとなくの構造がわかりそう。こんなかんじで↓

package main

import (
    "bufio"
    "io"
    "log/slog"
    "os"
    "strconv"
    "strings"
)

func marker(i uint64) string {
    return "0xFF" + strings.ToUpper(strconv.FormatUint(uint64(i), 16))
}

func main() {
    r := bufio.NewReader(os.Stdin)

    for {
        b, err := r.ReadByte()
        if err == io.EOF {
            slog.Info("FINISHED")
            return
        } else if err != nil {
            slog.Error("ReadByte", "err", err)
            return
        }

        if b != 0xFF {
            continue
        }

        m, err := r.ReadByte()
        if err == io.EOF {
            slog.Error("unexpected EOF")
            return
        } else if err != nil {
            slog.Error("read marker", "err", err)
            return
        }

        if m == 0x0 {
            continue
        }

        slog.Info("marker", "val", marker(uint64(m)))
    }
}

 これで適当なファイルを読む。

 go run main.go < test.jpg
INFO marker val=0xFFD8
INFO marker val=0xFFDB
INFO marker val=0xFFC0
INFO marker val=0xFFC4
INFO marker val=0xFFDA
INFO marker val=0xFFD9
INFO FINISHED

 意外と少ない。これを表B.1と見比べてみると

  • 0xFFD8 Start of image : ファイルの先頭
  • 0xFFDB Define quantization table(s) : そのまんま、量子化係数のテーブル
  • 0xFFC0 Baseline DCT : SOF(start of frame)。圧縮の方式はbaseline(progressiveやlosslessじゃないやつ)で、non-differentialで離散コサイン変換(DCT)。
  • 0xFFC4 Define Huffman table(s) : これもそのまんま、ハフマン符号表。
  • 0xFFDA Start of scan : Figure B.2からみるとここに符号化データが入ってる。
  • 0xFFD9 End of image : ファイルの終端

 ハフマン符号化は元データのヒストグラムに偏りがある方が圧縮効率が良くなるので、元画像の特性によってアルゴリズムを変更できるようになっている。

 次はこの中のどれかのフィールドの中身を見ていこう。