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

implement RTSP support #139

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion cmd/hkcam/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func main() {
var verbose *bool = flag.Bool("verbose", false, "Verbose logging")
var pin *string = flag.String("pin", "00102003", "PIN for HomeKit pairing")
var port *string = flag.String("port", "", "Port on which transport is reachable")
var cameraName *string = flag.String("name", "Camera", "Name to show in HomeKit")

flag.Parse()

Expand All @@ -91,7 +92,7 @@ func main() {

log.Info.Printf("version %s (built at %s)\n", Version, Date)

switchInfo := accessory.Info{Name: "Camera", Firmware: Version, Manufacturer: "Matthias Hochgatterer"}
switchInfo := accessory.Info{Name: *cameraName, Firmware: Version, Manufacturer: "HKCam"}
cam := accessory.NewCamera(switchInfo)

cfg := ffmpeg.Config{
Expand Down
4 changes: 2 additions & 2 deletions ffmpeg/ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type ffmpeg struct {
// If cfg specifies a video loopback, ffmpeg configures a loopback to support simultaneous access to the video device.
func New(cfg Config) *ffmpeg {
var loop *loopback = nil
if cfg.LoopbackFilename != "" {
if cfg.LoopbackFilename != "" && cfg.InputDevice != "rtsp" {
loop = NewLoopback(cfg.InputDevice, cfg.InputFilename, cfg.LoopbackFilename)
}

Expand Down Expand Up @@ -204,7 +204,7 @@ func (f *ffmpeg) videoInputDevice() string {
}

func (f *ffmpeg) videoInputFilename() string {
if f.cfg.LoopbackFilename != "" {
if f.cfg.LoopbackFilename != "" && f.cfg.InputDevice != "rtsp" {
return f.cfg.LoopbackFilename
}

Expand Down
5 changes: 4 additions & 1 deletion ffmpeg/loopback.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ func (l *loopback) Stop() {

// cmd returns a new command to stream video from the input file to the loopback file.
func (l *loopback) execCmd() *exec.Cmd {
cmd := exec.Command("ffmpeg", "-f", l.inputDevice, "-i", l.inputFilename, "-codec:v", "copy", "-f", l.inputDevice, l.loopbackFilename)
var cmd *exec.Cmd
if l.inputDevice != "rtsp" {
cmd = exec.Command("ffmpeg", "-f", l.inputDevice, "-i", l.inputFilename, "-codec:v", "copy", "-f", l.inputDevice, l.loopbackFilename)
}

log.Debug.Println(cmd)

Expand Down
10 changes: 9 additions & 1 deletion ffmpeg/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ func snapshot(width, height uint, inputDevice, inputFilename string) (*Snapshot,
filePath := path.Join(os.TempDir(), fileName)

// height "-2" keeps the aspect ratio
arg := fmt.Sprintf("-f %s -framerate 30 -i %s -vf scale=%d:-2 -frames:v 1 %s", inputDevice, inputFilename, width, filePath)
// arg := fmt.Sprintf("-f %s -framerate 30 -i %s -vf scale=%d:-2 -frames:v 1 %s", inputDevice, inputFilename, width, filePath)
var arg string

// @TODO: Refactor to switch
if inputDevice == "rtsp" {
arg = fmt.Sprintf("-i %s -vf scale=%d:-2 -frames:v 1 %s", inputFilename, width, filePath)
} else {
arg = fmt.Sprintf("-f %s -framerate 30 -i %s -vf scale=%d:-2 -frames:v 1 %s", inputDevice, inputFilename, width, filePath)
}
args := strings.Split(arg, " ")

cmd := exec.Command("ffmpeg", args[:]...)
Expand Down
99 changes: 59 additions & 40 deletions ffmpeg/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,65 @@ func (s *stream) start(video rtp.VideoParameters, audio rtp.AudioParameters) err
// -vsync 2: Fixes "Frame rate very high for a muxer not efficiently supporting it."
// -framerate before -i specifies the framerate for the input, after -i sets it for the output https://stackoverflow.com/questions/38498599/webcam-with-ffmpeg-on-mac-selected-framerate-29-970030-is-not-supported-by-th#38549528

ffmpegVideo := fmt.Sprintf("-f %s", s.inputDevice) +
fmt.Sprintf(" -framerate %d", s.framerate(video.Attributes)) +
fmt.Sprintf("%s", s.videoDecoderOption(video)) +
fmt.Sprintf(" -re -i %s", s.inputFilename) +
" -an" +
fmt.Sprintf(" -codec:v %s", s.videoEncoder(video)) +
" -pix_fmt yuv420p -vsync vfr" +

// height "-2" keeps the aspect ratio
fmt.Sprintf(" -video_size %d:-2", video.Attributes.Width) +
fmt.Sprintf(" -framerate %d", video.Attributes.Framerate) +

// 2019-06-20 (mah)
// Specifying profiles in h264_omx was added in ffmpeg 3.3
// https://github.com/FFmpeg/FFmpeg/commit/13332504c98918447159da2a1a34e377dca360e2#diff-36301d4a4bc7200caee9fbe8e8d8cc20
// hkcam currently uses ffmpeg 3.2
// 2018-08-18 (mah)
// Disable profile arguments because it cannot be parsed
// [h264_omx @ 0x93a410] [Eval @ 0xbeaad160] Undefined constant or missing '(' in 'high'
// fmt.Sprintf(" -profile:v %s", videoProfile(video.CodecParams)) +
fmt.Sprintf(" -level:v %s", videoLevel(video.CodecParams)) +
" -f rawvideo" +
fmt.Sprintf(" -b:v %dk", s.videoBitrate(video)) +
fmt.Sprintf(" -payload_type %d", video.RTP.PayloadType) +
fmt.Sprintf(" -ssrc %d", s.resp.SsrcVideo) +
" -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
fmt.Sprintf(" -srtp_out_params %s", s.req.Video.SrtpKey()) +
fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&pkt_size=%s&timeout=60", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.VideoRtpPort, s.req.ControllerAddr.VideoRtpPort, videoMTU(s.req))

// FIXME (mah) Audio doesn't work yet
// ffmpegAudio := "-vn" +
// fmt.Sprintf(" %s", audioCodecOption(audio)) +
// // compression-level 0-10 (fastest-slowest)
// fmt.Sprintf(" -b:a %dk -bufsize 48k", audio.RTP.Bitrate) +
// fmt.Sprintf(" -ar %s", audioSamplingRate(audio)) +
// fmt.Sprintf(" -payload_type %d", audio.RTP.PayloadType) +
// fmt.Sprintf(" -ssrc %d", s.resp.SsrcAudio) +
// " -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
// fmt.Sprintf(" -srtp_out_params %s", s.req.Audio.SrtpKey()) +
// fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&localrtcpport=%d&timeout=60", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort)
var ffmpegVideo string
if s.inputDevice == "rtsp" {
// rtsp support
ffmpegVideo = fmt.Sprintf("-re -i %s", s.inputFilename) +
Copy link

@alexdin alexdin Jul 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required param if you don't want get many loss packet and picture drop frames
" -rtsp_transport tcp" +
Thats help me in my project reduce this program with pockets loss with rtsp stream on my pi4.

fmt.Sprintf(" -an -vcodec %s -pix_fmt yuv420p -r 30 -x264-params bframes=0", s.videoEncoder(video)) +
fmt.Sprintf(" -video_size %d:-2", video.Attributes.Width) +
fmt.Sprintf(" -level:v %s", videoLevel(video.CodecParams)) +
" -f rawvideo" +
fmt.Sprintf(" -b:v %dk", s.videoBitrate(video)) +
fmt.Sprintf(" -payload_type %d", video.RTP.PayloadType) +
fmt.Sprintf(" -ssrc %d", s.resp.SsrcVideo) +
" -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
fmt.Sprintf(" -srtp_out_params %s", s.req.Video.SrtpKey()) +
fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&localrtcpport=%d&pkt_size=%s&timeout=120", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.VideoRtpPort, s.req.ControllerAddr.VideoRtpPort, s.req.ControllerAddr.VideoRtpPort, videoMTU(s.req))
// audio support may be added later
// fmt.Sprintf(" -map 0:1 -acodec libfdk_aac -profile:a aac_eld -flags +global_header -f null -ar 16k -b:a 24k -bufsize 24k -ac 1 -payload_type 110")+
// fmt.Sprintf(" -ssrc %d -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params %s",s.resp.SsrcAudio, s.req.Audio.SrtpKey())+
// fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&localrtcpport=%d&timeout=120", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort)
} else {
ffmpegVideo = fmt.Sprintf("-f %s", s.inputDevice) +
fmt.Sprintf(" -framerate %d", s.framerate(video.Attributes)) +
fmt.Sprintf("%s", s.videoDecoderOption(video)) +
fmt.Sprintf(" -re -i %s", s.inputFilename) +
" -an" +
fmt.Sprintf(" -codec:v %s", s.videoEncoder(video)) +
" -pix_fmt yuv420p -vsync vfr" +

// height "-2" keeps the aspect ratio
fmt.Sprintf(" -video_size %d:-2", video.Attributes.Width) +
fmt.Sprintf(" -framerate %d", video.Attributes.Framerate) +

// 2019-06-20 (mah)
// Specifying profiles in h264_omx was added in ffmpeg 3.3
// https://github.com/FFmpeg/FFmpeg/commit/13332504c98918447159da2a1a34e377dca360e2#diff-36301d4a4bc7200caee9fbe8e8d8cc20
// hkcam currently uses ffmpeg 3.2
// 2018-08-18 (mah)
// Disable profile arguments because it cannot be parsed
// [h264_omx @ 0x93a410] [Eval @ 0xbeaad160] Undefined constant or missing '(' in 'high'
// fmt.Sprintf(" -profile:v %s", videoProfile(video.CodecParams)) +
fmt.Sprintf(" -level:v %s", videoLevel(video.CodecParams)) +
" -f rawvideo" +
fmt.Sprintf(" -b:v %dk", s.videoBitrate(video)) +
fmt.Sprintf(" -payload_type %d", video.RTP.PayloadType) +
fmt.Sprintf(" -ssrc %d", s.resp.SsrcVideo) +
" -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
fmt.Sprintf(" -srtp_out_params %s", s.req.Video.SrtpKey()) +
fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&pkt_size=%s&timeout=60", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.VideoRtpPort, s.req.ControllerAddr.VideoRtpPort, videoMTU(s.req))
}
// FIXME (mah) Audio doesn't work yet
// ffmpegAudio := "-vn" +
// fmt.Sprintf(" %s", audioCodecOption(audio)) +
// // compression-level 0-10 (fastest-slowest)
// fmt.Sprintf(" -b:a %dk -bufsize 48k", audio.RTP.Bitrate) +
// fmt.Sprintf(" -ar %s", audioSamplingRate(audio)) +
// fmt.Sprintf(" -payload_type %d", audio.RTP.PayloadType) +
// fmt.Sprintf(" -ssrc %d", s.resp.SsrcAudio) +
// " -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80" +
// fmt.Sprintf(" -srtp_out_params %s", s.req.Audio.SrtpKey()) +
// fmt.Sprintf(" srtp://%s:%d?rtcpport=%d&localrtcpport=%d&timeout=60", s.req.ControllerAddr.IPAddr, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort, s.req.ControllerAddr.AudioRtpPort)

args := strings.Split(ffmpegVideo, " ")
cmd := exec.Command("ffmpeg", args[:]...)
Expand Down