Skip to content

Commit

Permalink
Add native support for the raspberry pi camera
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Aug 4, 2022
1 parent 055e08a commit 36b9341
Show file tree
Hide file tree
Showing 9 changed files with 522 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/release
/coverage*.txt
/apidocs/*.html
/rtsp-simple-server-rpi
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,36 @@ dockerhub:

docker buildx rm builder
rm -rf $$HOME/.docker/manifests/*

define DOCKERFILE_RPI
FROM golang:1.18

RUN apt update && apt install -y --no-install-recommends \
cmake

RUN cd / && git clone -b oldstable --depth=1 https://github.com/raspberrypi/firmware \
&& mv /firmware/opt/vc /opt/vc \
&& rm -rf /firmware

RUN cd / && git clone --depth=1 https://github.com/raspberrypi/tools

WORKDIR /s

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=1 \
GOOS=linux \
GOARCH=arm \
GOARM=6 \
CC=/tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc \
go build -o /out

endef
export DOCKERFILE_RPI

rpi:
echo "$$DOCKERFILE_RPI" | docker build . -f - -t temp
docker run --rm -it -v $(PWD):/o temp sh -c "mv /out /o/rtsp-simple-server-rpi"
8 changes: 8 additions & 0 deletions apidocs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ components:
- $ref: '#/components/schemas/PathSourceRTSPSource'
- $ref: '#/components/schemas/PathSourceRTMPSource'
- $ref: '#/components/schemas/PathSourceHLSSource'
- $ref: '#/components/schemas/PathSourceRPICameraSource'
sourceReady:
type: boolean
readers:
Expand Down Expand Up @@ -258,6 +259,13 @@ components:
type: string
enum: [hlsSource]

PathSourceRPICameraSource:
type: object
properties:
type:
type: string
enum: [rpiCameraSource]

PathReaderRTSPSession:
type: object
properties:
Expand Down
2 changes: 2 additions & 0 deletions internal/conf/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ func (pconf *PathConf) checkAndFillMissing(conf *Conf, name string) error {
return fmt.Errorf("'%s' is not a valid RTSP URL", pconf.SourceRedirect)
}

case pconf.Source == "rpicamera":

default:
return fmt.Errorf("invalid source: '%s'", pconf.Source)
}
Expand Down
3 changes: 2 additions & 1 deletion internal/core/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ func (pa *path) hasStaticSource() bool {
strings.HasPrefix(pa.conf.Source, "rtsps://") ||
strings.HasPrefix(pa.conf.Source, "rtmp://") ||
strings.HasPrefix(pa.conf.Source, "http://") ||
strings.HasPrefix(pa.conf.Source, "https://")
strings.HasPrefix(pa.conf.Source, "https://") ||
pa.conf.Source == "rpicamera"
}

func (pa *path) hasOnDemandStaticSource() bool {
Expand Down
111 changes: 111 additions & 0 deletions internal/core/rpicamera_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package core

import (
"context"
"time"

"github.com/aler9/gortsplib"
"github.com/aler9/gortsplib/pkg/h264"
"github.com/aler9/gortsplib/pkg/rtph264"

"github.com/aler9/rtsp-simple-server/internal/logger"
"github.com/aler9/rtsp-simple-server/internal/rpicamera"
)

const (
rpiCameraSourceRetryPause = 5 * time.Second
)

type rpiCameraSourceParent interface {
log(logger.Level, string, ...interface{})
sourceStaticImplSetReady(req pathSourceStaticSetReadyReq) pathSourceStaticSetReadyRes
sourceStaticImplSetNotReady(req pathSourceStaticSetNotReadyReq)
}

type rpiCameraSource struct {
parent rpiCameraSourceParent
}

func newRPICameraSource(
parent rpiCameraSourceParent,
) *rpiCameraSource {
return &rpiCameraSource{
parent: parent,
}
}

func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interface{}) {
s.parent.log(level, "[rpicamera source] "+format, args...)
}

// run implements sourceStaticImpl.
func (s *rpiCameraSource) run(ctx context.Context) error {
track := &gortsplib.TrackH264{PayloadType: 96}
enc := &rtph264.Encoder{PayloadType: 96}
enc.Init()
var stream *stream
var start time.Time

onData := func(nalus [][]byte) {
if stream == nil {
res := s.parent.sourceStaticImplSetReady(pathSourceStaticSetReadyReq{
tracks: gortsplib.Tracks{track},
})
if res.err != nil {
return
}

s.Log(logger.Info, "ready")
stream = res.stream
start = time.Now()
}

pts := time.Since(start)

pkts, err := enc.Encode(nalus, pts)
if err != nil {
return
}

lastPkt := len(pkts) - 1
for i, pkt := range pkts {
if i != lastPkt {
stream.writeData(&data{
trackID: 0,
rtp: pkt,
ptsEqualsDTS: false,
})
} else {
stream.writeData(&data{
trackID: 0,
rtp: pkt,
ptsEqualsDTS: h264.IDRPresent(nalus),
h264NALUs: nalus,
h264PTS: pts,
})
}
}
}

cam, err := rpicamera.New(onData)
if err != nil {
return err
}
defer cam.Close()

defer func() {
if stream != nil {
s.parent.sourceStaticImplSetNotReady(pathSourceStaticSetNotReadyReq{})
}
}()

<-ctx.Done()
return nil
}

// apiSourceDescribe implements sourceStaticImpl.
func (*rpiCameraSource) apiSourceDescribe() interface{} {
return struct {
Type string `json:"type"`
}{"rpiCameraSource"}
}
4 changes: 4 additions & 0 deletions internal/core/source_static.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ func newSourceStatic(
s.ur,
s.fingerprint,
s)

case s.ur == "rpicamera":
s.impl = newRPICameraSource(
s)
}

return s
Expand Down

0 comments on commit 36b9341

Please sign in to comment.