Fix pix_fmt for publishing to RTMP servers
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
+134
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user