Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manually force keyframes in encoder loop #20

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions cam/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,15 @@ func newEncoder(
enc.codecCtx.bit_rate = C.int64_t(bitrate)
enc.codecCtx.pix_fmt = C.AV_PIX_FMT_YUV422P
enc.codecCtx.time_base = C.AVRational{num: 1, den: C.int(framerate)}
enc.codecCtx.gop_size = C.int(framerate)
enc.codecCtx.width = C.int(width)
enc.codecCtx.height = C.int(height)

// TODO(seanp): Do we want b frames? This could make it more complicated to split clips.
enc.codecCtx.max_b_frames = 0
presetCStr := C.CString(preset)
tuneCStr := C.CString("zerolatency")
forceKeyFramesExpr := fmt.Sprintf("expr:gte(t,n_forced*%d)", framerate)
forceKeyFramesCStr := C.CString(forceKeyFramesExpr)
defer C.free(unsafe.Pointer(presetCStr))
defer C.free(unsafe.Pointer(tuneCStr))
defer C.free(unsafe.Pointer(forceKeyFramesCStr))

// The user can set the preset and tune for the encoder. This affects the
// encoding speed and quality. See https://trac.ffmpeg.org/wiki/Encode/H.264
Expand All @@ -79,10 +75,6 @@ func newEncoder(
if ret < 0 {
return nil, fmt.Errorf("av_dict_set failed: %s", ffmpegError(ret))
}
ret = C.av_dict_set(&opts, C.CString("force_key_frames"), forceKeyFramesCStr, 0)
if ret < 0 {
return nil, fmt.Errorf("av_dict_set failed: %s", ffmpegError(ret))
}

ret = C.avcodec_open2(enc.codecCtx, codec, &opts)
if ret < 0 {
Expand Down Expand Up @@ -134,6 +126,18 @@ func (e *encoder) encode(frame image.Image) ([]byte, int64, int64, error) {
// TODO(seanp): What happens to playback if frame is dropped?
e.srcFrame.pts = C.int64_t(e.frameCount)
e.srcFrame.pkt_dts = e.srcFrame.pts

// Manually force keyframes every second, removing the need to rely on
// gop_size or other encoder settings. This is necessary for the segmenter
// to split the video files at keyframe boundaries.
if e.frameCount%int64(e.codecCtx.time_base.den) == 0 {
e.srcFrame.key_frame = 1
e.srcFrame.pict_type = C.AV_PICTURE_TYPE_I
} else {
e.srcFrame.key_frame = 0
e.srcFrame.pict_type = C.AV_PICTURE_TYPE_NONE
}

ret := C.avcodec_send_frame(e.codecCtx, e.srcFrame)
if ret < 0 {
return nil, 0, 0, fmt.Errorf("avcodec_send_frame: %s", ffmpegError(ret))
Expand Down
Loading