-
Notifications
You must be signed in to change notification settings - Fork 342
/
encoder.go
189 lines (164 loc) · 4.52 KB
/
encoder.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package vpxencoder
import (
"fmt"
"log"
"time"
"unsafe"
"github.com/giongto35/cloud-game/config"
)
// https://chromium.googlesource.com/webm/libvpx/+/master/examples/simple_encoder.c
/*
#cgo pkg-config: vpx
#include <stdlib.h>
#include "vpx/vpx_encoder.h"
#include "tools_common.h"
typedef struct GoBytes {
void *bs;
int size;
} GoBytesType;
vpx_codec_err_t call_vpx_codec_enc_config_default(const VpxInterface *encoder, vpx_codec_enc_cfg_t *cfg) {
return vpx_codec_enc_config_default(encoder->codec_interface(), cfg, 0);
}
vpx_codec_err_t call_vpx_codec_enc_init(vpx_codec_ctx_t *codec, const VpxInterface *encoder, vpx_codec_enc_cfg_t *cfg) {
return vpx_codec_enc_init(codec, encoder->codec_interface(), cfg, 0);
}
GoBytesType get_frame_buffer(vpx_codec_ctx_t *codec, vpx_codec_iter_t *iter) {
// iter has set to NULL when after add new image
GoBytesType bytes = {NULL, 0};
const vpx_codec_cx_pkt_t *pkt = vpx_codec_get_cx_data(codec, iter);
if (pkt != NULL && pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
bytes.bs = pkt->data.frame.buf;
bytes.size = pkt->data.frame.sz;
}
return bytes;
}
*/
import "C"
const chanSize = 2
// NewVpxEncoder create vp8 encoder
func NewVpxEncoder(w, h, fps, bitrate, keyframe int) (*VpxEncoder, error) {
v := &VpxEncoder{
Output: make(chan []byte, 5*chanSize),
Input: make(chan []byte, chanSize),
IsRunning: true,
Done: false,
// C
width: C.uint(w),
height: C.uint(h),
fps: C.int(fps),
bitrate: C.uint(bitrate),
keyFrameInterval: C.int(keyframe),
frameCount: C.int(0),
}
if err := v.init(); err != nil {
return nil, err
}
return v, nil
}
// VpxEncoder yuvI420 image to vp8 video
type VpxEncoder struct {
Output chan []byte // frame
Input chan []byte // yuvI420
IsRunning bool
Done bool
// C
width C.uint
height C.uint
fps C.int
bitrate C.uint
keyFrameInterval C.int
frameCount C.int
vpxCodexCtx C.vpx_codec_ctx_t
vpxImage C.vpx_image_t
vpxCodexIter C.vpx_codec_iter_t
}
func (v *VpxEncoder) init() error {
v.frameCount = 0
codecName := C.CString("vp8")
encoder := C.get_vpx_encoder_by_name(codecName)
C.free(unsafe.Pointer(codecName))
if encoder == nil {
return fmt.Errorf("get_vpx_encoder_by_name failed")
}
if C.vpx_img_alloc(&v.vpxImage, C.VPX_IMG_FMT_I420, v.width, v.height, 0) == nil {
return fmt.Errorf("vpx_img_alloc failed")
}
var cfg C.vpx_codec_enc_cfg_t
if C.call_vpx_codec_enc_config_default(encoder, &cfg) != 0 {
return fmt.Errorf("Failed to get default codec config")
}
cfg.g_w = v.width
cfg.g_h = v.height
cfg.g_timebase.num = 1
cfg.g_timebase.den = v.fps
cfg.rc_target_bitrate = v.bitrate
cfg.g_error_resilient = 1
if C.call_vpx_codec_enc_init(&v.vpxCodexCtx, encoder, &cfg) != 0 {
return fmt.Errorf("Failed to initialize encoder")
}
v.IsRunning = true
go v.startLooping()
return nil
}
func (v *VpxEncoder) startLooping() {
defer func() {
if r := recover(); r != nil {
log.Println("Warn: Recovered panic in encoding ", r)
}
}()
for yuv := range v.Input {
if v.Done == true {
// The first time we see IsRunning set to false, we release and return
v.Release()
return
}
beginEncoding := time.Now()
// Add Image
v.vpxCodexIter = nil
C.vpx_img_read(&v.vpxImage, unsafe.Pointer(&yuv[0]))
var flags C.int
if v.keyFrameInterval > 0 && v.frameCount%v.keyFrameInterval == 0 {
flags |= C.VPX_EFLAG_FORCE_KF
}
if C.vpx_codec_encode(&v.vpxCodexCtx, &v.vpxImage, C.vpx_codec_pts_t(v.frameCount), 1, C.vpx_enc_frame_flags_t(flags), C.VPX_DL_REALTIME) != 0 {
fmt.Println("Failed to encode frame")
}
v.frameCount++
// Get Frame
for {
goBytes := C.get_frame_buffer(&v.vpxCodexCtx, &v.vpxCodexIter)
if goBytes.bs == nil {
break
}
bs := C.GoBytes(goBytes.bs, goBytes.size)
// if buffer is full skip frame
if len(v.Output) >= cap(v.Output) {
continue
}
v.Output <- bs
}
if *config.IsMonitor {
log.Println("Encoding time: ", time.Now().Sub(beginEncoding))
}
}
if v.Done == true {
// The first time we see IsRunning set to false, we release and return
v.Release()
return
}
}
// Release release memory and stop loop
func (v *VpxEncoder) Release() {
if v.IsRunning {
v.IsRunning = false
log.Println("Releasing encoder")
C.vpx_img_free(&v.vpxImage)
C.vpx_codec_destroy(&v.vpxCodexCtx)
// TODO: Bug here, after close it will signal
close(v.Output)
if v.Input != nil {
close(v.Input)
}
}
// TODO: Can we merge IsRunning and Done together
}