From 268629f551e43387a0ebaa8a1e6c63cbc00efa1e Mon Sep 17 00:00:00 2001 From: Alex X Date: Sat, 25 May 2024 16:47:57 +0300 Subject: [PATCH] Fix pix_fmt for publishing to RTMP servers --- README.md | 19 ++++--- pkg/bits/reader.go | 4 ++ pkg/flv/muxer.go | 2 + pkg/h264/sps.go | 135 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0ec1f0d0..c31ed748 100644 --- a/README.md +++ b/README.md @@ -779,7 +779,7 @@ POST http://localhost:1984/api/streams?dst=camera1&src=ffmpeg:http://example.com You can publish any stream to streaming services (YouTube, Telegram, etc.) via RTMP/RTMPS. Important: - Supported codecs: H264 for video and AAC for audio -- Pixel format should be `yuv420p`, for cameras with `yuvj420p` format you SHOULD use [transcoding](#source-ffmpeg) +- AAC audio is required for YouTube, videos without audio will not work - You don't need to enable [RTMP module](#module-rtmp) listening for this task You can use API: @@ -792,16 +792,19 @@ Or config file: ```yaml publish: - # publish stream "tplink_tapo" to Telegram - tplink_tapo: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx - # publish stream "other_camera" to Telegram and YouTube - other_camera: + # publish stream "video_audio_transcode" to Telegram + video_audio_transcode: - rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx - - rtmps://xxx.rtmp.youtube.com/live2/xxxx-xxxx-xxxx-xxxx-xxxx + # publish stream "audio_transcode" to Telegram and YouTube + audio_transcode: + - rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx + - rtmp://xxx.rtmp.youtube.com/live2/xxxx-xxxx-xxxx-xxxx-xxxx streams: - # for TP-Link cameras it's important to use transcoding because of wrong pixel format - tplink_tapo: ffmpeg:rtsp://user:pass@192.168.1.123/stream1#video=h264#hardware#audio=aac + video_audio_transcode: + - ffmpeg:rtsp://user:pass@192.168.1.123/stream1#video=h264#hardware#audio=aac + audio_transcode: + - ffmpeg:rtsp://user:pass@192.168.1.123/stream1#video=copy#audio=aac ``` - **Telegram Desktop App** > Any public or private channel or group (where you admin) > Live stream > Start with... > Start streaming. diff --git a/pkg/bits/reader.go b/pkg/bits/reader.go index 10ea2253..31ea9ef8 100644 --- a/pkg/bits/reader.go +++ b/pkg/bits/reader.go @@ -131,3 +131,7 @@ func (r *Reader) ReadSEGolomb() int32 { func (r *Reader) Left() []byte { return r.buf[r.pos:] } + +func (r *Reader) Pos() (int, byte) { + return r.pos - 1, r.bits +} diff --git a/pkg/flv/muxer.go b/pkg/flv/muxer.go index 499f3aa0..98794265 100644 --- a/pkg/flv/muxer.go +++ b/pkg/flv/muxer.go @@ -54,6 +54,8 @@ func (m *Muxer) GetInit() []byte { sps, pps := h264.GetParameterSet(codec.FmtpLine) if len(sps) == 0 { sps = []byte{0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2} + } else { + h264.FixPixFmt(sps) } if len(pps) == 0 { pps = []byte{0x68, 0xce, 0x38, 0x80} diff --git a/pkg/h264/sps.go b/pkg/h264/sps.go index 71ab5b45..6bcca669 100644 --- a/pkg/h264/sps.go +++ b/pkg/h264/sps.go @@ -1,6 +1,10 @@ package h264 -import "github.com/AlexxIT/go2rtc/pkg/bits" +import ( + "fmt" + + "github.com/AlexxIT/go2rtc/pkg/bits" +) // http://www.itu.int/rec/T-REC-H.264 // https://webrtc.googlesource.com/src/+/refs/heads/main/common_video/h264/sps_parser.cc @@ -229,3 +233,132 @@ func (s *SPS) scaling_list(r *bits.Reader, sizeOfScalingList int) { } } } + +func (s *SPS) Profile() string { + switch s.profile_idc { + case 0x42: + return "Baseline" + case 0x4D: + return "Main" + case 0x58: + return "Extended" + case 0x64: + return "High" + } + return fmt.Sprintf("0x%02X", s.profile_idc) +} + +func (s *SPS) PixFmt() string { + if s.bit_depth_luma_minus8 == 0 { + switch s.chroma_format_idc { + case 1: + if s.video_full_range_flag == 1 { + return "yuvj420p" + } + return "yuv420p" + case 2: + return "yuv422p" + case 3: + return "yuv444p" + } + } + return "" +} + +func (s *SPS) String() string { + return fmt.Sprintf( + "%s %d.%d, %s, %dx%d", + s.Profile(), s.level_idc/10, s.level_idc%10, s.PixFmt(), s.Width(), s.Height(), + ) +} + +// FixPixFmt - change yuvj420p to yuv420p in SPS +// same as "-c:v copy -bsf:v h264_metadata=video_full_range_flag=0" +func FixPixFmt(sps []byte) { + r := bits.NewReader(sps) + + _ = r.ReadByte() + + profile := r.ReadByte() + _ = r.ReadByte() + _ = r.ReadByte() + _ = r.ReadUEGolomb() + + switch profile { + case 100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135: + n := byte(8) + + if r.ReadUEGolomb() == 3 { + _ = r.ReadBit() + n = 12 + } + + _ = r.ReadUEGolomb() + _ = r.ReadUEGolomb() + _ = r.ReadBit() + + if r.ReadBit() != 0 { + for i := byte(0); i < n; i++ { + if r.ReadBit() != 0 { + return // skip + } + } + } + } + + _ = r.ReadUEGolomb() + + switch r.ReadUEGolomb() { + case 0: + _ = r.ReadUEGolomb() + case 1: + _ = r.ReadBit() + _ = r.ReadSEGolomb() + _ = r.ReadSEGolomb() + + n := r.ReadUEGolomb() + for i := uint32(0); i < n; i++ { + _ = r.ReadSEGolomb() + } + } + + _ = r.ReadUEGolomb() + _ = r.ReadBit() + + _ = r.ReadUEGolomb() + _ = r.ReadUEGolomb() + + if r.ReadBit() == 0 { + _ = r.ReadBit() + } + + _ = r.ReadBit() + + if r.ReadBit() != 0 { + _ = r.ReadUEGolomb() + _ = r.ReadUEGolomb() + _ = r.ReadUEGolomb() + _ = r.ReadUEGolomb() + } + + if r.ReadBit() != 0 { + if r.ReadBit() != 0 { + if r.ReadByte() == 255 { + _ = r.ReadUint16() + _ = r.ReadUint16() + } + } + + if r.ReadBit() != 0 { + _ = r.ReadBit() + } + + if r.ReadBit() != 0 { + _ = r.ReadBits8(3) + if r.ReadBit() == 1 { + pos, bit := r.Pos() + sps[pos] &= ^byte(1 << bit) + } + } + } +}