Skip to content

Commit

Permalink
Manually force keyframes in encoder loop
Browse files Browse the repository at this point in the history
  • Loading branch information
seanavery committed Oct 7, 2024
1 parent 4c26696 commit bf80996
Showing 1 changed file with 12 additions and 8 deletions.
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

0 comments on commit bf80996

Please sign in to comment.