├── .github └── workflows │ └── go.yml ├── LICENSE ├── README.md ├── decode.go ├── decode_test.go ├── encode.go ├── encode_test.go ├── go.mod ├── header.go └── otest.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build 22 | run: go build 23 | 24 | - name: Test 25 | run: go test 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | © 2016 Steve McCoy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Package ogg implements encoding and decoding of OGG streams as defined in 2 | http://xiph.org/ogg/doc/rfc3533.txt 3 | and 4 | http://xiph.org/ogg/doc/framing.html . 5 | 6 | [![GoDoc](https://godoc.org/github.com/mccoyst/ogg?status.svg)](https://godoc.org/github.com/mccoyst/ogg) 7 | 8 | [![Test Coverage](https://img.shields.io/badge/coverage-100.0%25-brightgreen.svg)](https://gocover.io/github.com/mccoyst/ogg) 9 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | // © 2016 Steve McCoy under the MIT license. See LICENSE for details. 2 | 3 | package ogg 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "errors" 9 | "io" 10 | "strconv" 11 | ) 12 | 13 | // A Decoder decodes an ogg stream page-by-page with its Decode method. 14 | type Decoder struct { 15 | // buffer for packet lengths, to avoid allocating (mss is also the max per page) 16 | lenbuf [mss]int 17 | r io.Reader 18 | buf [maxPageSize]byte 19 | } 20 | 21 | // NewDecoder creates an ogg Decoder. 22 | func NewDecoder(r io.Reader) *Decoder { 23 | return &Decoder{r: r} 24 | } 25 | 26 | // A Page represents a logical ogg page. 27 | type Page struct { 28 | // Type is a bitmask of COP, BOS, and/or EOS. 29 | Type byte 30 | // Serial is the bitstream serial number. 31 | Serial uint32 32 | // Granule is the granule position, whose meaning is dependent on the encapsulated codec. 33 | Granule int64 34 | // Packets are the raw packet data. 35 | // If Type & COP != 0, the first element is 36 | // a continuation of the previous page's last packet. 37 | Packets [][]byte 38 | } 39 | 40 | // ErrBadSegs is the error used when trying to decode a page with a segment table size less than 1. 41 | var ErrBadSegs = errors.New("invalid segment table size") 42 | 43 | // ErrBadCrc is the error used when an ogg page's CRC field does not match the CRC calculated by the Decoder. 44 | type ErrBadCrc struct { 45 | Found uint32 46 | Expected uint32 47 | } 48 | 49 | func (bc ErrBadCrc) Error() string { 50 | return "invalid crc in packet: got " + strconv.FormatInt(int64(bc.Found), 16) + 51 | ", expected " + strconv.FormatInt(int64(bc.Expected), 16) 52 | } 53 | 54 | var oggs = []byte{'O', 'g', 'g', 'S'} 55 | 56 | // Decode reads from d's Reader to the next ogg page, then returns the decoded Page or an error. 57 | // The error may be io.EOF if that's what the Reader returned. 58 | // 59 | // The buffer underlying the returned Page's Packets' bytes is owned by the Decoder. 60 | // It may be overwritten by subsequent calls to Decode. 61 | // 62 | // It is safe to call Decode concurrently on distinct Decoders if their Readers are distinct. 63 | // Otherwise, the behavior is undefined. 64 | func (d *Decoder) Decode() (Page, error) { 65 | hbuf := d.buf[0:headsz] 66 | b := 0 67 | for { 68 | _, err := io.ReadFull(d.r, hbuf[b:]) 69 | if err != nil { 70 | return Page{}, err 71 | } 72 | 73 | i := bytes.Index(hbuf, oggs) 74 | if i == 0 { 75 | break 76 | } 77 | 78 | if i < 0 { 79 | const n = headsz 80 | if hbuf[n-1] == 'O' { 81 | i = n - 1 82 | } else if hbuf[n-2] == 'O' && hbuf[n-1] == 'g' { 83 | i = n - 2 84 | } else if hbuf[n-3] == 'O' && hbuf[n-2] == 'g' && hbuf[n-1] == 'g' { 85 | i = n - 3 86 | } 87 | } 88 | 89 | if i > 0 { 90 | b = copy(hbuf, hbuf[i:]) 91 | } 92 | } 93 | 94 | var h pageHeader 95 | _ = binary.Read(bytes.NewBuffer(hbuf), byteOrder, &h) 96 | 97 | if h.Nsegs < 1 { 98 | return Page{}, ErrBadSegs 99 | } 100 | 101 | nsegs := int(h.Nsegs) 102 | segtbl := d.buf[headsz : headsz+nsegs] 103 | _, err := io.ReadFull(d.r, segtbl) 104 | if err != nil { 105 | return Page{}, err 106 | } 107 | 108 | // A page can contain multiple packets; record their lengths from the table 109 | // now and slice up the payload after reading it. 110 | // I'm inclined to limit the Read calls this way, 111 | // but it's possible it isn't worth the annoyance of iterating twice 112 | packetlens := d.lenbuf[0:0] 113 | payloadlen := 0 114 | more := false 115 | for _, l := range segtbl { 116 | if more { 117 | packetlens[len(packetlens)-1] += int(l) 118 | } else { 119 | packetlens = append(packetlens, int(l)) 120 | } 121 | 122 | more = l == mss 123 | payloadlen += int(l) 124 | } 125 | 126 | payload := d.buf[headsz+nsegs : headsz+nsegs+payloadlen] 127 | _, err = io.ReadFull(d.r, payload) 128 | if err != nil { 129 | return Page{}, err 130 | } 131 | 132 | page := d.buf[0 : headsz+nsegs+payloadlen] 133 | // Clear out existing crc before calculating it 134 | page[22] = 0 135 | page[23] = 0 136 | page[24] = 0 137 | page[25] = 0 138 | crc := crc32(page) 139 | if crc != h.Crc { 140 | return Page{}, ErrBadCrc{h.Crc, crc} 141 | } 142 | 143 | packets := make([][]byte, len(packetlens)) 144 | s := 0 145 | for i, l := range packetlens { 146 | packets[i] = payload[s : s+l] 147 | s += l 148 | } 149 | 150 | return Page{h.HeaderType, h.Serial, h.Granule, packets}, nil 151 | } 152 | -------------------------------------------------------------------------------- /decode_test.go: -------------------------------------------------------------------------------- 1 | // © 2016 Steve McCoy under the MIT license. See LICENSE for details. 2 | 3 | package ogg 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "math/rand" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestBasicDecode(t *testing.T) { 14 | var b bytes.Buffer 15 | e := NewEncoder(1, &b) 16 | 17 | err := e.EncodeBOS(2, [][]byte{[]byte("hello")}) 18 | if err != nil { 19 | t.Fatal("unexpected EncodeBOS error:", err) 20 | } 21 | 22 | d := NewDecoder(&b) 23 | 24 | p, err := d.Decode() 25 | if err != nil { 26 | t.Fatal("unexpected Decode error:", err) 27 | } 28 | 29 | if p.Type != BOS { 30 | t.Fatal("expected BOS, got", p.Type) 31 | } 32 | 33 | if p.Serial != 1 { 34 | t.Fatal("expected serial 1, got", p.Serial) 35 | } 36 | 37 | if p.Granule != 2 { 38 | t.Fatal("expected granule 2, got", p.Granule) 39 | } 40 | 41 | expect := []byte{ 42 | 'h', 'e', 'l', 'l', 'o', 43 | } 44 | 45 | if len(p.Packets) != 1 { 46 | t.Fatalf("len(p.Packets) = %d", len(p.Packets)) 47 | } 48 | 49 | if !bytes.Equal(p.Packets[0], expect) { 50 | t.Fatalf("bytes != expected:\n%x\n%x", p.Packets[0], expect) 51 | } 52 | } 53 | 54 | func TestBasicMultiDecode(t *testing.T) { 55 | var b bytes.Buffer 56 | e := NewEncoder(1, &b) 57 | 58 | err := e.EncodeBOS(2, [][]byte{[]byte("hello")}) 59 | if err != nil { 60 | t.Fatal("unexpected EncodeBOS error:", err) 61 | } 62 | err = e.Encode(7, [][]byte{[]byte("there")}) 63 | if err != nil { 64 | t.Fatal("unexpected Encode error:", err) 65 | } 66 | 67 | d := NewDecoder(&b) 68 | 69 | p, err := d.Decode() 70 | if err != nil { 71 | t.Fatal("unexpected Decode error:", err) 72 | } 73 | 74 | if p.Type != BOS { 75 | t.Fatal("expected BOS, got", p.Type) 76 | } 77 | 78 | if p.Serial != 1 { 79 | t.Fatal("expected serial 1, got", p.Serial) 80 | } 81 | 82 | if p.Granule != 2 { 83 | t.Fatal("expected granule 2, got", p.Granule) 84 | } 85 | 86 | expect := []byte{ 87 | 'h', 'e', 'l', 'l', 'o', 88 | } 89 | 90 | if len(p.Packets) != 1 { 91 | t.Fatalf("len(p.Packets) = %d", len(p.Packets)) 92 | } 93 | 94 | if !bytes.Equal(p.Packets[0], expect) { 95 | t.Fatalf("bytes != expected:\n%x\n%x", p.Packets[0], expect) 96 | } 97 | 98 | p, err = d.Decode() 99 | if err != nil { 100 | t.Fatal("unexpected Decode error:", err) 101 | } 102 | 103 | if p.Type != 0 { 104 | t.Fatal("expected normal type, got", p.Type) 105 | } 106 | 107 | if p.Serial != 1 { 108 | t.Fatal("expected serial 1, got", p.Serial) 109 | } 110 | 111 | if p.Granule != 7 { 112 | t.Fatal("expected granule 7, got", p.Granule) 113 | } 114 | 115 | expect = []byte{ 116 | 't', 'h', 'e', 'r', 'e', 117 | } 118 | 119 | if len(p.Packets) != 1 { 120 | t.Fatalf("len(p.Packets) = %d", len(p.Packets)) 121 | } 122 | 123 | if !bytes.Equal(p.Packets[0], expect) { 124 | t.Fatalf("bytes != expected:\n%x\n%x", p.Packets[0], expect) 125 | } 126 | } 127 | 128 | func TestMultipacketDecode(t *testing.T) { 129 | var b bytes.Buffer 130 | e := NewEncoder(1, &b) 131 | 132 | err := e.EncodeBOS(2, [][]byte{[]byte("hello"), []byte("there")}) 133 | if err != nil { 134 | t.Fatal("unexpected EncodeBOS error:", err) 135 | } 136 | 137 | d := NewDecoder(&b) 138 | 139 | p, err := d.Decode() 140 | if err != nil { 141 | t.Fatal("unexpected Decode error:", err) 142 | } 143 | 144 | if p.Type != BOS { 145 | t.Fatal("expected BOS, got", p.Type) 146 | } 147 | 148 | if p.Serial != 1 { 149 | t.Fatal("expected serial 1, got", p.Serial) 150 | } 151 | 152 | if p.Granule != 2 { 153 | t.Fatal("expected granule 2, got", p.Granule) 154 | } 155 | 156 | expect := []byte{ 157 | 'h', 'e', 'l', 'l', 'o', 158 | } 159 | 160 | if len(p.Packets) != 2 { 161 | t.Fatalf("len(p.Packets) = %d", len(p.Packets)) 162 | } 163 | 164 | if !bytes.Equal(p.Packets[0], expect) { 165 | t.Fatalf("bytes != expected:\n%x\n%x", p.Packets[0], expect) 166 | } 167 | 168 | expect = []byte{ 169 | 't', 'h', 'e', 'r', 'e', 170 | } 171 | 172 | if !bytes.Equal(p.Packets[1], expect) { 173 | t.Fatalf("bytes != expected:\n%x\n%x", p.Packets[0], expect) 174 | } 175 | } 176 | 177 | func TestBadCrc(t *testing.T) { 178 | var b bytes.Buffer 179 | e := NewEncoder(1, &b) 180 | 181 | err := e.EncodeBOS(2, [][]byte{[]byte("hello")}) 182 | if err != nil { 183 | t.Fatal("unexpected EncodeBOS error:", err) 184 | } 185 | 186 | b.Bytes()[22] = 0 187 | 188 | d := NewDecoder(&b) 189 | 190 | _, err = d.Decode() 191 | if err == nil { 192 | t.Fatal("unexpected lack of Decode error") 193 | } 194 | if bs, ok := err.(ErrBadCrc); !ok { 195 | t.Fatal("exected ErrBadCrc, got:", err) 196 | } else if !strings.HasPrefix(bs.Error(), "invalid crc in packet") { 197 | t.Fatalf("the error message looks wrong: %q", err.Error()) 198 | } 199 | } 200 | 201 | func TestShortDecode(t *testing.T) { 202 | var b bytes.Buffer 203 | d := NewDecoder(&b) 204 | _, err := d.Decode() 205 | if err != io.EOF { 206 | t.Fatal("expected EOF, got:", err) 207 | } 208 | 209 | e := NewEncoder(1, &b) 210 | err = e.Encode(2, [][]byte{[]byte("hello")}) 211 | if err != nil { 212 | t.Fatal("unexpected Encode error:", err) 213 | } 214 | d = NewDecoder(&io.LimitedReader{R: &b, N: headsz}) 215 | _, err = d.Decode() 216 | if err != io.EOF { 217 | t.Fatal("expected EOF, got:", err) 218 | } 219 | 220 | b.Reset() 221 | e = NewEncoder(1, &b) 222 | err = e.Encode(2, [][]byte{[]byte("hello")}) 223 | if err != nil { 224 | t.Fatal("unexpected Encode error:", err) 225 | } 226 | d = NewDecoder(&io.LimitedReader{R: &b, N: int64(b.Len()) - 1}) 227 | _, err = d.Decode() 228 | if err != io.ErrUnexpectedEOF { 229 | t.Fatal("expected ErrUnexpectedEOF, got:", err) 230 | } 231 | } 232 | 233 | func TestBadSegs(t *testing.T) { 234 | var b bytes.Buffer 235 | e := NewEncoder(1, &b) 236 | 237 | err := e.EncodeBOS(2, [][]byte{[]byte("hello")}) 238 | if err != nil { 239 | t.Fatal("unexpected EncodeBOS error:", err) 240 | } 241 | 242 | b.Bytes()[26] = 0 243 | 244 | d := NewDecoder(&b) 245 | _, err = d.Decode() 246 | if err != ErrBadSegs { 247 | t.Fatal("expected ErrBadSegs, got:", err) 248 | } 249 | } 250 | 251 | func TestSyncDecode(t *testing.T) { 252 | var b bytes.Buffer 253 | for i := 0; i < headsz-1; i++ { 254 | b.Write([]byte("x")) 255 | } 256 | b.Write([]byte("O")) 257 | 258 | for i := 0; i < headsz-3; i++ { 259 | b.Write([]byte("x")) 260 | } 261 | b.Write([]byte("Og")) 262 | 263 | for i := 0; i < headsz-5; i++ { 264 | b.Write([]byte("x")) 265 | } 266 | b.Write([]byte("Ogg")) 267 | 268 | e := NewEncoder(1, &b) 269 | 270 | err := e.EncodeBOS(2, [][]byte{[]byte("hello")}) 271 | if err != nil { 272 | t.Fatal("unexpected EncodeBOS error:", err) 273 | } 274 | 275 | d := NewDecoder(&b) 276 | 277 | p, err := d.Decode() 278 | if err != nil { 279 | t.Fatal("unexpected Decode error:", err) 280 | } 281 | 282 | if p.Type != BOS { 283 | t.Fatal("expected BOS, got", p.Type) 284 | } 285 | 286 | if p.Serial != 1 { 287 | t.Fatal("expected serial 1, got", p.Serial) 288 | } 289 | 290 | if p.Granule != 2 { 291 | t.Fatal("expected granule 2, got", p.Granule) 292 | } 293 | 294 | expect := []byte{ 295 | 'h', 'e', 'l', 'l', 'o', 296 | } 297 | 298 | if len(p.Packets) != 1 { 299 | t.Fatalf("len(p.Packets) = %d", len(p.Packets)) 300 | } 301 | 302 | if !bytes.Equal(p.Packets[0], expect) { 303 | t.Fatalf("bytes != expected:\n%x\n%x", p.Packets[0], expect) 304 | } 305 | } 306 | 307 | func TestLongDecode(t *testing.T) { 308 | var b bytes.Buffer 309 | e := NewEncoder(1, &b) 310 | 311 | var junk bytes.Buffer 312 | for i := 0; i < maxPageSize*2; i++ { 313 | c := byte(rand.Intn(26)) + 'a' 314 | junk.WriteByte(c) 315 | } 316 | 317 | err := e.Encode(2, [][]byte{junk.Bytes()}) 318 | if err != nil { 319 | t.Fatal("unexpected Encode error:", err) 320 | } 321 | 322 | d := NewDecoder(&b) 323 | p1, err := d.Decode() 324 | if err != nil { 325 | t.Fatal("unexpected Decode error:", err) 326 | } 327 | if p1.Type != 0 { 328 | t.Fatal("unexpected page type:", p1.Type) 329 | } 330 | if len(p1.Packets) != 1 { 331 | t.Fatalf("len(p1.Packets) = %d", len(p1.Packets)) 332 | } 333 | if !bytes.Equal(p1.Packets[0], junk.Bytes()[:mps]) { 334 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p1.Packets[0], junk.Bytes()[:mps]) 335 | } 336 | 337 | p2, err := d.Decode() 338 | if err != nil { 339 | t.Fatal("unexpected Decode error:", err) 340 | } 341 | if p2.Type != COP { 342 | t.Fatal("unexpected page type:", p1.Type) 343 | } 344 | if len(p2.Packets) != 1 { 345 | t.Fatalf("len(p2.Packets) = %d", len(p2.Packets)) 346 | } 347 | if !bytes.Equal(p2.Packets[0], junk.Bytes()[mps:mps+mps]) { 348 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p2.Packets[0], junk.Bytes()[mps:mps+mps]) 349 | } 350 | 351 | p3, err := d.Decode() 352 | if err != nil { 353 | t.Fatal("unexpected Decode error:", err) 354 | } 355 | if p3.Type != COP { 356 | t.Fatal("unexpected page type:", p1.Type) 357 | } 358 | if len(p3.Packets) != 1 { 359 | t.Fatalf("len(p3.Packets) = %d", len(p3.Packets)) 360 | } 361 | rem := (maxPageSize * 2) - mps*2 362 | if !bytes.Equal(p3.Packets[0], junk.Bytes()[mps*2:mps*2+rem]) { 363 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p3.Packets[0], junk.Bytes()[mps*2:mps*2+rem]) 364 | } 365 | } 366 | 367 | func TestLongMultipacketDecode(t *testing.T) { 368 | var b bytes.Buffer 369 | e := NewEncoder(1, &b) 370 | 371 | var junk bytes.Buffer 372 | for i := 0; i < maxPageSize*2; i++ { 373 | c := byte(rand.Intn(26)) + 'a' 374 | junk.WriteByte(c) 375 | } 376 | 377 | err := e.Encode(2, [][]byte{junk.Bytes()[:50], junk.Bytes()[50:]}) 378 | if err != nil { 379 | t.Fatal("unexpected Encode error:", err) 380 | } 381 | 382 | d := NewDecoder(&b) 383 | p1, err := d.Decode() 384 | if err != nil { 385 | t.Fatal("unexpected Decode error:", err) 386 | } 387 | if p1.Type != 0 { 388 | t.Fatal("unexpected page type:", p1.Type) 389 | } 390 | if len(p1.Packets) != 2 { 391 | t.Fatalf("len(p1.Packets) = %d", len(p1.Packets)) 392 | } 393 | if !bytes.Equal(p1.Packets[0], junk.Bytes()[:50]) { 394 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p1.Packets[0], junk.Bytes()[:50]) 395 | } 396 | if len(p1.Packets[1]) != mps-mss { 397 | t.Fatalf("packet is wrong size: %d vs. %d", len(p1.Packets[1]), mps-mss) 398 | } 399 | if !bytes.Equal(p1.Packets[1], junk.Bytes()[50:50+mps-mss]) { 400 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p1.Packets[1], junk.Bytes()[50:50+mps-mss]) 401 | } 402 | 403 | p2, err := d.Decode() 404 | if err != nil { 405 | t.Fatal("unexpected Decode error:", err) 406 | } 407 | if p2.Type != COP { 408 | t.Fatal("unexpected page type:", p1.Type) 409 | } 410 | if len(p2.Packets) != 1 { 411 | t.Fatalf("len(p2.Packets) = %d", len(p2.Packets)) 412 | } 413 | if len(p2.Packets[0]) != mps { 414 | t.Fatalf("packet is wrong size: %d vs. %d", len(p2.Packets[0]), mps) 415 | } 416 | 417 | start := 50 + mps - mss 418 | if !bytes.Equal(p2.Packets[0], junk.Bytes()[start:start+mps]) { 419 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p2.Packets[0], junk.Bytes()[start:start+mps]) 420 | } 421 | 422 | p3, err := d.Decode() 423 | if err != nil { 424 | t.Fatal("unexpected Decode error:", err) 425 | } 426 | if p3.Type != COP { 427 | t.Fatal("unexpected page type:", p1.Type) 428 | } 429 | if len(p3.Packets) != 1 { 430 | t.Fatalf("len(p3.Packets) = %d", len(p3.Packets)) 431 | } 432 | start += mps 433 | if !bytes.Equal(p3.Packets[0], junk.Bytes()[start:]) { 434 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p3.Packets[0], junk.Bytes()[start:]) 435 | } 436 | } 437 | 438 | func TestEvenLongerMultipacketDecode(t *testing.T) { 439 | var b bytes.Buffer 440 | e := NewEncoder(1, &b) 441 | 442 | var junk bytes.Buffer 443 | for i := 0; i < maxPageSize*2; i++ { 444 | c := byte(rand.Intn(26)) + 'a' 445 | junk.WriteByte(c) 446 | } 447 | 448 | err := e.Encode(2, [][]byte{ 449 | junk.Bytes()[:50], 450 | junk.Bytes()[50 : junk.Len()-13], 451 | junk.Bytes()[junk.Len()-13:], 452 | }) 453 | if err != nil { 454 | t.Fatal("unexpected Encode error:", err) 455 | } 456 | 457 | d := NewDecoder(&b) 458 | p1, err := d.Decode() 459 | if err != nil { 460 | t.Fatal("unexpected Decode error:", err) 461 | } 462 | if p1.Type != 0 { 463 | t.Fatal("unexpected page type:", p1.Type) 464 | } 465 | if len(p1.Packets) != 2 { 466 | t.Fatalf("len(p1.Packets) = %d", len(p1.Packets)) 467 | } 468 | if !bytes.Equal(p1.Packets[0], junk.Bytes()[:50]) { 469 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p1.Packets[0], junk.Bytes()[:50]) 470 | } 471 | if len(p1.Packets[1]) != mps-mss { 472 | t.Fatalf("packet is wrong size: %d vs. %d", len(p1.Packets[1]), mps-mss) 473 | } 474 | if !bytes.Equal(p1.Packets[1], junk.Bytes()[50:50+mps-mss]) { 475 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p1.Packets[1], junk.Bytes()[50:50+mps-mss]) 476 | } 477 | 478 | p2, err := d.Decode() 479 | if err != nil { 480 | t.Fatal("unexpected Decode error:", err) 481 | } 482 | if p2.Type != COP { 483 | t.Fatal("unexpected page type:", p1.Type) 484 | } 485 | if len(p2.Packets) != 1 { 486 | t.Fatalf("len(p2.Packets) = %d", len(p2.Packets)) 487 | } 488 | if len(p2.Packets[0]) != mps { 489 | t.Fatalf("packet is wrong size: %d vs. %d", len(p2.Packets[0]), mps) 490 | } 491 | 492 | start := 50 + mps - mss 493 | if !bytes.Equal(p2.Packets[0], junk.Bytes()[start:start+mps]) { 494 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p2.Packets[0], junk.Bytes()[start:start+mps]) 495 | } 496 | 497 | p3, err := d.Decode() 498 | if err != nil { 499 | t.Fatal("unexpected Decode error:", err) 500 | } 501 | if p3.Type != COP { 502 | t.Fatal("unexpected page type:", p3.Type) 503 | } 504 | if len(p3.Packets) != 2 { 505 | t.Fatalf("len(p3.Packets) = %d", len(p3.Packets)) 506 | } 507 | start += mps 508 | if !bytes.Equal(p3.Packets[0], junk.Bytes()[start:junk.Len()-13]) { 509 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p3.Packets[0], junk.Bytes()[start:start+mps-13]) 510 | } 511 | start = junk.Len() - 13 512 | if !bytes.Equal(p3.Packets[1], junk.Bytes()[start:]) { 513 | t.Fatalf("packet is wrong:\n\t%x\nvs\n\t%x\n", p3.Packets[0], junk.Bytes()[start:]) 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | // © 2016 Steve McCoy under the MIT license. See LICENSE for details. 2 | 3 | package ogg 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "io" 9 | ) 10 | 11 | // An Encoder encodes raw bytes into an ogg stream. 12 | type Encoder struct { 13 | serial uint32 14 | page uint32 15 | dummy [1][]byte // convenience field to handle nil packets args without allocating 16 | w io.Writer 17 | buf [maxPageSize]byte 18 | } 19 | 20 | // NewEncoder creates an ogg encoder with the given serial ID. 21 | // Multiple Encoders can be used to encode multiplexed logical streams 22 | // by giving them distinct IDs. Users must be sure to encode the streams 23 | // as specified by the ogg RFC: 24 | // When Grouping, all BOS pages must come before the data 25 | // and EOS pages, with the order of BOS pages defined by the encapsulated encoding. 26 | // When Chaining, the EOS page of the first stream must be immediately followed by 27 | // the BOS of the second stream, and so on. 28 | // 29 | // For more details, see 30 | // http://xiph.org/ogg/doc/rfc3533.txt and 31 | // http://xiph.org/ogg/doc/framing.html 32 | func NewEncoder(id uint32, w io.Writer) *Encoder { 33 | return &Encoder{serial: id, w: w} 34 | } 35 | 36 | // EncodeBOS writes a beginning-of-stream packet to the ogg stream, 37 | // using the provided granule position. 38 | // If the packets are larger than can fit in a page, the payload is split into multiple 39 | // pages with the continuation-of-packet flag set. 40 | // Packets can be empty or nil, in which one segment of size 0 is encoded. 41 | func (w *Encoder) EncodeBOS(granule int64, packets [][]byte) error { 42 | if len(packets) == 0 { 43 | packets = w.dummy[:] 44 | } 45 | return w.writePackets(BOS, granule, packets) 46 | } 47 | 48 | // Encode writes a data packet to the ogg stream, 49 | // using the provided granule position. 50 | // If the packet is larger than can fit in a page, it is split into multiple 51 | // pages with the continuation-of-packet flag set. 52 | // Packets can be empty or nil, in which one segment of size 0 is encoded. 53 | func (w *Encoder) Encode(granule int64, packets [][]byte) error { 54 | if len(packets) == 0 { 55 | packets = w.dummy[:] 56 | } 57 | return w.writePackets(0, granule, packets) 58 | } 59 | 60 | // EncodeEOS writes an end-of-stream packet to the ogg stream. 61 | // Packets can be empty or nil, in which one segment of size 0 is encoded. 62 | func (w *Encoder) EncodeEOS(granule int64, packets [][]byte) error { 63 | if len(packets) == 0 { 64 | packets = w.dummy[:] 65 | } 66 | return w.writePackets(EOS, granule, packets) 67 | } 68 | 69 | func (w *Encoder) writePackets(kind byte, granule int64, packets [][]byte) error { 70 | h := pageHeader{ 71 | OggS: [4]byte{'O', 'g', 'g', 'S'}, 72 | HeaderType: kind, 73 | Serial: w.serial, 74 | Granule: granule, 75 | } 76 | 77 | // Write the lacing values before filling in their quantity 78 | segtbl, car, cdr := w.segmentize(payload{packets[0], packets[1:], nil}) 79 | err := w.writePage(&h, segtbl, car) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | h.HeaderType |= COP 85 | for len(cdr.leftover) > 0 { 86 | segtbl, car, cdr = w.segmentize(cdr) 87 | err = w.writePage(&h, segtbl, car) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func (w *Encoder) writePage(h *pageHeader, segtbl []byte, pay payload) error { 97 | h.Page = w.page 98 | w.page++ 99 | h.Nsegs = byte(len(segtbl)) 100 | hb := bytes.NewBuffer(w.buf[0:0:cap(w.buf)]) 101 | _ = binary.Write(hb, byteOrder, h) 102 | 103 | // segtbl is already written in the buffer, 104 | // but the writer needs to move along anyhow 105 | hb.Write(segtbl) 106 | 107 | hb.Write(pay.leftover) 108 | for _, p := range pay.packets { 109 | hb.Write(p) 110 | } 111 | hb.Write(pay.rightover) 112 | 113 | bb := hb.Bytes() 114 | crc := crc32(bb) 115 | _ = binary.Write(bytes.NewBuffer(bb[22:22:26]), byteOrder, crc) 116 | 117 | _, err := hb.WriteTo(w.w) 118 | return err 119 | } 120 | 121 | // payload represents a potentially-split group of packets. 122 | // For the "left" portion of a split, 123 | // rightover is the beginning portion of the *last* packet, 124 | // and packets contains the preceding packets. 125 | // For the "right" portion of a split, 126 | // leftover is the *first* packet and the other packets follow. 127 | // 128 | // ASCII example (each run of letters represents one packet): 129 | // 130 | // Page 1 (left) Page 2 (right) 131 | // [aaaabbbbccccd][dddeeeffff] 132 | // 133 | // For Page 1, packets would be a slice holding the a's, b's, and c's. 134 | // and rightover would contain the first d. 135 | // For Page 2, leftover would contain the d's, 136 | // and packets would contain the e's and f's 137 | type payload struct { 138 | leftover []byte 139 | packets [][]byte 140 | rightover []byte 141 | } 142 | 143 | // segmentize fills the segment table with lacing values based on the packets 144 | // provided in payload, starting with leftover (if any). 145 | // It returns the segment table (sized appropriately), 146 | // the payload to write with the segment table in the current page, 147 | // and any leftover payload that remains due to not fitting in a page. 148 | func (w *Encoder) segmentize(pay payload) ([]byte, payload, payload) { 149 | segtbl := w.buf[headsz : headsz+mss] 150 | i := 0 151 | 152 | s255s := len(pay.leftover) / mss 153 | rem := len(pay.leftover) % mss 154 | for i < len(segtbl) && s255s > 0 { 155 | segtbl[i] = mss 156 | i++ 157 | s255s-- 158 | } 159 | if i < mss { 160 | segtbl[i] = byte(rem) 161 | i++ 162 | } else { 163 | leftStart := len(pay.leftover) - (s255s * mss) - rem 164 | good := payload{pay.leftover[0:leftStart], nil, nil} 165 | bad := payload{pay.leftover[leftStart:], pay.packets, nil} 166 | return segtbl, good, bad 167 | } 168 | 169 | // Now loop through the rest and track if we need to split 170 | for p := 0; p < len(pay.packets); p++ { 171 | s255s := len(pay.packets[p]) / mss 172 | rem := len(pay.packets[p]) % mss 173 | for i < len(segtbl) && s255s > 0 { 174 | segtbl[i] = mss 175 | i++ 176 | s255s-- 177 | } 178 | if i < mss { 179 | segtbl[i] = byte(rem) 180 | i++ 181 | } else { 182 | right := len(pay.packets[p]) - (s255s * mss) - rem 183 | good := payload{pay.leftover, pay.packets[0:p], pay.packets[p][0:right]} 184 | bad := payload{pay.packets[p][right:], pay.packets[p+1:], nil} 185 | return segtbl, good, bad 186 | } 187 | } 188 | 189 | good := pay 190 | bad := payload{} 191 | return segtbl[0:i], good, bad 192 | } 193 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | // © 2016 Steve McCoy under the MIT license. See LICENSE for details. 2 | 3 | package ogg 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "testing" 9 | ) 10 | 11 | func TestBasicEncodeBOS(t *testing.T) { 12 | var b bytes.Buffer 13 | e := NewEncoder(1, &b) 14 | 15 | err := e.EncodeBOS(2, [][]byte{[]byte("hello")}) 16 | if err != nil { 17 | t.Fatal("unexpected EncodeBOS error:", err) 18 | } 19 | 20 | bb := b.Bytes() 21 | expect := []byte{ 22 | 'O', 'g', 'g', 'S', 23 | 0, 24 | BOS, 25 | 2, 0, 0, 0, 0, 0, 0, 0, 26 | 1, 0, 0, 0, 27 | 0, 0, 0, 0, 28 | 0x7e, 0xdf, 0x2e, 0x1e, // crc 29 | 1, 30 | 5, // segment table 31 | 'h', 'e', 'l', 'l', 'o', 32 | } 33 | 34 | if !bytes.Equal(bb, expect) { 35 | t.Fatalf("bytes != expected:\n%x\n%x", bb, expect) 36 | } 37 | } 38 | 39 | func TestEmptyEncodeBOS(t *testing.T) { 40 | var b bytes.Buffer 41 | e := NewEncoder(1, &b) 42 | 43 | err := e.EncodeBOS(2, nil) 44 | if err != nil { 45 | t.Fatal("unexpected Encode error:", err) 46 | } 47 | 48 | bb := b.Bytes() 49 | expect := []byte{ 50 | 'O', 'g', 'g', 'S', 51 | 0, 52 | BOS, 53 | 2, 0, 0, 0, 0, 0, 0, 0, 54 | 1, 0, 0, 0, 55 | 0, 0, 0, 0, 56 | 0x7a, 0xa4, 0x84, 0xc2, // crc 57 | 1, 58 | 0, // segment table 59 | } 60 | 61 | if !bytes.Equal(bb, expect) { 62 | t.Fatalf("bytes != expected:\n%x\n%x", bb, expect) 63 | } 64 | } 65 | 66 | func TestEmptyEncode(t *testing.T) { 67 | var b bytes.Buffer 68 | e := NewEncoder(1, &b) 69 | 70 | err := e.Encode(2, nil) 71 | if err != nil { 72 | t.Fatal("unexpected Encode error:", err) 73 | } 74 | 75 | bb := b.Bytes() 76 | expect := []byte{ 77 | 'O', 'g', 'g', 'S', 78 | 0, 79 | 0, 80 | 2, 0, 0, 0, 0, 0, 0, 0, 81 | 1, 0, 0, 0, 82 | 0, 0, 0, 0, 83 | 0xda, 0xf7, 0x1c, 0xce, // crc 84 | 1, 85 | 0, // segment table 86 | } 87 | 88 | if !bytes.Equal(bb, expect) { 89 | t.Fatalf("bytes != expected:\n%x\n%x", bb, expect) 90 | } 91 | } 92 | 93 | func TestEmptyEncodeEOS(t *testing.T) { 94 | var b bytes.Buffer 95 | e := NewEncoder(1, &b) 96 | 97 | err := e.EncodeEOS(2, nil) 98 | if err != nil { 99 | t.Fatal("unexpected Encode error:", err) 100 | } 101 | 102 | bb := b.Bytes() 103 | expect := []byte{ 104 | 'O', 'g', 'g', 'S', 105 | 0, 106 | EOS, 107 | 2, 0, 0, 0, 0, 0, 0, 0, 108 | 1, 0, 0, 0, 109 | 0, 0, 0, 0, 110 | 0x9a, 0x50, 0x2c, 0xd7, // crc 111 | 1, 112 | 0, // segment table 113 | } 114 | 115 | if !bytes.Equal(bb, expect) { 116 | t.Fatalf("bytes != expected:\n%x\n%x", bb, expect) 117 | } 118 | } 119 | 120 | func TestBasicEncode(t *testing.T) { 121 | var b bytes.Buffer 122 | e := NewEncoder(1, &b) 123 | 124 | err := e.Encode(2, [][]byte{[]byte("hello")}) 125 | if err != nil { 126 | t.Fatal("unexpected Encode error:", err) 127 | } 128 | 129 | bb := b.Bytes() 130 | expect := []byte{ 131 | 'O', 'g', 'g', 'S', 132 | 0, 133 | 0, 134 | 2, 0, 0, 0, 0, 0, 0, 0, 135 | 1, 0, 0, 0, 136 | 0, 0, 0, 0, 137 | 0xc8, 0x21, 0xcc, 0x1c, // crc 138 | 1, 139 | 5, // segment table 140 | 'h', 'e', 'l', 'l', 'o', 141 | } 142 | 143 | if !bytes.Equal(bb, expect) { 144 | t.Fatalf("bytes != expected:\n%x\n%x", bb, expect) 145 | } 146 | } 147 | 148 | func TestBasicEncodeEOS(t *testing.T) { 149 | var b bytes.Buffer 150 | e := NewEncoder(1, &b) 151 | 152 | err := e.EncodeEOS(7, [][]byte{nil}) 153 | if err != nil { 154 | t.Fatal("unexpected EncodeEOS error:", err) 155 | } 156 | 157 | bb := b.Bytes() 158 | expect := []byte{ 159 | 'O', 'g', 'g', 'S', 160 | 0, 161 | EOS, 162 | 7, 0, 0, 0, 0, 0, 0, 0, 163 | 1, 0, 0, 0, 164 | 0, 0, 0, 0, 165 | 0x79, 0x1e, 0xe7, 0xe7, // crc 166 | 1, 167 | 0, // segment table 168 | } 169 | 170 | if !bytes.Equal(bb, expect) { 171 | t.Fatalf("bytes != expected:\n%x\n%x", bb, expect) 172 | } 173 | } 174 | 175 | func TestLongEncode(t *testing.T) { 176 | var b bytes.Buffer 177 | e := NewEncoder(1, &b) 178 | 179 | var junk bytes.Buffer 180 | for i := 0; i < maxPageSize*2; i++ { 181 | junk.WriteByte('x') 182 | } 183 | 184 | err := e.Encode(2, [][]byte{junk.Bytes()}) 185 | if err != nil { 186 | t.Fatal("unexpected Encode error:", err) 187 | } 188 | 189 | bb := b.Bytes() 190 | expect := []byte{ 191 | 'O', 'g', 'g', 'S', 192 | 0, 193 | 0, 194 | 2, 0, 0, 0, 0, 0, 0, 0, 195 | 1, 0, 0, 0, 196 | 0, 0, 0, 0, 197 | 0xee, 0xb2, 0x0b, 0xca, // crc 198 | 255, 199 | } 200 | 201 | if !bytes.Equal(bb[:headsz], expect) { 202 | t.Fatalf("bytes != expected:\n%x\n%x", bb[:headsz], expect) 203 | } 204 | 205 | expect2 := []byte{ 206 | 'O', 'g', 'g', 'S', 207 | 0, 208 | COP, 209 | 2, 0, 0, 0, 0, 0, 0, 0, 210 | 1, 0, 0, 0, 211 | 1, 0, 0, 0, 212 | 0x17, 0x0d, 0xe6, 0xe6, // crc 213 | 255, 214 | } 215 | 216 | if !bytes.Equal(bb[maxPageSize:maxPageSize+headsz], expect2) { 217 | t.Fatalf("bytes != expected:\n%x\n%x", bb[maxPageSize:maxPageSize+headsz], expect2) 218 | } 219 | } 220 | 221 | type limitedWriter struct { 222 | N int64 223 | } 224 | 225 | func (w *limitedWriter) Write(p []byte) (int, error) { 226 | if w.N <= int64(len(p)) { 227 | n := w.N 228 | w.N = 0 229 | return int(n), io.ErrClosedPipe 230 | } 231 | 232 | w.N -= int64(len(p)) 233 | return len(p), nil 234 | } 235 | 236 | func TestShortWrites(t *testing.T) { 237 | e := NewEncoder(1, &limitedWriter{N: 0}) 238 | err := e.Encode(2, [][]byte{[]byte("hello")}) 239 | if err != io.ErrClosedPipe { 240 | t.Fatal("expected ErrClosedPipe, got:", err) 241 | } 242 | 243 | e = NewEncoder(1, &limitedWriter{N: maxPageSize + 1}) 244 | var junk bytes.Buffer 245 | for i := 0; i < maxPageSize*2; i++ { 246 | junk.WriteByte('x') 247 | } 248 | err = e.Encode(2, [][]byte{junk.Bytes()}) 249 | if err != io.ErrClosedPipe { 250 | t.Fatal("expected ErrClosedPipe, got:", err) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mccoy.space/g/ogg 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | // © 2016 Steve McCoy under the MIT license. See LICENSE for details. 2 | 3 | /* 4 | Package ogg implements encoding and decoding of OGG streams as defined in 5 | http://xiph.org/ogg/doc/rfc3533.txt 6 | and 7 | http://xiph.org/ogg/doc/framing.html . 8 | */ 9 | package ogg 10 | 11 | import ( 12 | "encoding/binary" 13 | ) 14 | 15 | // The MIME type as defined in RFC 3534. 16 | const MIMEType = "application/ogg" 17 | 18 | const headsz = 27 19 | 20 | // max segment size 21 | const mss = 255 22 | 23 | // max sequence-of-segments size in a page 24 | const mps = mss * 255 25 | 26 | // == 65307, per the RFC 27 | const maxPageSize = headsz + mss + mps 28 | 29 | // The byte order of integers in ogg page headers. 30 | var byteOrder = binary.LittleEndian 31 | 32 | type pageHeader struct { 33 | OggS [4]byte // 0-3, always == "OggS" 34 | StreamVersion byte // 4, always == 0 35 | HeaderType byte // 5 36 | Granule int64 // 6-13, codec-specific 37 | Serial uint32 // 14-17, associated with a logical stream 38 | Page uint32 // 18-21, sequence number of page in packet 39 | Crc uint32 // 22-25 40 | Nsegs byte // 26 41 | } 42 | 43 | const ( 44 | // Continuation of packet 45 | COP byte = 1 << iota 46 | // Beginning of stream 47 | BOS = 1 << iota 48 | // End of stream 49 | EOS = 1 << iota 50 | ) 51 | 52 | var crcTable = [256]uint32{ 53 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 54 | 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 55 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 56 | 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 57 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 58 | 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 59 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 60 | 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 61 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 62 | 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 63 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 64 | 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 65 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 66 | 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 67 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 68 | 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 69 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 70 | 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 71 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 72 | 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 73 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 74 | 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 75 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 76 | 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 77 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 78 | 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 79 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 80 | 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 81 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 82 | 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 83 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 84 | 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 85 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 86 | 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 87 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 88 | 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 89 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 90 | 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 91 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 92 | 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 93 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 94 | 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 95 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 96 | 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 97 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 98 | 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 99 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 100 | 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 101 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 102 | 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 103 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 104 | 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 105 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 106 | 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 107 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 108 | 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 109 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 110 | 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 111 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 112 | 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 113 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 114 | 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 115 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 116 | 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, 117 | } 118 | 119 | // "unreflected" crc used by libogg 120 | func crc32(p []byte) uint32 { 121 | c := uint32(0) 122 | for _, n := range p { 123 | c = crcTable[byte(c>>24)^n] ^ (c << 8) 124 | } 125 | return c 126 | } 127 | -------------------------------------------------------------------------------- /otest.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | // © 2022 Steve McCoy under the MIT license. See LICENSE for details. 4 | 5 | package main 6 | 7 | // This is a simple test program which can be run like so: 8 | // go run otest.go < a.ogg > b.ogg 9 | // It's quite simple in that it doesn't handle recombining COP packets, 10 | // but at least for the ogg/vorbis files from wikipedia that I've tested, 11 | // it results in an identical copy of the original file. 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | 17 | "mccoy.space/g/ogg" 18 | ) 19 | 20 | func main() { 21 | decoder := ogg.NewDecoder(os.Stdin) 22 | page, err := decoder.Decode() 23 | if err != nil { 24 | fmt.Fprint(os.Stderr, err) 25 | os.Exit(1) 26 | } 27 | encoder := ogg.NewEncoder(page.Serial, os.Stdout) 28 | encoder.EncodeBOS(page.Granule, page.Packets) 29 | 30 | for { 31 | page, err := decoder.Decode() 32 | if err != nil { 33 | fmt.Fprint(os.Stderr, err) 34 | break 35 | } 36 | 37 | if page.Type&ogg.EOS == ogg.EOS { 38 | encoder.EncodeEOS(page.Granule, page.Packets) 39 | break 40 | } 41 | encoder.Encode(page.Granule, page.Packets) 42 | } 43 | } 44 | --------------------------------------------------------------------------------