Skip to content
Open
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
65 changes: 41 additions & 24 deletions machinery/src/capture/gortsplib.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package capture

// #cgo pkg-config: libavcodec libavutil libswscale
// #include <stdlib.h>
// #include <string.h>
// #include <libavcodec/avcodec.h>
// #include <libavutil/imgutils.h>
// #include <libavutil/hwcontext.h>
// #include <libswscale/swscale.h>
import "C"

Expand All @@ -11,7 +14,7 @@ import (
"errors"
"fmt"
"image"
"reflect"
"runtime"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -893,7 +896,7 @@ func (g *Golibrtsp) DecodePacket(pkt packets.Packet) (image.YCbCr, error) {
}
if img.Bounds().Empty() {
log.Log.Debug("capture.golibrtsp.DecodePacket(): empty frame")
return image.YCbCr{}, errors.New("Empty image")
return image.YCbCr{}, errors.New("empty image")
}
return img, nil
}
Expand All @@ -919,7 +922,7 @@ func (g *Golibrtsp) DecodePacketRaw(pkt packets.Packet) (image.Gray, error) {
}
if img.Bounds().Empty() {
log.Log.Debug("capture.golibrtsp.DecodePacketRaw(): empty image")
return image.Gray{}, errors.New("Empty image")
return image.Gray{}, errors.New("empty image")
}

// Do a deep copy of the image
Expand Down Expand Up @@ -985,7 +988,7 @@ func frameLineSize(frame *C.AVFrame) *C.int {
return (*C.int)(unsafe.Pointer(&frame.linesize[0]))
}

// h264Decoder is a wrapper around FFmpeg's H264 decoder.
// Decoder structure for H264/H265 video decoding
type Decoder struct {
codecCtx *C.AVCodecContext
srcFrame *C.AVFrame
Expand All @@ -1006,6 +1009,13 @@ func newDecoder(codecName string) (*Decoder, error) {
return nil, fmt.Errorf("avcodec_alloc_context3() failed")
}

// Optimize decoder for speed
threadCount := runtime.NumCPU()
if threadCount < 1 {
threadCount = 1 // Ensure at least one thread is used
}
codecCtx.thread_count = C.int(threadCount) // Use multiple threads for decoding

res := C.avcodec_open2(codecCtx, codec, nil)
if res < 0 {
C.avcodec_close(codecCtx)
Expand Down Expand Up @@ -1034,13 +1044,19 @@ func (d *Decoder) Close() {
}

func (d *Decoder) decode(nalu []byte) (image.YCbCr, error) {
nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...)
// Prepend the NALU start code using a helper function
naluWithStartCode := prependNALUStartCode(nalu)

// send NALU to decoder
var avPacket C.AVPacket
avPacket.data = (*C.uint8_t)(C.CBytes(nalu))
defer C.free(unsafe.Pointer(avPacket.data))
avPacket.size = C.int(len(nalu))
// Use runtime.Pinner to pin the memory during the CGO call
var pinner runtime.Pinner
pinner.Pin(&naluWithStartCode[0])
defer pinner.Unpin()

avPacket.data = (*C.uint8_t)(unsafe.Pointer(&naluWithStartCode[0]))
avPacket.size = C.int(len(naluWithStartCode))

res := C.avcodec_send_packet(d.codecCtx, &avPacket)
if res < 0 {
return image.YCbCr{}, nil
Expand All @@ -1060,9 +1076,9 @@ func (d *Decoder) decode(nalu []byte) (image.YCbCr, error) {
cs := int(fr.linesize[1])

return image.YCbCr{
Y: fromCPtr(unsafe.Pointer(fr.data[0]), ys*h),
Cb: fromCPtr(unsafe.Pointer(fr.data[1]), cs*h/2),
Cr: fromCPtr(unsafe.Pointer(fr.data[2]), cs*h/2),
Y: unsafe.Slice((*byte)(fr.data[0]), ys*h),
Cb: unsafe.Slice((*byte)(fr.data[1]), cs*h/2),
Cr: unsafe.Slice((*byte)(fr.data[2]), cs*h/2),
YStride: ys,
CStride: cs,
SubsampleRatio: image.YCbCrSubsampleRatio420,
Expand All @@ -1074,13 +1090,22 @@ func (d *Decoder) decode(nalu []byte) (image.YCbCr, error) {
}

func (d *Decoder) decodeRaw(nalu []byte) (image.Gray, error) {
nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...)
// Create a new slice with start code + nalu data
// This avoids the cgo pointer issue by creating a fresh slice each time
naluWithStartCode := make([]byte, len(nalu)+4)
copy(naluWithStartCode[:4], []byte{0x00, 0x00, 0x00, 0x01})
copy(naluWithStartCode[4:], nalu)

// send NALU to decoder
var avPacket C.AVPacket
avPacket.data = (*C.uint8_t)(C.CBytes(nalu))
defer C.free(unsafe.Pointer(avPacket.data))
avPacket.size = C.int(len(nalu))
// Use runtime.Pinner to pin the memory during the CGO call
var pinner runtime.Pinner
pinner.Pin(&naluWithStartCode[0])
defer pinner.Unpin()

avPacket.data = (*C.uint8_t)(unsafe.Pointer(&naluWithStartCode[0]))
avPacket.size = C.int(len(naluWithStartCode))

res := C.avcodec_send_packet(d.codecCtx, &avPacket)
if res < 0 {
return image.Gray{}, nil
Expand All @@ -1099,7 +1124,7 @@ func (d *Decoder) decodeRaw(nalu []byte) (image.Gray, error) {
ys := int(fr.linesize[0])

return image.Gray{
Pix: fromCPtr(unsafe.Pointer(fr.data[0]), w*h),
Pix: unsafe.Slice((*byte)(fr.data[0]), w*h),
Stride: ys,
Rect: image.Rect(0, 0, w, h),
}, nil
Expand All @@ -1108,14 +1133,6 @@ func (d *Decoder) decodeRaw(nalu []byte) (image.Gray, error) {
return image.Gray{}, nil
}

func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) {
hdr := (*reflect.SliceHeader)((unsafe.Pointer(&ret)))
hdr.Cap = size
hdr.Len = size
hdr.Data = uintptr(buf)
return
}

func FindPCMU(desc *description.Session, isBackChannel bool) (*format.G711, *description.Media) {
for _, media := range desc.Medias {
if media.IsBackChannel == isBackChannel {
Expand Down
Loading