-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
ivfreader.go
137 lines (117 loc) · 4.02 KB
/
ivfreader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package ivfreader implements IVF media container reader
package ivfreader
import (
"encoding/binary"
"fmt"
"io"
)
const (
ivfFileHeaderSignature = "DKIF"
ivfFileHeaderSize = 32
ivfFrameHeaderSize = 12
)
// IVFFileHeader 32-byte header for IVF files
// https://wiki.multimedia.cx/index.php/IVF
type IVFFileHeader struct {
signature string // 0-3
version uint16 // 4-5
headerSize uint16 // 6-7
FourCC string // 8-11
Width uint16 // 12-13
Height uint16 // 14-15
TimebaseDenominator uint32 // 16-19
TimebaseNumerator uint32 // 20-23
NumFrames uint32 // 24-27
unused uint32 // 28-31
}
// IVFFrameHeader 12-byte header for IVF frames
// https://wiki.multimedia.cx/index.php/IVF
type IVFFrameHeader struct {
FrameSize uint32 // 0-3
Timestamp uint64 // 4-11
}
// IVFReader is used to read IVF files and return frame payloads
type IVFReader struct {
stream io.Reader
bytesReadSuccesfully int64
}
// NewWith returns a new IVF reader and IVF file header
// with an io.Reader input
func NewWith(in io.Reader) (*IVFReader, *IVFFileHeader, error) {
if in == nil {
return nil, nil, fmt.Errorf("stream is nil")
}
reader := &IVFReader{
stream: in,
}
header, err := reader.parseFileHeader()
if err != nil {
return nil, nil, err
}
return reader, header, nil
}
// ResetReader resets the internal stream of IVFReader. This is useful
// for live streams, where the end of the file might be read without the
// data being finished.
func (i *IVFReader) ResetReader(reset func(bytesRead int64) io.Reader) {
i.stream = reset(i.bytesReadSuccesfully)
}
// ParseNextFrame reads from stream and returns IVF frame payload, header,
// and an error if there is incomplete frame data.
// Returns all nil values when no more frames are available.
func (i *IVFReader) ParseNextFrame() ([]byte, *IVFFrameHeader, error) {
buffer := make([]byte, ivfFrameHeaderSize)
var header *IVFFrameHeader
bytesRead, err := io.ReadFull(i.stream, buffer)
headerBytesRead := bytesRead
if err == io.ErrUnexpectedEOF {
return nil, nil, fmt.Errorf("incomplete frame header")
} else if err != nil {
return nil, nil, err
}
header = &IVFFrameHeader{
FrameSize: binary.LittleEndian.Uint32(buffer[:4]),
Timestamp: binary.LittleEndian.Uint64(buffer[4:12]),
}
payload := make([]byte, header.FrameSize)
bytesRead, err = io.ReadFull(i.stream, payload)
if err == io.ErrUnexpectedEOF {
return nil, nil, fmt.Errorf("incomplete frame data")
} else if err != nil {
return nil, nil, err
}
i.bytesReadSuccesfully += int64(headerBytesRead) + int64(bytesRead)
return payload, header, nil
}
// parseFileHeader reads 32 bytes from stream and returns
// IVF file header. This is always called before ParseNextFrame()
func (i *IVFReader) parseFileHeader() (*IVFFileHeader, error) {
buffer := make([]byte, ivfFileHeaderSize)
bytesRead, err := io.ReadFull(i.stream, buffer)
if err == io.ErrUnexpectedEOF {
return nil, fmt.Errorf("incomplete file header")
} else if err != nil {
return nil, err
}
header := &IVFFileHeader{
signature: string(buffer[:4]),
version: binary.LittleEndian.Uint16(buffer[4:6]),
headerSize: binary.LittleEndian.Uint16(buffer[6:8]),
FourCC: string(buffer[8:12]),
Width: binary.LittleEndian.Uint16(buffer[12:14]),
Height: binary.LittleEndian.Uint16(buffer[14:16]),
TimebaseDenominator: binary.LittleEndian.Uint32(buffer[16:20]),
TimebaseNumerator: binary.LittleEndian.Uint32(buffer[20:24]),
NumFrames: binary.LittleEndian.Uint32(buffer[24:28]),
unused: binary.LittleEndian.Uint32(buffer[28:32]),
}
if header.signature != ivfFileHeaderSignature {
return nil, fmt.Errorf("IVF signature mismatch")
} else if header.version != uint16(0) {
errStr := fmt.Sprintf("IVF version unknown: %d,"+
" parser may not parse correctly", header.version)
return nil, fmt.Errorf(errStr)
}
i.bytesReadSuccesfully += int64(bytesRead)
return header, nil
}