-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathring.go
145 lines (116 loc) · 3.14 KB
/
ring.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
138
139
140
141
142
143
144
145
/*
Cyclic buffer for fragmented mp4 stream in order to save memory when pre-recording an event
*/
package mp4ring
import (
"bytes"
"container/ring"
"encoding/binary"
"errors"
"fmt"
)
var (
//ErrSize - size must be positive
ErrSize = errors.New("size must be positive")
// ErrBufClose - ring buffer is closed
ErrBufClose = errors.New("buffer is closed")
// ErrBufNil - ring buffer is nil
ErrBufNil = errors.New("buffer is nil")
)
// Buffer implements a cyclic buffer. Has a fixed size,
// and the new data overwrites the old, so that for a buffer
// of size N, for any number of write operations, only the last N mp4 atoms are saved.
// At the same time, the ftyp and moov headers are stored separately.
type Buffer struct {
ftyp []byte // ftyp header
moov []byte // moov header
r *ring.Ring // atoms
size int64
isClosed bool // closing flag
}
// header representation (https://docs.fileformat.com/video/mp4/#structure-of-mp4-files)
type boxHeader struct {
Size uint32
FourccType [4]byte
Size64 uint64
}
// getting header information
func getHeaderBoxInfo(data []byte) (boxHeader, error) {
buf := bytes.NewBuffer(data)
var box boxHeader
err := binary.Read(buf, binary.BigEndian, &box)
return box, err
}
// New creates a new buffer of the specified size. The size must be greater than 0
func New(size int) (*Buffer, error) {
if size <= 0 {
return nil, ErrSize
}
return &Buffer{
r: ring.New(size),
ftyp: make([]byte, 32),
isClosed: false,
}, nil
}
// Close closing the buffer
func (b *Buffer) Close() error {
if b == nil {
return ErrBufNil
}
b.isClosed = true
return nil
}
// Write writes headers and up to N atoms to the inner ring, overwriting older data if necessary
func (b *Buffer) Write(buf []byte) (int, error) {
if b.isClosed { // to unsubscribe, you need to return an error
return 0, ErrBufClose
}
// Total number of bytes written
var n int
// Separately store the header, which is easy to determine - each atom has a name. And the name of the header atoms is ftyp and moov. It is enough to watch the first 4 bytes of the stream, if there is an ftp, then this is the header and you need to save it
bHead, err := getHeaderBoxInfo(buf)
if err != nil {
return 0, err
}
// get header type
fourccType := string(bHead.FourccType[:])
switch fourccType {
case `ftyp`:
b.ftyp = make([]byte, len(buf))
n = copy(b.ftyp, buf)
case `moov`:
b.moov = make([]byte, len(buf))
n = copy(b.moov, buf)
default:
tmp := make([]byte, len(buf))
n = copy(tmp, buf)
b.r.Value = tmp
b.r = b.r.Next()
}
return n, nil
}
// Bytes provides all recorded mp4 headers and atoms
func (b *Buffer) Bytes() []byte {
var buf bytes.Buffer
buf.Write(b.ftyp)
buf.Write(b.moov)
b.r.Do(func(p interface{}) {
atom, ok := p.([]uint8)
if ok {
buf.Write(atom)
}
})
return buf.Bytes()
}
// Size provides the size of all recorded data in kilobytes
func (b *Buffer) Size() string {
b.size = 0
b.size += int64(len(b.ftyp) + len(b.moov))
b.r.Do(func(p interface{}) {
atom, ok := p.([]uint8)
if ok {
b.size += int64(len(atom))
}
})
return fmt.Sprintf("%.2f", float64(b.size)/(1<<10))
}