rtc2rtmp: Support RTC-to-RTMP remuxing with HEVC. v7.0.43 (#4349)
**Introduce** This pull request builds upon the foundation laid in https://github.com/ossrs/srs/pull/4289 . While the previous work solely implemented unidirectional HEVC support from RTMP to RTC, this submission further enhances it by introducing support for the RTC to RTMP direction. **Usage** Launch SRS with `rtc2rtmp.conf` ```bash ./objs/srs -c conf/rtc2rtmp.conf ``` **Push with WebRTC** Upgrade browser to Chrome(136+) or Safari(18+), then open [WHIP encoder](http://localhost:8080/players/whip.html?schema=http&&codec=hevc), push stream with URL that enables HEVC by query string `codec=hevc`: ```bash http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream&codec=hevc ``` This query string `codec=hevc` is used to select the video codec, and generate lines in the answer SDP. ``` m=video 9 UDP/TLS/RTP/SAVPF 49 123 a=rtpmap:49 H265/90000 ``` The encoder log also show the codec: ``` Audio: opus, 48000HZ, channels: 2, pt: 111 Video: H265, 90000HZ, pt: 49 ``` **Play with RTMP** Play HEVC stream via RTMP. ```bash ffplay -i rtmp://localhost/live/livestream ``` You will see the codec in logs: ``` Stream #0:0: Audio: aac (LC), 48000 Hz, stereo, fltp Stream #0:1: Video: hevc (Main), yuv420p(tv, bt709), 320x240, 30 fps, 30 tbr, 1k tbn ``` You can also use [WHEP player](http://localhost:8080/players/whep.html?schema=http&&codec=hevc) to play the stream. Important refactor with AI: * [AI: Refactor packet cache for RTC frame builder.](b8ffa1630e) * [AI: Refactor the packet copy and free for SrsRtcFrameBuilder](f3487b45d7) * [AI: Refactor the frame detector for SrsRtcFrameBuilder](4ffc1526b9) * [AI: Refactor the packet_video_rtmp for SrsRtcFrameBuilder](81f6aef4ed) * [AI: Add utests for SrsCodecPayload.codec](61eb1c0bfc) * [AI: Add utests for VideoPacketCache in SrsRtcFrameBuilder.](fd25480dfa) * [AI: Add utests for VideoFrameDetector in SrsRtcFrameBuilder.](b4aa977bbd) * [AI: Add regression test for RTC2RTMP with HEVC.](5259a2aac3) --------- Co-authored-by: Jacob Su <suzp1984@gmail.com> Co-authored-by: winlin <winlinvip@gmail.com>
This commit is contained in:
parent
d73ac3670a
commit
cbc98dc0d9
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -42,4 +42,5 @@ cmake-build-debug
|
||||||
/trunk/ide/srs_clion/srs
|
/trunk/ide/srs_clion/srs
|
||||||
/trunk/ide/srs_clion/Testing/
|
/trunk/ide/srs_clion/Testing/
|
||||||
/trunk/ide/vscode-build
|
/trunk/ide/vscode-build
|
||||||
|
/build/
|
||||||
|
|
||||||
|
|
|
||||||
1
trunk/.gitignore
vendored
1
trunk/.gitignore
vendored
|
|
@ -52,3 +52,4 @@ bug
|
||||||
*.svg
|
*.svg
|
||||||
/3rdparty/st-srs/srs
|
/3rdparty/st-srs/srs
|
||||||
/3rdparty/st-srs/.circleci
|
/3rdparty/st-srs/.circleci
|
||||||
|
/test_detail.xml
|
||||||
|
|
|
||||||
6
trunk/3rdparty/srs-bench/README.md
vendored
6
trunk/3rdparty/srs-bench/README.md
vendored
|
|
@ -30,10 +30,10 @@ cd srs-bench && make
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/ossrs/srs.git &&
|
git clone https://github.com/ossrs/srs.git &&
|
||||||
cd srs/trunk && ./configure --h265=on --gb28181=on && make &&
|
cd srs/trunk && ./configure --h265=on --gb28181=on && make &&
|
||||||
./objs/srs -c conf/console.conf
|
./objs/srs -c conf/regression-test-for-clion.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note: Use valgrind to check memory leak, please use `valgrind --leak-check=full ./objs/srs -c conf/console.conf >/dev/null` to start SRS.
|
> Note: Use valgrind to check memory leak, please use `valgrind --leak-check=full ./objs/srs -c conf/regression-test-for-clion.conf >/dev/null` to start SRS.
|
||||||
|
|
||||||
具体场景,请按下面的操作启动测试。
|
具体场景,请按下面的操作启动测试。
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ done
|
||||||
回归测试需要先启动[SRS](https://github.com/ossrs/srs/issues/307),支持WebRTC推拉流:
|
回归测试需要先启动[SRS](https://github.com/ossrs/srs/issues/307),支持WebRTC推拉流:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./objs/srs -c conf/rtc.conf
|
./objs/srs -c conf/regression-test-for-clion.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
然后运行回归测试用例,如果只跑一次,可以直接运行:
|
然后运行回归测试用例,如果只跑一次,可以直接运行:
|
||||||
|
|
|
||||||
36
trunk/3rdparty/srs-bench/srs/ingester.go
vendored
36
trunk/3rdparty/srs-bench/srs/ingester.go
vendored
|
|
@ -40,6 +40,7 @@ import (
|
||||||
|
|
||||||
type videoIngester struct {
|
type videoIngester struct {
|
||||||
sourceVideo string
|
sourceVideo string
|
||||||
|
codecType string // "h264", "hevc", or "vp8"
|
||||||
fps int
|
fps int
|
||||||
markerInterceptor *rtpInterceptor
|
markerInterceptor *rtpInterceptor
|
||||||
sVideoTrack *webrtc.TrackLocalStaticSample
|
sVideoTrack *webrtc.TrackLocalStaticSample
|
||||||
|
|
@ -48,9 +49,26 @@ type videoIngester struct {
|
||||||
readyCancel context.CancelFunc
|
readyCancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVideoIngester(sourceVideo string) *videoIngester {
|
// WithCodec sets the codec type for the video ingester
|
||||||
v := &videoIngester{markerInterceptor: &rtpInterceptor{}, sourceVideo: sourceVideo}
|
func WithCodec(codecType string) func(*videoIngester) {
|
||||||
|
return func(v *videoIngester) {
|
||||||
|
v.codecType = codecType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVideoIngester(sourceVideo string, opts ...func(*videoIngester)) *videoIngester {
|
||||||
|
v := &videoIngester{
|
||||||
|
markerInterceptor: &rtpInterceptor{},
|
||||||
|
sourceVideo: sourceVideo,
|
||||||
|
codecType: "h264", // default codec
|
||||||
|
}
|
||||||
v.ready, v.readyCancel = context.WithCancel(context.Background())
|
v.ready, v.readyCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Apply options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(v)
|
||||||
|
}
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,8 +84,20 @@ func (v *videoIngester) AddTrack(pc *webrtc.PeerConnection, fps int) error {
|
||||||
v.fps = fps
|
v.fps = fps
|
||||||
|
|
||||||
mimeType, trackID := "video/H264", "video"
|
mimeType, trackID := "video/H264", "video"
|
||||||
if strings.HasSuffix(v.sourceVideo, ".ivf") {
|
|
||||||
|
// Determine MIME type based on codec type or file extension
|
||||||
|
switch v.codecType {
|
||||||
|
case "hevc", "h265":
|
||||||
|
mimeType = "video/H265"
|
||||||
|
case "vp8":
|
||||||
mimeType = "video/VP8"
|
mimeType = "video/VP8"
|
||||||
|
case "h264":
|
||||||
|
mimeType = "video/H264"
|
||||||
|
default:
|
||||||
|
// Fallback to file extension detection
|
||||||
|
if strings.HasSuffix(v.sourceVideo, ".ivf") {
|
||||||
|
mimeType = "video/VP8"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
|
||||||
139
trunk/3rdparty/srs-bench/srs/rtc_test.go
vendored
139
trunk/3rdparty/srs-bench/srs/rtc_test.go
vendored
|
|
@ -2432,3 +2432,142 @@ func TestRtcPublish_HttpFlvPlay(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test WebRTC-to-RTMP conversion with HEVC codec (PR #4349)
|
||||||
|
func TestRtcPublish_HttpFlvPlay_HEVC(t *testing.T) {
|
||||||
|
ctx := logger.WithContext(context.Background())
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
|
||||||
|
|
||||||
|
var r0, r1, r2, r3 error
|
||||||
|
defer func(ctx context.Context) {
|
||||||
|
if err := filterTestError(ctx.Err(), r0, r1, r2, r3); err != nil {
|
||||||
|
t.Errorf("Fail for err %+v", err)
|
||||||
|
} else {
|
||||||
|
logger.Tf(ctx, "test done with err %+v", err)
|
||||||
|
}
|
||||||
|
}(ctx)
|
||||||
|
|
||||||
|
var resources []io.Closer
|
||||||
|
defer func() {
|
||||||
|
for _, resource := range resources {
|
||||||
|
_ = resource.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
// The event notify.
|
||||||
|
var thePublisher *testPublisher
|
||||||
|
|
||||||
|
mainReady, mainReadyCancel := context.WithCancel(context.Background())
|
||||||
|
publishReady, publishReadyCancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
streamSuffix := fmt.Sprintf("hevc-publish-flvplay-%v-%v", os.Getpid(), rand.Int())
|
||||||
|
// Objects init.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
doInit := func() (err error) {
|
||||||
|
// Initialize publisher with HEVC codec support.
|
||||||
|
if thePublisher, err = newTestPublisher(registerHEVCCodecs, func(pub *testPublisher) error {
|
||||||
|
pub.streamSuffix = streamSuffix
|
||||||
|
pub.streamQuery = "codec=hevc" // Add codec=hevc parameter for HEVC support
|
||||||
|
pub.videoCodec = "hevc" // Set video codec to HEVC
|
||||||
|
pub.iceReadyCancel = publishReadyCancel
|
||||||
|
resources = append(resources, pub)
|
||||||
|
|
||||||
|
return pub.Setup(*srsVnetClientIP)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init done.
|
||||||
|
mainReadyCancel()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := doInit(); err != nil {
|
||||||
|
r1 = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Run publisher.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-mainReady.Done():
|
||||||
|
r2 = thePublisher.Run(logger.WithContext(ctx), cancel)
|
||||||
|
logger.Tf(ctx, "pub done")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Run player.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-publishReady.Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
player := NewFLVPlayer()
|
||||||
|
defer player.Close()
|
||||||
|
|
||||||
|
r3 = func() error {
|
||||||
|
flvUrl := fmt.Sprintf("http://%v%v-%v.flv", *srsHttpServer, *srsStream, streamSuffix)
|
||||||
|
if err := player.Play(ctx, flvUrl); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nnVideo, nnAudio int
|
||||||
|
var hasVideo, hasAudio bool
|
||||||
|
var hevcDetected bool
|
||||||
|
player.onRecvHeader = func(ha, hv bool) error {
|
||||||
|
hasAudio, hasVideo = ha, hv
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
player.onRecvTag = func(tagType flv.TagType, size, timestamp uint32, tag []byte) error {
|
||||||
|
if tagType == flv.TagTypeAudio {
|
||||||
|
nnAudio++
|
||||||
|
} else if tagType == flv.TagTypeVideo {
|
||||||
|
nnVideo++
|
||||||
|
// Check for HEVC Enhanced RTMP format
|
||||||
|
if len(tag) >= 5 {
|
||||||
|
// Check for Enhanced RTMP header (0x80 | frame_type << 4 | packet_type)
|
||||||
|
// and HEVC fourCC 'hvc1'
|
||||||
|
if (tag[0]&0x80) != 0 && len(tag) >= 5 {
|
||||||
|
if tag[1] == 'h' && tag[2] == 'v' && tag[3] == 'c' && tag[4] == '1' {
|
||||||
|
hevcDetected = true
|
||||||
|
logger.Tf(ctx, "HEVC Enhanced RTMP format detected in video tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "got %v tag, %v %vms %vB, hevc=%v", nnVideo+nnAudio, tagType, timestamp, len(tag), hevcDetected)
|
||||||
|
|
||||||
|
if audioPacketsOK, videoPacketsOK := hasAudio && nnAudio >= 10, hasVideo && nnVideo >= 10; audioPacketsOK && videoPacketsOK && hevcDetected {
|
||||||
|
logger.Tf(ctx, "HEVC Flv recv %v/%v audio, %v/%v video, hevc detected=%v", hasAudio, nnAudio, hasVideo, nnVideo, hevcDetected)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := player.Consume(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
|
||||||
40
trunk/3rdparty/srs-bench/srs/util.go
vendored
40
trunk/3rdparty/srs-bench/srs/util.go
vendored
|
|
@ -674,6 +674,34 @@ func registerMiniCodecsWithoutNack(api *testWebRTCAPI) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements interface testWebRTCAPIInitFunc to init testWebRTCAPI with HEVC support
|
||||||
|
func registerHEVCCodecs(api *testWebRTCAPI) error {
|
||||||
|
v := api
|
||||||
|
|
||||||
|
// Register Opus audio codec
|
||||||
|
if err := v.mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: webrtc.RTPCodecCapability{webrtc.MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
|
||||||
|
PayloadType: 111,
|
||||||
|
}, webrtc.RTPCodecTypeAudio); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register HEVC/H.265 video codec
|
||||||
|
videoRTCPFeedback := []webrtc.RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
|
||||||
|
if err := v.mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
|
||||||
|
RTPCodecCapability: webrtc.RTPCodecCapability{webrtc.MimeTypeH265, 90000, 0, "profile-id=1", videoRTCPFeedback},
|
||||||
|
PayloadType: 49, // Use payload type 49 for HEVC as mentioned in PR description
|
||||||
|
}, webrtc.RTPCodecTypeVideo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := webrtc.RegisterDefaultInterceptors(v.mediaEngine, v.registry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func newTestWebRTCAPI(inits ...testWebRTCAPIInitFunc) (*testWebRTCAPI, error) {
|
func newTestWebRTCAPI(inits ...testWebRTCAPIInitFunc) (*testWebRTCAPI, error) {
|
||||||
v := &testWebRTCAPI{}
|
v := &testWebRTCAPI{}
|
||||||
|
|
||||||
|
|
@ -1052,6 +1080,10 @@ type testPublisher struct {
|
||||||
api *testWebRTCAPI
|
api *testWebRTCAPI
|
||||||
// Optional suffix for stream url.
|
// Optional suffix for stream url.
|
||||||
streamSuffix string
|
streamSuffix string
|
||||||
|
// Optional query parameters for stream url.
|
||||||
|
streamQuery string
|
||||||
|
// Video codec type: "h264", "hevc", "vp8"
|
||||||
|
videoCodec string
|
||||||
// To cancel the publisher, pass by Run.
|
// To cancel the publisher, pass by Run.
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// The config for peer connection.
|
// The config for peer connection.
|
||||||
|
|
@ -1064,7 +1096,8 @@ func newTestPublisher(init testWebRTCAPIInitFunc, options ...testPublisherOption
|
||||||
sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
|
sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
|
||||||
|
|
||||||
v := &testPublisher{
|
v := &testPublisher{
|
||||||
pcc: &webrtc.Configuration{},
|
pcc: &webrtc.Configuration{},
|
||||||
|
videoCodec: "h264", // Default to H.264
|
||||||
}
|
}
|
||||||
|
|
||||||
api, err := newTestWebRTCAPI(init)
|
api, err := newTestWebRTCAPI(init)
|
||||||
|
|
@ -1084,7 +1117,7 @@ func newTestPublisher(init testWebRTCAPIInitFunc, options ...testPublisherOption
|
||||||
v.aIngester = newAudioIngester(sourceAudio)
|
v.aIngester = newAudioIngester(sourceAudio)
|
||||||
}
|
}
|
||||||
if sourceVideo != "" {
|
if sourceVideo != "" {
|
||||||
v.vIngester = newVideoIngester(sourceVideo)
|
v.vIngester = newVideoIngester(sourceVideo, WithCodec(v.videoCodec))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the interceptors for packets.
|
// Setup the interceptors for packets.
|
||||||
|
|
@ -1148,6 +1181,9 @@ func (v *testPublisher) Run(ctx context.Context, cancel context.CancelFunc) erro
|
||||||
if v.streamSuffix != "" {
|
if v.streamSuffix != "" {
|
||||||
r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
|
r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
|
||||||
}
|
}
|
||||||
|
if v.streamQuery != "" {
|
||||||
|
r = fmt.Sprintf("%v?%v", r, v.streamQuery)
|
||||||
|
}
|
||||||
sourceVideo, sourceAudio, fps := *srsPublishVideo, *srsPublishAudio, *srsPublishVideoFps
|
sourceVideo, sourceAudio, fps := *srsPublishVideo, *srsPublishAudio, *srsPublishVideoFps
|
||||||
|
|
||||||
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v",
|
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v",
|
||||||
|
|
|
||||||
2
trunk/configure
vendored
2
trunk/configure
vendored
|
|
@ -465,7 +465,7 @@ if [[ $SRS_UTEST == YES ]]; then
|
||||||
"srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload"
|
"srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload"
|
||||||
"srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_rtc" "srs_utest_config2"
|
"srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_rtc" "srs_utest_config2"
|
||||||
"srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3"
|
"srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3"
|
||||||
"srs_utest_st")
|
"srs_utest_st" "srs_utest_rtc2")
|
||||||
if [[ $SRS_SRT == YES ]]; then
|
if [[ $SRS_SRT == YES ]]; then
|
||||||
MODULE_FILES+=("srs_utest_srt")
|
MODULE_FILES+=("srs_utest_srt")
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ The changelog for SRS.
|
||||||
<a name="v7-changes"></a>
|
<a name="v7-changes"></a>
|
||||||
|
|
||||||
## SRS 7.0 Changelog
|
## SRS 7.0 Changelog
|
||||||
|
* v7.0, 2025-07-03, Merge [#4349](https://github.com/ossrs/srs/pull/4349): rtc2rtmp: Support WebRTC-to-RTMP conversion with HEVC. v7.0.43 (#4349)
|
||||||
* v7.0, 2025-06-04, Merge [#4310](https://github.com/ossrs/srs/pull/4310): Player: Get codec by webrtc api: pc.getStats. v7.0.42 (#4310)
|
* v7.0, 2025-06-04, Merge [#4310](https://github.com/ossrs/srs/pull/4310): Player: Get codec by webrtc api: pc.getStats. v7.0.42 (#4310)
|
||||||
* v7.0, 2025-06-04, Merge [#4325](https://github.com/ossrs/srs/pull/4325): fix bug: loop transcoding #3516. v7.0.41 (#4325)
|
* v7.0, 2025-06-04, Merge [#4325](https://github.com/ossrs/srs/pull/4325): fix bug: loop transcoding #3516. v7.0.41 (#4325)
|
||||||
* v7.0, 2025-06-04, Merge [#4341](https://github.com/ossrs/srs/pull/4341): Update the release in the README for consistent. v7.0.40 (#4341)
|
* v7.0, 2025-06-04, Merge [#4341](https://github.com/ossrs/srs/pull/4341): Update the release in the README for consistent. v7.0.40 (#4341)
|
||||||
|
|
|
||||||
|
|
@ -1961,7 +1961,7 @@ srs_error_t SrsGbMuxer::write_h265_ipb_frame(char* frame, int frame_size, uint32
|
||||||
// F.3.29 intra random access point (IRAP) picture
|
// F.3.29 intra random access point (IRAP) picture
|
||||||
// ITU-T-H.265-2021.pdf, page 462.
|
// ITU-T-H.265-2021.pdf, page 462.
|
||||||
SrsVideoAvcFrameType frame_type = SrsVideoAvcFrameTypeInterFrame;
|
SrsVideoAvcFrameType frame_type = SrsVideoAvcFrameTypeInterFrame;
|
||||||
if (nt >= SrsHevcNaluType_CODED_SLICE_BLA && nt <= SrsHevcNaluType_RESERVED_23) {
|
if (SrsIsIRAP(nt)) {
|
||||||
frame_type = SrsVideoAvcFrameTypeKeyFrame;
|
frame_type = SrsVideoAvcFrameTypeKeyFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1500,7 +1500,7 @@ srs_error_t SrsRtcPublishStream::check_send_nacks()
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SrsRtcPublishStream::on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtspPacketPayloadType* ppt)
|
void SrsRtcPublishStream::on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtpPacketPayloadType* ppt)
|
||||||
{
|
{
|
||||||
// No payload, ignore.
|
// No payload, ignore.
|
||||||
if (buf->empty()) {
|
if (buf->empty()) {
|
||||||
|
|
@ -2725,7 +2725,7 @@ srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc
|
||||||
// Only choose one match opus codec.
|
// Only choose one match opus codec.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (remote_media_desc.is_video() && ruc->codec_ == "av1") {
|
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->codec_) == SrsVideoCodecIdAV1) {
|
||||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
|
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
|
||||||
if (payloads.empty()) {
|
if (payloads.empty()) {
|
||||||
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
||||||
|
|
@ -2762,7 +2762,7 @@ srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc
|
||||||
track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
|
track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (remote_media_desc.is_video() && ruc->codec_ == "hevc") {
|
} else if (remote_media_desc.is_video() && srs_video_codec_str2id(ruc->codec_) == SrsVideoCodecIdHEVC) {
|
||||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
|
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
|
||||||
if (payloads.empty()) {
|
if (payloads.empty()) {
|
||||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.265 payload type");
|
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.265 payload type");
|
||||||
|
|
@ -2819,7 +2819,7 @@ srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc
|
||||||
bool profile_matched = (!has_42e01f || h264_param.profile_level_id == "42e01f");
|
bool profile_matched = (!has_42e01f || h264_param.profile_level_id == "42e01f");
|
||||||
|
|
||||||
// Try to pick the "best match" H.264 payload type.
|
// Try to pick the "best match" H.264 payload type.
|
||||||
if (profile_matched && h264_param.packetization_mode == "1" && h264_param.level_asymmerty_allow == "1") {
|
if (profile_matched && h264_param.packetization_mode == "1" && h264_param.level_asymmetry_allow == "1") {
|
||||||
// if the playload is opus, and the encoding_param_ is channel
|
// if the playload is opus, and the encoding_param_ is channel
|
||||||
SrsVideoPayload* video_payload = new SrsVideoPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_);
|
SrsVideoPayload* video_payload = new SrsVideoPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_);
|
||||||
video_payload->set_h264_param_desc(payload.format_specific_param_);
|
video_payload->set_h264_param_desc(payload.format_specific_param_);
|
||||||
|
|
@ -3127,24 +3127,19 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s
|
||||||
remote_payload = payloads.at(0);
|
remote_payload = payloads.at(0);
|
||||||
track_descs = source->get_track_desc("audio", "opus");
|
track_descs = source->get_track_desc("audio", "opus");
|
||||||
} else if (remote_media_desc.is_video()) {
|
} else if (remote_media_desc.is_video()) {
|
||||||
std::string prefer_codec = ruc->codec_;
|
SrsVideoCodecId prefer_codec = srs_video_codec_str2id(ruc->codec_);
|
||||||
if (prefer_codec.empty()) {
|
if (prefer_codec == SrsVideoCodecIdReserved) {
|
||||||
// Get the source codec if not specified.
|
// Get the source codec if not specified.
|
||||||
std::vector<SrsRtcTrackDescription*> track_descs = source->get_track_desc("video", "");
|
std::vector<SrsRtcTrackDescription*> track_descs = source->get_track_desc("video", "");
|
||||||
if (!track_descs.empty()) {
|
if (!track_descs.empty()) {
|
||||||
std::string codec_name = track_descs.at(0)->media_->name_;
|
SrsRtcTrackDescription* first_track = track_descs.at(0);
|
||||||
std::transform(codec_name.begin(), codec_name.end(), codec_name.begin(), ::tolower);
|
prefer_codec = srs_video_codec_str2id(first_track->media_->name_);
|
||||||
if (codec_name == "h265") {
|
|
||||||
prefer_codec = "hevc";
|
|
||||||
} else {
|
|
||||||
prefer_codec = codec_name;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no video track in source");
|
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no video track in source");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefer_codec == "av1") {
|
if (prefer_codec == SrsVideoCodecIdAV1) {
|
||||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
|
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
|
||||||
if (payloads.empty()) {
|
if (payloads.empty()) {
|
||||||
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
||||||
|
|
@ -3162,7 +3157,7 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s
|
||||||
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
|
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
|
||||||
track_descs = source->get_track_desc("video", "AV1X");
|
track_descs = source->get_track_desc("video", "AV1X");
|
||||||
}
|
}
|
||||||
} else if (prefer_codec == "hevc") {
|
} else if (prefer_codec == SrsVideoCodecIdHEVC) {
|
||||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
|
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
|
||||||
if (payloads.empty()) {
|
if (payloads.empty()) {
|
||||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h265 payload type");
|
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h265 payload type");
|
||||||
|
|
@ -3312,7 +3307,7 @@ void video_track_generate_play_offer(SrsRtcTrackDescription* track, string mid,
|
||||||
|
|
||||||
SrsVideoPayload* payload = (SrsVideoPayload*)track->media_;
|
SrsVideoPayload* payload = (SrsVideoPayload*)track->media_;
|
||||||
|
|
||||||
if (payload->name_ == "H265") {
|
if (srs_video_codec_str2id(payload->name_) == SrsVideoCodecIdHEVC) {
|
||||||
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type_h265());
|
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type_h265());
|
||||||
} else {
|
} else {
|
||||||
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type());
|
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type());
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
// A RTC publish stream, client push and publish stream to SRS.
|
// A RTC publish stream, client push and publish stream to SRS.
|
||||||
class SrsRtcPublishStream : public ISrsRtspPacketDecodeHandler
|
class SrsRtcPublishStream : public ISrsRtpPacketDecodeHandler
|
||||||
, public ISrsRtcPublishStream, public ISrsRtcPLIWorkerHandler
|
, public ISrsRtcPublishStream, public ISrsRtcPLIWorkerHandler
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
@ -378,7 +378,7 @@ private:
|
||||||
public:
|
public:
|
||||||
srs_error_t check_send_nacks();
|
srs_error_t check_send_nacks();
|
||||||
public:
|
public:
|
||||||
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtspPacketPayloadType* ppt);
|
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtpPacketPayloadType* ppt);
|
||||||
private:
|
private:
|
||||||
srs_error_t send_periodic_twcc();
|
srs_error_t send_periodic_twcc();
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264
|
||||||
// @see https://tools.ietf.org/html/rfc6184#section-6.3
|
// @see https://tools.ietf.org/html/rfc6184#section-6.3
|
||||||
h264_param.packetization_mode = kv[1];
|
h264_param.packetization_mode = kv[1];
|
||||||
} else if (kv[0] == "level-asymmetry-allowed") {
|
} else if (kv[0] == "level-asymmetry-allowed") {
|
||||||
h264_param.level_asymmerty_allow = kv[1];
|
h264_param.level_asymmetry_allow = kv[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264
|
||||||
if (h264_param.packetization_mode.empty()) {
|
if (h264_param.packetization_mode.empty()) {
|
||||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: packetization-mode");
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: packetization-mode");
|
||||||
}
|
}
|
||||||
if (h264_param.level_asymmerty_allow.empty()) {
|
if (h264_param.level_asymmetry_allow.empty()) {
|
||||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: level-asymmetry-allowed");
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h264 param: level-asymmetry-allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ struct H264SpecificParam
|
||||||
{
|
{
|
||||||
std::string profile_level_id;
|
std::string profile_level_id;
|
||||||
std::string packetization_mode;
|
std::string packetization_mode;
|
||||||
std::string level_asymmerty_allow;
|
std::string level_asymmetry_allow;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct H265SpecificParam
|
struct H265SpecificParam
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -312,18 +312,12 @@ private:
|
||||||
srs_error_t consume_packets(std::vector<SrsRtpPacket*>& pkts);
|
srs_error_t consume_packets(std::vector<SrsRtpPacket*>& pkts);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Collect and build WebRTC RTP packets to AV frames.
|
// Video packet cache for RTP packet management
|
||||||
class SrsRtcFrameBuilder
|
// TODO: Maybe should use SrsRtpRingBuffer?
|
||||||
|
class SrsRtcFrameBuilderVideoPacketCache
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
ISrsStreamBridge* bridge_;
|
const static uint16_t cache_size_ = 512;
|
||||||
private:
|
|
||||||
bool is_first_audio_;
|
|
||||||
SrsAudioTranscoder *codec_;
|
|
||||||
private:
|
|
||||||
const static uint16_t s_cache_size = 512;
|
|
||||||
//TODO:use SrsRtpRingBuffer
|
|
||||||
//TODO:jitter buffer class
|
|
||||||
struct RtcPacketCache {
|
struct RtcPacketCache {
|
||||||
bool in_use;
|
bool in_use;
|
||||||
uint16_t sn;
|
uint16_t sn;
|
||||||
|
|
@ -331,22 +325,74 @@ private:
|
||||||
uint32_t rtp_ts;
|
uint32_t rtp_ts;
|
||||||
SrsRtpPacket* pkt;
|
SrsRtpPacket* pkt;
|
||||||
};
|
};
|
||||||
RtcPacketCache cache_video_pkts_[s_cache_size];
|
RtcPacketCache cache_pkts_[cache_size_];
|
||||||
|
public:
|
||||||
|
SrsRtcFrameBuilderVideoPacketCache();
|
||||||
|
virtual ~SrsRtcFrameBuilderVideoPacketCache();
|
||||||
|
public:
|
||||||
|
SrsRtpPacket* get_packet(uint16_t sequence_number);
|
||||||
|
void store_packet(SrsRtpPacket* pkt);
|
||||||
|
void clear_all();
|
||||||
|
SrsRtpPacket* take_packet(uint16_t sequence_number);
|
||||||
|
public:
|
||||||
|
// Find next lost sequence number starting from current_sn
|
||||||
|
// Returns: lost_sn if found, -1 if complete frame found (sets end_sn), -2 if cache overflow
|
||||||
|
int32_t find_next_lost_sn(uint16_t current_sn, uint16_t header_sn, uint16_t& end_sn);
|
||||||
|
// Check if frame is complete by verifying FU-A start/end fragment counts match
|
||||||
|
bool check_frame_complete(const uint16_t start, const uint16_t end);
|
||||||
|
private:
|
||||||
|
bool is_slot_in_use(uint16_t sequence_number);
|
||||||
|
uint32_t get_rtp_timestamp(uint16_t sequence_number);
|
||||||
|
inline uint16_t cache_index(uint16_t sequence_number) {
|
||||||
|
return sequence_number % cache_size_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Video frame detector for managing frame boundaries and packet loss detection
|
||||||
|
class SrsRtcFrameBuilderVideoFrameDetector
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
SrsRtcFrameBuilderVideoPacketCache* video_cache_;
|
||||||
uint16_t header_sn_;
|
uint16_t header_sn_;
|
||||||
uint16_t lost_sn_;
|
uint16_t lost_sn_;
|
||||||
int64_t rtp_key_frame_ts_;
|
int64_t rtp_key_frame_ts_;
|
||||||
|
public:
|
||||||
|
SrsRtcFrameBuilderVideoFrameDetector(SrsRtcFrameBuilderVideoPacketCache* cache);
|
||||||
|
virtual ~SrsRtcFrameBuilderVideoFrameDetector();
|
||||||
|
public:
|
||||||
|
void on_keyframe_start(SrsRtpPacket* pkt);
|
||||||
|
srs_error_t detect_frame(uint16_t received, uint16_t& frame_start, uint16_t& frame_end, bool& frame_ready);
|
||||||
|
srs_error_t detect_next_frame(uint16_t next_head, uint16_t& next_start, uint16_t& next_end, bool& next_ready);
|
||||||
|
void on_keyframe_detached();
|
||||||
|
bool is_lost_sn(uint16_t received);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect and build WebRTC RTP packets to AV frames.
|
||||||
|
class SrsRtcFrameBuilder
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ISrsStreamBridge* bridge_;
|
||||||
|
private:
|
||||||
|
bool is_first_audio_;
|
||||||
|
SrsAudioTranscoder *audio_transcoder_;
|
||||||
|
|
||||||
|
SrsVideoCodecId video_codec_;
|
||||||
|
private:
|
||||||
|
SrsRtcFrameBuilderVideoPacketCache* video_cache_;
|
||||||
|
SrsRtcFrameBuilderVideoFrameDetector* frame_detector_;
|
||||||
private:
|
private:
|
||||||
// The state for timestamp sync state. -1 for init. 0 not sync. 1 sync.
|
// The state for timestamp sync state. -1 for init. 0 not sync. 1 sync.
|
||||||
int sync_state_;
|
int sync_state_;
|
||||||
private:
|
private:
|
||||||
// For OBS WHIP, send SPS/PPS in dedicated RTP packet.
|
// For OBS WHIP, send (VPS/)SPS/PPS in dedicated RTP packet.
|
||||||
|
SrsRtpPacket* obs_whip_vps_;
|
||||||
SrsRtpPacket* obs_whip_sps_;
|
SrsRtpPacket* obs_whip_sps_;
|
||||||
SrsRtpPacket* obs_whip_pps_;
|
SrsRtpPacket* obs_whip_pps_;
|
||||||
public:
|
public:
|
||||||
SrsRtcFrameBuilder(ISrsStreamBridge* bridge);
|
SrsRtcFrameBuilder(ISrsStreamBridge* bridge);
|
||||||
virtual ~SrsRtcFrameBuilder();
|
virtual ~SrsRtcFrameBuilder();
|
||||||
public:
|
public:
|
||||||
srs_error_t initialize(SrsRequest* r);
|
srs_error_t initialize(SrsRequest* r, SrsAudioCodecId audio_codec, SrsVideoCodecId video_codec);
|
||||||
virtual srs_error_t on_publish();
|
virtual srs_error_t on_publish();
|
||||||
virtual void on_unpublish();
|
virtual void on_unpublish();
|
||||||
virtual srs_error_t on_rtp(SrsRtpPacket *pkt);
|
virtual srs_error_t on_rtp(SrsRtpPacket *pkt);
|
||||||
|
|
@ -358,14 +404,12 @@ private:
|
||||||
srs_error_t packet_video_key_frame(SrsRtpPacket* pkt);
|
srs_error_t packet_video_key_frame(SrsRtpPacket* pkt);
|
||||||
srs_error_t packet_sequence_header_avc(SrsRtpPacket* pkt);
|
srs_error_t packet_sequence_header_avc(SrsRtpPacket* pkt);
|
||||||
srs_error_t do_packet_sequence_header_avc(SrsRtpPacket* pkt, SrsSample* sps, SrsSample* pps);
|
srs_error_t do_packet_sequence_header_avc(SrsRtpPacket* pkt, SrsSample* sps, SrsSample* pps);
|
||||||
|
srs_error_t packet_sequence_header_hevc(SrsRtpPacket* pkt);
|
||||||
|
srs_error_t do_packet_sequence_header_hevc(SrsRtpPacket* pkt, SrsSample* vps, SrsSample* sps, SrsSample* pps);
|
||||||
private:
|
private:
|
||||||
inline uint16_t cache_index(uint16_t current_sn) {
|
|
||||||
return current_sn % s_cache_size;
|
|
||||||
}
|
|
||||||
int32_t find_next_lost_sn(uint16_t current_sn, uint16_t& end_sn);
|
|
||||||
bool check_frame_complete(const uint16_t start, const uint16_t end);
|
|
||||||
srs_error_t packet_video_rtmp(const uint16_t start, const uint16_t end);
|
srs_error_t packet_video_rtmp(const uint16_t start, const uint16_t end);
|
||||||
void clear_cached_video();
|
int calculate_packet_payload_size(SrsRtpPacket* pkt);
|
||||||
|
void write_packet_payload_to_buffer(SrsRtpPacket* pkt, SrsBuffer& payload, int& nalu_len);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -383,11 +427,21 @@ public:
|
||||||
int sample_;
|
int sample_;
|
||||||
|
|
||||||
std::vector<std::string> rtcp_fbs_;
|
std::vector<std::string> rtcp_fbs_;
|
||||||
|
private:
|
||||||
|
// The cached codec ID, corresponding to name_.
|
||||||
|
// For video, you can convert it to type SrsVideoCodecId
|
||||||
|
// For audio, you can convert it to type SrsAudioCodecId
|
||||||
|
// Note: Set up to -1, which means not initialized/cached yet
|
||||||
|
// Note: Won't copy codec_, it will be recalculated when codec(bool) is called
|
||||||
|
int8_t codec_;
|
||||||
public:
|
public:
|
||||||
SrsCodecPayload();
|
SrsCodecPayload();
|
||||||
SrsCodecPayload(uint8_t pt, std::string encode_name, int sample);
|
SrsCodecPayload(uint8_t pt, std::string encode_name, int sample);
|
||||||
virtual ~SrsCodecPayload();
|
virtual ~SrsCodecPayload();
|
||||||
public:
|
public:
|
||||||
|
// Get codec ID with context information about whether it's video or audio
|
||||||
|
// Returns the numeric codec ID, with caching for performance
|
||||||
|
int8_t codec(bool video);
|
||||||
virtual SrsCodecPayload* copy();
|
virtual SrsCodecPayload* copy();
|
||||||
virtual SrsMediaPayloadType generate_media_payload_type();
|
virtual SrsMediaPayloadType generate_media_payload_type();
|
||||||
};
|
};
|
||||||
|
|
@ -593,25 +647,25 @@ protected:
|
||||||
virtual srs_error_t do_check_send_nacks(uint32_t& timeout_nacks);
|
virtual srs_error_t do_check_send_nacks(uint32_t& timeout_nacks);
|
||||||
};
|
};
|
||||||
|
|
||||||
class SrsRtcAudioRecvTrack : public SrsRtcRecvTrack, public ISrsRtspPacketDecodeHandler
|
class SrsRtcAudioRecvTrack : public SrsRtcRecvTrack, public ISrsRtpPacketDecodeHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SrsRtcAudioRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc);
|
SrsRtcAudioRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc);
|
||||||
virtual ~SrsRtcAudioRecvTrack();
|
virtual ~SrsRtcAudioRecvTrack();
|
||||||
public:
|
public:
|
||||||
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtspPacketPayloadType* ppt);
|
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtpPacketPayloadType* ppt);
|
||||||
public:
|
public:
|
||||||
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt);
|
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt);
|
||||||
virtual srs_error_t check_send_nacks();
|
virtual srs_error_t check_send_nacks();
|
||||||
};
|
};
|
||||||
|
|
||||||
class SrsRtcVideoRecvTrack : public SrsRtcRecvTrack, public ISrsRtspPacketDecodeHandler
|
class SrsRtcVideoRecvTrack : public SrsRtcRecvTrack, public ISrsRtpPacketDecodeHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SrsRtcVideoRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* stream_descs);
|
SrsRtcVideoRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* stream_descs);
|
||||||
virtual ~SrsRtcVideoRecvTrack();
|
virtual ~SrsRtcVideoRecvTrack();
|
||||||
public:
|
public:
|
||||||
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtspPacketPayloadType* ppt);
|
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtpPacketPayloadType* ppt);
|
||||||
public:
|
public:
|
||||||
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt);
|
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt);
|
||||||
virtual srs_error_t check_send_nacks();
|
virtual srs_error_t check_send_nacks();
|
||||||
|
|
|
||||||
|
|
@ -708,7 +708,7 @@ srs_error_t SrsSrtFrameBuilder::on_hevc_frame(SrsTsMessage* msg, vector<pair<cha
|
||||||
// 4 bytes for nalu length.
|
// 4 bytes for nalu length.
|
||||||
frame_size += 4 + ipb_frames[i].second;
|
frame_size += 4 + ipb_frames[i].second;
|
||||||
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(ipb_frames[i].first[0]);
|
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(ipb_frames[i].first[0]);
|
||||||
if ((nalu_type >= SrsHevcNaluType_CODED_SLICE_BLA) && (nalu_type <= SrsHevcNaluType_RESERVED_23)) {
|
if (SrsIsIRAP(nalu_type)) {
|
||||||
frame_type = SrsVideoAvcFrameTypeKeyFrame;
|
frame_type = SrsVideoAvcFrameTypeKeyFrame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@
|
||||||
|
|
||||||
#define VERSION_MAJOR 7
|
#define VERSION_MAJOR 7
|
||||||
#define VERSION_MINOR 0
|
#define VERSION_MINOR 0
|
||||||
#define VERSION_REVISION 42
|
#define VERSION_REVISION 43
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <algorithm>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#include <srs_kernel_error.hpp>
|
#include <srs_kernel_error.hpp>
|
||||||
|
|
@ -41,6 +42,26 @@ string srs_video_codec_id2str(SrsVideoCodecId codec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SrsVideoCodecId srs_video_codec_str2id(const std::string &codec)
|
||||||
|
{
|
||||||
|
std::string upper_codec = codec;
|
||||||
|
std::transform(upper_codec.begin(), upper_codec.end(), upper_codec.begin(), ::toupper);
|
||||||
|
|
||||||
|
if (upper_codec == "H264" || upper_codec == "AVC") {
|
||||||
|
return SrsVideoCodecIdAVC;
|
||||||
|
} else if (upper_codec == "H265" || upper_codec == "HEVC") {
|
||||||
|
return SrsVideoCodecIdHEVC;
|
||||||
|
} else if (upper_codec == "AV1") {
|
||||||
|
return SrsVideoCodecIdAV1;
|
||||||
|
} else if (upper_codec == "VP6") {
|
||||||
|
return SrsVideoCodecIdOn2VP6;
|
||||||
|
} else if (upper_codec == "VP6A") {
|
||||||
|
return SrsVideoCodecIdOn2VP6WithAlphaChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SrsVideoCodecIdReserved;
|
||||||
|
}
|
||||||
|
|
||||||
string srs_audio_codec_id2str(SrsAudioCodecId codec)
|
string srs_audio_codec_id2str(SrsAudioCodecId codec)
|
||||||
{
|
{
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
|
|
@ -68,6 +89,25 @@ string srs_audio_codec_id2str(SrsAudioCodecId codec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SrsAudioCodecId srs_audio_codec_str2id(const std::string &codec)
|
||||||
|
{
|
||||||
|
// to uppercase
|
||||||
|
std::string upper_codec = codec;
|
||||||
|
std::transform(upper_codec.begin(), upper_codec.end(), upper_codec.begin(), ::toupper);
|
||||||
|
|
||||||
|
if (upper_codec == "AAC") {
|
||||||
|
return SrsAudioCodecIdAAC;
|
||||||
|
} else if (upper_codec == "MP3") {
|
||||||
|
return SrsAudioCodecIdMP3;
|
||||||
|
} else if (upper_codec == "OPUS") {
|
||||||
|
return SrsAudioCodecIdOpus;
|
||||||
|
} else if (upper_codec == "SPEEX") {
|
||||||
|
return SrsAudioCodecIdSpeex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SrsAudioCodecIdReserved1;
|
||||||
|
}
|
||||||
|
|
||||||
SrsAudioSampleRate srs_audio_sample_rate_from_number(uint32_t v)
|
SrsAudioSampleRate srs_audio_sample_rate_from_number(uint32_t v)
|
||||||
{
|
{
|
||||||
if (v == 5512) return SrsAudioSampleRate5512;
|
if (v == 5512) return SrsAudioSampleRate5512;
|
||||||
|
|
@ -667,7 +707,7 @@ srs_error_t SrsVideoFrame::add_sample(char* bytes, int size)
|
||||||
if (c && c->id == SrsVideoCodecIdHEVC) {
|
if (c && c->id == SrsVideoCodecIdHEVC) {
|
||||||
#ifdef SRS_H265
|
#ifdef SRS_H265
|
||||||
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(bytes[0]);
|
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(bytes[0]);
|
||||||
has_idr = (SrsHevcNaluType_CODED_SLICE_BLA <= nalu_type) && (nalu_type <= SrsHevcNaluType_RESERVED_23);
|
has_idr = SrsIsIRAP(nalu_type);
|
||||||
return err;
|
return err;
|
||||||
#else
|
#else
|
||||||
return srs_error_new(ERROR_HEVC_DISABLED, "H.265 is disabled");
|
return srs_error_new(ERROR_HEVC_DISABLED, "H.265 is disabled");
|
||||||
|
|
@ -751,6 +791,7 @@ srs_error_t SrsVideoFrame::parse_avc_bframe(const SrsSample* sample, bool& is_b_
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SRS_H265
|
||||||
srs_error_t SrsVideoFrame::parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type)
|
srs_error_t SrsVideoFrame::parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type)
|
||||||
{
|
{
|
||||||
srs_error_t err = srs_success;
|
srs_error_t err = srs_success;
|
||||||
|
|
@ -831,6 +872,7 @@ srs_error_t SrsVideoFrame::parse_hevc_bframe(const SrsSample* sample, SrsFormat
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SrsFormat::SrsFormat()
|
SrsFormat::SrsFormat()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ class SrsBuffer;
|
||||||
class SrsBitBuffer;
|
class SrsBitBuffer;
|
||||||
class SrsFormat;
|
class SrsFormat;
|
||||||
|
|
||||||
|
/* Extended VideoTagHeader
|
||||||
|
* defined in reference link:
|
||||||
|
* https://veovera.org/docs/enhanced/enhanced-rtmp-v1.pdf
|
||||||
|
* */
|
||||||
|
#define SRS_FLV_IS_EX_HEADER 0x80
|
||||||
|
|
||||||
// @see: https://datatracker.ietf.org/doc/html/rfc6184#section-1.3
|
// @see: https://datatracker.ietf.org/doc/html/rfc6184#section-1.3
|
||||||
const int SrsAvcNaluHeaderSize = 1;
|
const int SrsAvcNaluHeaderSize = 1;
|
||||||
// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
||||||
|
|
@ -57,6 +63,7 @@ enum SrsVideoCodecId
|
||||||
SrsVideoCodecIdAV1 = 13,
|
SrsVideoCodecIdAV1 = 13,
|
||||||
};
|
};
|
||||||
std::string srs_video_codec_id2str(SrsVideoCodecId codec);
|
std::string srs_video_codec_id2str(SrsVideoCodecId codec);
|
||||||
|
SrsVideoCodecId srs_video_codec_str2id(const std::string& codec);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The video AVC frame trait(characteristic).
|
* The video AVC frame trait(characteristic).
|
||||||
|
|
@ -173,6 +180,7 @@ enum SrsAudioCodecId
|
||||||
SrsAudioCodecIdReservedDeviceSpecificSound = 15,
|
SrsAudioCodecIdReservedDeviceSpecificSound = 15,
|
||||||
};
|
};
|
||||||
std::string srs_audio_codec_id2str(SrsAudioCodecId codec);
|
std::string srs_audio_codec_id2str(SrsAudioCodecId codec);
|
||||||
|
SrsAudioCodecId srs_audio_codec_str2id(const std::string& codec);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The audio AAC frame trait(characteristic).
|
* The audio AAC frame trait(characteristic).
|
||||||
|
|
@ -506,6 +514,7 @@ enum SrsHevcNaluType {
|
||||||
};
|
};
|
||||||
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
||||||
#define SrsHevcNaluTypeParse(code) (SrsHevcNaluType)((code & 0x7E) >> 1)
|
#define SrsHevcNaluTypeParse(code) (SrsHevcNaluType)((code & 0x7E) >> 1)
|
||||||
|
#define SrsIsIRAP(type) ((type >= SrsHevcNaluType_CODED_SLICE_BLA) && (type <= SrsHevcNaluType_RESERVED_23))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Table 7-7 – Name association to slice_type
|
* @see Table 7-7 – Name association to slice_type
|
||||||
|
|
@ -1340,9 +1349,10 @@ public:
|
||||||
public:
|
public:
|
||||||
static srs_error_t parse_avc_nalu_type(const SrsSample* sample, SrsAvcNaluType& avc_nalu_type);
|
static srs_error_t parse_avc_nalu_type(const SrsSample* sample, SrsAvcNaluType& avc_nalu_type);
|
||||||
static srs_error_t parse_avc_bframe(const SrsSample* sample, bool& is_b_frame);
|
static srs_error_t parse_avc_bframe(const SrsSample* sample, bool& is_b_frame);
|
||||||
|
#ifdef SRS_H265
|
||||||
static srs_error_t parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type);
|
static srs_error_t parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type);
|
||||||
static srs_error_t parse_hevc_bframe(const SrsSample* sample, SrsFormat* format, bool& is_b_frame);
|
static srs_error_t parse_hevc_bframe(const SrsSample* sample, SrsFormat* format, bool& is_b_frame);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -750,18 +750,18 @@ ISrsRtpPayloader::~ISrsRtpPayloader()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ISrsRtspPacketDecodeHandler::ISrsRtspPacketDecodeHandler()
|
ISrsRtpPacketDecodeHandler::ISrsRtpPacketDecodeHandler()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ISrsRtspPacketDecodeHandler::~ISrsRtspPacketDecodeHandler()
|
ISrsRtpPacketDecodeHandler::~ISrsRtpPacketDecodeHandler()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsRtpPacket::SrsRtpPacket()
|
SrsRtpPacket::SrsRtpPacket()
|
||||||
{
|
{
|
||||||
payload_ = NULL;
|
payload_ = NULL;
|
||||||
payload_type_ = SrsRtspPacketPayloadTypeUnknown;
|
payload_type_ = SrsRtpPacketPayloadTypeUnknown;
|
||||||
shared_buffer_ = NULL;
|
shared_buffer_ = NULL;
|
||||||
actual_buffer_size_ = 0;
|
actual_buffer_size_ = 0;
|
||||||
|
|
||||||
|
|
@ -864,7 +864,7 @@ void SrsRtpPacket::add_padding(int size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SrsRtpPacket::set_decode_handler(ISrsRtspPacketDecodeHandler* h)
|
void SrsRtpPacket::set_decode_handler(ISrsRtpPacketDecodeHandler* h)
|
||||||
{
|
{
|
||||||
decode_handler = h;
|
decode_handler = h;
|
||||||
}
|
}
|
||||||
|
|
@ -936,7 +936,7 @@ srs_error_t SrsRtpPacket::decode(SrsBuffer* buf)
|
||||||
// By default, we always use the RAW payload.
|
// By default, we always use the RAW payload.
|
||||||
if (!payload_) {
|
if (!payload_) {
|
||||||
payload_ = new SrsRtpRawPayload();
|
payload_ = new SrsRtpRawPayload();
|
||||||
payload_type_ = SrsRtspPacketPayloadTypeRaw;
|
payload_type_ = SrsRtpPacketPayloadTypeRaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((err = payload_->decode(buf)) != srs_success) {
|
if ((err = payload_->decode(buf)) != srs_success) {
|
||||||
|
|
@ -946,7 +946,6 @@ srs_error_t SrsRtpPacket::decode(SrsBuffer* buf)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to check if H.264 RTP packet is a keyframe
|
|
||||||
bool srs_rtp_packet_h264_is_keyframe(uint8_t nalu_type, ISrsRtpPayloader* payload)
|
bool srs_rtp_packet_h264_is_keyframe(uint8_t nalu_type, ISrsRtpPayloader* payload)
|
||||||
{
|
{
|
||||||
if (nalu_type == kStapA) {
|
if (nalu_type == kStapA) {
|
||||||
|
|
@ -968,49 +967,45 @@ bool srs_rtp_packet_h264_is_keyframe(uint8_t nalu_type, ISrsRtpPayloader* payloa
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SRS_H265
|
|
||||||
// Helper function to check if H.265 RTP packet is a keyframe
|
|
||||||
bool srs_rtp_packet_h265_is_keyframe(uint8_t nalu_type, ISrsRtpPayloader* payload)
|
bool srs_rtp_packet_h265_is_keyframe(uint8_t nalu_type, ISrsRtpPayloader* payload)
|
||||||
{
|
{
|
||||||
if(nalu_type == kStapHevc) {
|
if(nalu_type == kStapHevc) {
|
||||||
SrsRtpSTAPPayloadHevc* stap_payload = dynamic_cast<SrsRtpSTAPPayloadHevc*>(payload);
|
SrsRtpSTAPPayloadHevc* stap_payload = dynamic_cast<SrsRtpSTAPPayloadHevc*>(payload);
|
||||||
if(NULL != stap_payload->get_vps() || NULL != stap_payload->get_sps() || NULL != stap_payload->get_pps()) {
|
if (stap_payload->get_vps() || stap_payload->get_sps() || stap_payload->get_pps()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if(nalu_type == kFuHevc) {
|
} else if (nalu_type == kFuHevc) {
|
||||||
SrsRtpFUAPayloadHevc2* fua_payload = dynamic_cast<SrsRtpFUAPayloadHevc2*>(payload);
|
SrsRtpFUAPayloadHevc2* fua_payload = dynamic_cast<SrsRtpFUAPayloadHevc2*>(payload);
|
||||||
if(fua_payload->nalu_type >= SrsHevcNaluType_CODED_SLICE_BLA && fua_payload->nalu_type <= SrsHevcNaluType_RESERVED_23) {
|
if(SrsIsIRAP(fua_payload->nalu_type)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if((SrsHevcNaluType_VPS == nalu_type) || (SrsHevcNaluType_SPS == nalu_type) || (SrsHevcNaluType_PPS == nalu_type)) {
|
if (SrsIsIRAP(nalu_type) || (SrsHevcNaluType_VPS == nalu_type) || (SrsHevcNaluType_SPS == nalu_type) || (SrsHevcNaluType_PPS == nalu_type)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
bool SrsRtpPacket::is_keyframe()
|
bool SrsRtpPacket::is_keyframe(SrsVideoCodecId codec_id)
|
||||||
{
|
{
|
||||||
// False if audio packet
|
// False if audio packet
|
||||||
if(SrsFrameTypeAudio == frame_type) {
|
if (SrsFrameTypeAudio == frame_type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check H.264 keyframe types
|
// For H264 video rtp packet
|
||||||
if (nalu_type == kStapA || nalu_type == kFuA ||
|
if (codec_id == SrsVideoCodecIdAVC) {
|
||||||
nalu_type == SrsAvcNaluTypeIDR || nalu_type == SrsAvcNaluTypeSPS || nalu_type == SrsAvcNaluTypePPS) {
|
|
||||||
return srs_rtp_packet_h264_is_keyframe(nalu_type, payload_);
|
return srs_rtp_packet_h264_is_keyframe(nalu_type, payload_);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SRS_H265
|
// For H265 video rtp packet
|
||||||
// Check H.265 keyframe types
|
if (codec_id == SrsVideoCodecIdHEVC) {
|
||||||
return srs_rtp_packet_h265_is_keyframe(nalu_type, payload_);
|
return srs_rtp_packet_h265_is_keyframe(nalu_type, payload_);
|
||||||
#else
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsRtpRawPayload::SrsRtpRawPayload()
|
SrsRtpRawPayload::SrsRtpRawPayload()
|
||||||
|
|
@ -1573,6 +1568,7 @@ ISrsRtpPayloader* SrsRtpFUAPayload2::copy()
|
||||||
return cp;
|
return cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SRS_H265
|
||||||
SrsRtpSTAPPayloadHevc::SrsRtpSTAPPayloadHevc()
|
SrsRtpSTAPPayloadHevc::SrsRtpSTAPPayloadHevc()
|
||||||
{
|
{
|
||||||
++_srs_pps_objs_rothers->sugar;
|
++_srs_pps_objs_rothers->sugar;
|
||||||
|
|
@ -1931,3 +1927,4 @@ ISrsRtpPayloader* SrsRtpFUAPayloadHevc2::copy()
|
||||||
|
|
||||||
return cp;
|
return cp;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -264,27 +264,27 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
// The payload type, for performance to avoid dynamic cast.
|
// The payload type, for performance to avoid dynamic cast.
|
||||||
enum SrsRtspPacketPayloadType
|
enum SrsRtpPacketPayloadType
|
||||||
{
|
{
|
||||||
SrsRtspPacketPayloadTypeRaw,
|
SrsRtpPacketPayloadTypeRaw,
|
||||||
SrsRtspPacketPayloadTypeFUA2,
|
SrsRtpPacketPayloadTypeFUA2,
|
||||||
SrsRtspPacketPayloadTypeFUAHevc2,
|
SrsRtpPacketPayloadTypeFUAHevc2,
|
||||||
SrsRtspPacketPayloadTypeFUA,
|
SrsRtpPacketPayloadTypeFUA,
|
||||||
SrsRtspPacketPayloadTypeFUAHevc,
|
SrsRtpPacketPayloadTypeFUAHevc,
|
||||||
SrsRtspPacketPayloadTypeNALU,
|
SrsRtpPacketPayloadTypeNALU,
|
||||||
SrsRtspPacketPayloadTypeSTAP,
|
SrsRtpPacketPayloadTypeSTAP,
|
||||||
SrsRtspPacketPayloadTypeSTAPHevc,
|
SrsRtpPacketPayloadTypeSTAPHevc,
|
||||||
SrsRtspPacketPayloadTypeUnknown,
|
SrsRtpPacketPayloadTypeUnknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
class ISrsRtspPacketDecodeHandler
|
class ISrsRtpPacketDecodeHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ISrsRtspPacketDecodeHandler();
|
ISrsRtpPacketDecodeHandler();
|
||||||
virtual ~ISrsRtspPacketDecodeHandler();
|
virtual ~ISrsRtpPacketDecodeHandler();
|
||||||
public:
|
public:
|
||||||
// We don't know the actual payload, so we depends on external handler.
|
// We don't know the actual payload, so we depends on external handler.
|
||||||
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtspPacketPayloadType* ppt) = 0;
|
virtual void on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload, SrsRtpPacketPayloadType* ppt) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The RTP packet with cached shared message.
|
// The RTP packet with cached shared message.
|
||||||
|
|
@ -295,7 +295,7 @@ public:
|
||||||
SrsRtpHeader header;
|
SrsRtpHeader header;
|
||||||
private:
|
private:
|
||||||
ISrsRtpPayloader* payload_;
|
ISrsRtpPayloader* payload_;
|
||||||
SrsRtspPacketPayloadType payload_type_;
|
SrsRtpPacketPayloadType payload_type_;
|
||||||
private:
|
private:
|
||||||
// The original shared message, all RTP packets can refer to its data.
|
// The original shared message, all RTP packets can refer to its data.
|
||||||
// Note that the size of shared msg, is not the packet size, it's a larger aligned buffer.
|
// Note that the size of shared msg, is not the packet size, it's a larger aligned buffer.
|
||||||
|
|
@ -315,7 +315,7 @@ private:
|
||||||
// The cached payload size for packet.
|
// The cached payload size for packet.
|
||||||
int cached_payload_size;
|
int cached_payload_size;
|
||||||
// The helper handler for decoder, use RAW payload if NULL.
|
// The helper handler for decoder, use RAW payload if NULL.
|
||||||
ISrsRtspPacketDecodeHandler* decode_handler;
|
ISrsRtpPacketDecodeHandler* decode_handler;
|
||||||
private:
|
private:
|
||||||
int64_t avsync_time_;
|
int64_t avsync_time_;
|
||||||
public:
|
public:
|
||||||
|
|
@ -334,14 +334,14 @@ public:
|
||||||
void enable_twcc_decode() { header.enable_twcc_decode(); } // SrsRtpPacket::enable_twcc_decode
|
void enable_twcc_decode() { header.enable_twcc_decode(); } // SrsRtpPacket::enable_twcc_decode
|
||||||
// Get and set the payload of packet.
|
// Get and set the payload of packet.
|
||||||
// @remark Note that return NULL if no payload.
|
// @remark Note that return NULL if no payload.
|
||||||
void set_payload(ISrsRtpPayloader* p, SrsRtspPacketPayloadType pt) { payload_ = p; payload_type_ = pt; }
|
void set_payload(ISrsRtpPayloader* p, SrsRtpPacketPayloadType pt) { payload_ = p; payload_type_ = pt; }
|
||||||
ISrsRtpPayloader* payload() { return payload_; }
|
ISrsRtpPayloader* payload() { return payload_; }
|
||||||
// Set the padding of RTP packet.
|
// Set the padding of RTP packet.
|
||||||
void set_padding(int size);
|
void set_padding(int size);
|
||||||
// Increase the padding of RTP packet.
|
// Increase the padding of RTP packet.
|
||||||
void add_padding(int size);
|
void add_padding(int size);
|
||||||
// Set the decode handler.
|
// Set the decode handler.
|
||||||
void set_decode_handler(ISrsRtspPacketDecodeHandler* h);
|
void set_decode_handler(ISrsRtpPacketDecodeHandler* h);
|
||||||
// Whether the packet is Audio packet.
|
// Whether the packet is Audio packet.
|
||||||
bool is_audio();
|
bool is_audio();
|
||||||
// Set RTP header extensions for encoding or decoding header extension
|
// Set RTP header extensions for encoding or decoding header extension
|
||||||
|
|
@ -352,7 +352,7 @@ public:
|
||||||
virtual srs_error_t encode(SrsBuffer* buf);
|
virtual srs_error_t encode(SrsBuffer* buf);
|
||||||
virtual srs_error_t decode(SrsBuffer* buf);
|
virtual srs_error_t decode(SrsBuffer* buf);
|
||||||
public:
|
public:
|
||||||
bool is_keyframe();
|
bool is_keyframe(SrsVideoCodecId codec_id);
|
||||||
// Get and set the packet sync time in milliseconds.
|
// Get and set the packet sync time in milliseconds.
|
||||||
void set_avsync_time(int64_t avsync_time) { avsync_time_ = avsync_time; }
|
void set_avsync_time(int64_t avsync_time) { avsync_time_ = avsync_time; }
|
||||||
int64_t get_avsync_time() const { return avsync_time_; }
|
int64_t get_avsync_time() const { return avsync_time_; }
|
||||||
|
|
@ -478,6 +478,7 @@ public:
|
||||||
virtual ISrsRtpPayloader* copy();
|
virtual ISrsRtpPayloader* copy();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef SRS_H265
|
||||||
class SrsRtpSTAPPayloadHevc : public ISrsRtpPayloader
|
class SrsRtpSTAPPayloadHevc : public ISrsRtpPayloader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -541,5 +542,6 @@ public:
|
||||||
virtual srs_error_t decode(SrsBuffer* buf);
|
virtual srs_error_t decode(SrsBuffer* buf);
|
||||||
virtual ISrsRtpPayloader* copy();
|
virtual ISrsRtpPayloader* copy();
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -3146,8 +3146,7 @@ srs_error_t SrsTsMessageCache::do_cache_hevc(SrsVideoFrame* frame)
|
||||||
|
|
||||||
// Insert aud before NALU for HEVC.
|
// Insert aud before NALU for HEVC.
|
||||||
SrsHevcNaluType nalu_type = (SrsHevcNaluType)SrsHevcNaluTypeParse(sample->bytes[0]);
|
SrsHevcNaluType nalu_type = (SrsHevcNaluType)SrsHevcNaluTypeParse(sample->bytes[0]);
|
||||||
bool is_idr = (SrsHevcNaluType_CODED_SLICE_BLA <= nalu_type) && (nalu_type <= SrsHevcNaluType_RESERVED_23);
|
if (SrsIsIRAP(nalu_type) && !frame->has_sps_pps && !is_sps_pps_appended) {
|
||||||
if (is_idr && !frame->has_sps_pps && !is_sps_pps_appended) {
|
|
||||||
for (size_t i = 0; i < codec->hevc_dec_conf_record_.nalu_vec.size(); i++) {
|
for (size_t i = 0; i < codec->hevc_dec_conf_record_.nalu_vec.size(); i++) {
|
||||||
const SrsHevcHvccNalu& nalu = codec->hevc_dec_conf_record_.nalu_vec[i];
|
const SrsHevcHvccNalu& nalu = codec->hevc_dec_conf_record_.nalu_vec[i];
|
||||||
if (nalu.num_nalus <= 0 || nalu.nal_data_vec.empty()) continue;
|
if (nalu.num_nalus <= 0 || nalu.nal_data_vec.empty()) continue;
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,39 @@ srs_error_t SrsRawHEVCStream::mux_avc2flv(std::string video, int8_t frame_type,
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srs_error_t SrsRawHEVCStream::mux_avc2flv_enhanced(std::string video, int8_t frame_type, int8_t packet_type, uint32_t dts, uint32_t pts, char ** flv, int * nb_flv)
|
||||||
|
{
|
||||||
|
srs_error_t err = srs_success;
|
||||||
|
|
||||||
|
// for h265 in RTMP video payload, there is 5bytes header:
|
||||||
|
// 1bytes, IsExHeader | FrameType | PacketType
|
||||||
|
// 4bytes, Video FourCC. AV1 = { 'a', 'v', '0', '1' }
|
||||||
|
// VP9 = { 'v', 'p', '0', '9' }
|
||||||
|
// HEVC = { 'h', 'v', 'c', '1' }
|
||||||
|
// @see: enhanced-rtmp-v1.pdf, page 9
|
||||||
|
int size = (int)video.length() + 5;
|
||||||
|
char *data = new char[size];
|
||||||
|
char *p = data;
|
||||||
|
|
||||||
|
// IsExHeader | FrameType | PacketType
|
||||||
|
*p++ = SRS_FLV_IS_EX_HEADER | (frame_type << 4) | packet_type;
|
||||||
|
|
||||||
|
// Video FourCC.
|
||||||
|
*p++ = 'h';
|
||||||
|
*p++ = 'v';
|
||||||
|
*p++ = 'c';
|
||||||
|
*p++ = '1';
|
||||||
|
|
||||||
|
// hevc raw data.
|
||||||
|
memcpy(p, video.data(), video.length());
|
||||||
|
|
||||||
|
*flv = data;
|
||||||
|
*nb_flv = size;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SrsRawAacStream::SrsRawAacStream()
|
SrsRawAacStream::SrsRawAacStream()
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,15 @@ public:
|
||||||
// @param flv output the muxed flv packet.
|
// @param flv output the muxed flv packet.
|
||||||
// @param nb_flv output the muxed flv size.
|
// @param nb_flv output the muxed flv size.
|
||||||
virtual srs_error_t mux_avc2flv(std::string video, int8_t frame_type, int8_t avc_packet_type, uint32_t dts, uint32_t pts, char **flv, int *nb_flv);
|
virtual srs_error_t mux_avc2flv(std::string video, int8_t frame_type, int8_t avc_packet_type, uint32_t dts, uint32_t pts, char **flv, int *nb_flv);
|
||||||
|
// Mux the hevc video packet to flv video packet, enhanced mode.
|
||||||
|
// @param packet_type, SrsVideoHEVCFrameTraitPacketTypeSequenceStart or SrsVideoHEVCFrameTraitPacketTypeCodedFrames.
|
||||||
|
// @param frame_type, SrsVideoAvcFrameTypeKeyFrame or SrsVideoAvcFrameTypeInterFrame.
|
||||||
|
// @param video the hevc raw data.
|
||||||
|
// @param flv output the muxed flv packet.
|
||||||
|
// @param nb_flv output the muxed flv size.
|
||||||
|
// TODO: Rename method to mux_hevc2flv_enhanced since AVC is an alias for H.264, not H.265/HEVC.
|
||||||
|
// This affects other modules like SRT and GB28181, so should be done in a separate refactoring.
|
||||||
|
virtual srs_error_t mux_avc2flv_enhanced(std::string video, int8_t frame_type, int8_t packet_type, uint32_t dts, uint32_t pts, char **flv, int *nb_flv);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
1517
trunk/src/utest/srs_utest_rtc2.cpp
Normal file
1517
trunk/src/utest/srs_utest_rtc2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
15
trunk/src/utest/srs_utest_rtc2.hpp
Normal file
15
trunk/src/utest/srs_utest_rtc2.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2013-2025 The SRS Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SRS_UTEST_RTC2_HPP
|
||||||
|
#define SRS_UTEST_RTC2_HPP
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <srs_utest_rtc2.hpp>
|
||||||
|
*/
|
||||||
|
#include <srs_utest.hpp>
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user