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/Testing/
|
||||
/trunk/ide/vscode-build
|
||||
/build/
|
||||
|
||||
|
|
|
|||
1
trunk/.gitignore
vendored
1
trunk/.gitignore
vendored
|
|
@ -52,3 +52,4 @@ bug
|
|||
*.svg
|
||||
/3rdparty/st-srs/srs
|
||||
/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
|
||||
git clone https://github.com/ossrs/srs.git &&
|
||||
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推拉流:
|
||||
|
||||
```bash
|
||||
./objs/srs -c conf/rtc.conf
|
||||
./objs/srs -c conf/regression-test-for-clion.conf
|
||||
```
|
||||
|
||||
然后运行回归测试用例,如果只跑一次,可以直接运行:
|
||||
|
|
|
|||
34
trunk/3rdparty/srs-bench/srs/ingester.go
vendored
34
trunk/3rdparty/srs-bench/srs/ingester.go
vendored
|
|
@ -40,6 +40,7 @@ import (
|
|||
|
||||
type videoIngester struct {
|
||||
sourceVideo string
|
||||
codecType string // "h264", "hevc", or "vp8"
|
||||
fps int
|
||||
markerInterceptor *rtpInterceptor
|
||||
sVideoTrack *webrtc.TrackLocalStaticSample
|
||||
|
|
@ -48,9 +49,26 @@ type videoIngester struct {
|
|||
readyCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newVideoIngester(sourceVideo string) *videoIngester {
|
||||
v := &videoIngester{markerInterceptor: &rtpInterceptor{}, sourceVideo: sourceVideo}
|
||||
// WithCodec sets the codec type for the video ingester
|
||||
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())
|
||||
|
||||
// Apply options
|
||||
for _, opt := range opts {
|
||||
opt(v)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
|
|
@ -66,9 +84,21 @@ func (v *videoIngester) AddTrack(pc *webrtc.PeerConnection, fps int) error {
|
|||
v.fps = fps
|
||||
|
||||
mimeType, trackID := "video/H264", "video"
|
||||
|
||||
// Determine MIME type based on codec type or file extension
|
||||
switch v.codecType {
|
||||
case "hevc", "h265":
|
||||
mimeType = "video/H265"
|
||||
case "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
|
||||
v.sVideoTrack, err = webrtc.NewTrackLocalStaticSample(
|
||||
|
|
|
|||
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
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
38
trunk/3rdparty/srs-bench/srs/util.go
vendored
38
trunk/3rdparty/srs-bench/srs/util.go
vendored
|
|
@ -674,6 +674,34 @@ func registerMiniCodecsWithoutNack(api *testWebRTCAPI) error {
|
|||
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) {
|
||||
v := &testWebRTCAPI{}
|
||||
|
||||
|
|
@ -1052,6 +1080,10 @@ type testPublisher struct {
|
|||
api *testWebRTCAPI
|
||||
// Optional suffix for stream url.
|
||||
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.
|
||||
cancel context.CancelFunc
|
||||
// The config for peer connection.
|
||||
|
|
@ -1065,6 +1097,7 @@ func newTestPublisher(init testWebRTCAPIInitFunc, options ...testPublisherOption
|
|||
|
||||
v := &testPublisher{
|
||||
pcc: &webrtc.Configuration{},
|
||||
videoCodec: "h264", // Default to H.264
|
||||
}
|
||||
|
||||
api, err := newTestWebRTCAPI(init)
|
||||
|
|
@ -1084,7 +1117,7 @@ func newTestPublisher(init testWebRTCAPIInitFunc, options ...testPublisherOption
|
|||
v.aIngester = newAudioIngester(sourceAudio)
|
||||
}
|
||||
if sourceVideo != "" {
|
||||
v.vIngester = newVideoIngester(sourceVideo)
|
||||
v.vIngester = newVideoIngester(sourceVideo, WithCodec(v.videoCodec))
|
||||
}
|
||||
|
||||
// Setup the interceptors for packets.
|
||||
|
|
@ -1148,6 +1181,9 @@ func (v *testPublisher) Run(ctx context.Context, cancel context.CancelFunc) erro
|
|||
if 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
|
||||
|
||||
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_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_st")
|
||||
"srs_utest_st" "srs_utest_rtc2")
|
||||
if [[ $SRS_SRT == YES ]]; then
|
||||
MODULE_FILES+=("srs_utest_srt")
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ The changelog for SRS.
|
|||
<a name="v7-changes"></a>
|
||||
|
||||
## 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 [#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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ITU-T-H.265-2021.pdf, page 462.
|
||||
SrsVideoAvcFrameType frame_type = SrsVideoAvcFrameTypeInterFrame;
|
||||
if (nt >= SrsHevcNaluType_CODED_SLICE_BLA && nt <= SrsHevcNaluType_RESERVED_23) {
|
||||
if (SrsIsIRAP(nt)) {
|
||||
frame_type = SrsVideoAvcFrameTypeKeyFrame;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1500,7 +1500,7 @@ srs_error_t SrsRtcPublishStream::check_send_nacks()
|
|||
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.
|
||||
if (buf->empty()) {
|
||||
|
|
@ -2725,7 +2725,7 @@ srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc
|
|||
// Only choose one match opus codec.
|
||||
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");
|
||||
if (payloads.empty()) {
|
||||
// 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);
|
||||
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");
|
||||
if (payloads.empty()) {
|
||||
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");
|
||||
|
||||
// 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
|
||||
SrsVideoPayload* video_payload = new SrsVideoPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_);
|
||||
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);
|
||||
track_descs = source->get_track_desc("audio", "opus");
|
||||
} else if (remote_media_desc.is_video()) {
|
||||
std::string prefer_codec = ruc->codec_;
|
||||
if (prefer_codec.empty()) {
|
||||
SrsVideoCodecId prefer_codec = srs_video_codec_str2id(ruc->codec_);
|
||||
if (prefer_codec == SrsVideoCodecIdReserved) {
|
||||
// Get the source codec if not specified.
|
||||
std::vector<SrsRtcTrackDescription*> track_descs = source->get_track_desc("video", "");
|
||||
if (!track_descs.empty()) {
|
||||
std::string codec_name = track_descs.at(0)->media_->name_;
|
||||
std::transform(codec_name.begin(), codec_name.end(), codec_name.begin(), ::tolower);
|
||||
if (codec_name == "h265") {
|
||||
prefer_codec = "hevc";
|
||||
} else {
|
||||
prefer_codec = codec_name;
|
||||
}
|
||||
SrsRtcTrackDescription* first_track = track_descs.at(0);
|
||||
prefer_codec = srs_video_codec_str2id(first_track->media_->name_);
|
||||
} else {
|
||||
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");
|
||||
if (payloads.empty()) {
|
||||
// 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
|
||||
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");
|
||||
if (payloads.empty()) {
|
||||
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_;
|
||||
|
||||
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());
|
||||
} else {
|
||||
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.
|
||||
class SrsRtcPublishStream : public ISrsRtspPacketDecodeHandler
|
||||
class SrsRtcPublishStream : public ISrsRtpPacketDecodeHandler
|
||||
, public ISrsRtcPublishStream, public ISrsRtcPLIWorkerHandler
|
||||
{
|
||||
private:
|
||||
|
|
@ -378,7 +378,7 @@ private:
|
|||
public:
|
||||
srs_error_t check_send_nacks();
|
||||
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:
|
||||
srs_error_t send_periodic_twcc();
|
||||
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
|
||||
h264_param.packetization_mode = kv[1];
|
||||
} 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()) {
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ struct H264SpecificParam
|
|||
{
|
||||
std::string profile_level_id;
|
||||
std::string packetization_mode;
|
||||
std::string level_asymmerty_allow;
|
||||
std::string level_asymmetry_allow;
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// Collect and build WebRTC RTP packets to AV frames.
|
||||
class SrsRtcFrameBuilder
|
||||
// Video packet cache for RTP packet management
|
||||
// TODO: Maybe should use SrsRtpRingBuffer?
|
||||
class SrsRtcFrameBuilderVideoPacketCache
|
||||
{
|
||||
private:
|
||||
ISrsStreamBridge* bridge_;
|
||||
private:
|
||||
bool is_first_audio_;
|
||||
SrsAudioTranscoder *codec_;
|
||||
private:
|
||||
const static uint16_t s_cache_size = 512;
|
||||
//TODO:use SrsRtpRingBuffer
|
||||
//TODO:jitter buffer class
|
||||
const static uint16_t cache_size_ = 512;
|
||||
struct RtcPacketCache {
|
||||
bool in_use;
|
||||
uint16_t sn;
|
||||
|
|
@ -331,22 +325,74 @@ private:
|
|||
uint32_t rtp_ts;
|
||||
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 lost_sn_;
|
||||
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:
|
||||
// The state for timestamp sync state. -1 for init. 0 not sync. 1 sync.
|
||||
int sync_state_;
|
||||
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_pps_;
|
||||
public:
|
||||
SrsRtcFrameBuilder(ISrsStreamBridge* bridge);
|
||||
virtual ~SrsRtcFrameBuilder();
|
||||
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 void on_unpublish();
|
||||
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_sequence_header_avc(SrsRtpPacket* pkt);
|
||||
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:
|
||||
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);
|
||||
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
|
||||
|
|
@ -383,11 +427,21 @@ public:
|
|||
int sample_;
|
||||
|
||||
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:
|
||||
SrsCodecPayload();
|
||||
SrsCodecPayload(uint8_t pt, std::string encode_name, int sample);
|
||||
virtual ~SrsCodecPayload();
|
||||
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 SrsMediaPayloadType generate_media_payload_type();
|
||||
};
|
||||
|
|
@ -593,25 +647,25 @@ protected:
|
|||
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:
|
||||
SrsRtcAudioRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc);
|
||||
virtual ~SrsRtcAudioRecvTrack();
|
||||
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:
|
||||
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt);
|
||||
virtual srs_error_t check_send_nacks();
|
||||
};
|
||||
|
||||
class SrsRtcVideoRecvTrack : public SrsRtcRecvTrack, public ISrsRtspPacketDecodeHandler
|
||||
class SrsRtcVideoRecvTrack : public SrsRtcRecvTrack, public ISrsRtpPacketDecodeHandler
|
||||
{
|
||||
public:
|
||||
SrsRtcVideoRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* stream_descs);
|
||||
virtual ~SrsRtcVideoRecvTrack();
|
||||
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:
|
||||
virtual srs_error_t on_rtp(SrsSharedPtr<SrsRtcSource>& source, SrsRtpPacket* pkt);
|
||||
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.
|
||||
frame_size += 4 + ipb_frames[i].second;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 42
|
||||
#define VERSION_REVISION 43
|
||||
|
||||
#endif
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
using namespace std;
|
||||
|
||||
#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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (v == 5512) return SrsAudioSampleRate5512;
|
||||
|
|
@ -667,7 +707,7 @@ srs_error_t SrsVideoFrame::add_sample(char* bytes, int size)
|
|||
if (c && c->id == SrsVideoCodecIdHEVC) {
|
||||
#ifdef SRS_H265
|
||||
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;
|
||||
#else
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef SRS_H265
|
||||
srs_error_t SrsVideoFrame::parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
|
@ -831,6 +872,7 @@ srs_error_t SrsVideoFrame::parse_hevc_bframe(const SrsSample* sample, SrsFormat
|
|||
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
SrsFormat::SrsFormat()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ class SrsBuffer;
|
|||
class SrsBitBuffer;
|
||||
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
|
||||
const int SrsAvcNaluHeaderSize = 1;
|
||||
// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
||||
|
|
@ -57,6 +63,7 @@ enum SrsVideoCodecId
|
|||
SrsVideoCodecIdAV1 = 13,
|
||||
};
|
||||
std::string srs_video_codec_id2str(SrsVideoCodecId codec);
|
||||
SrsVideoCodecId srs_video_codec_str2id(const std::string& codec);
|
||||
|
||||
/**
|
||||
* The video AVC frame trait(characteristic).
|
||||
|
|
@ -173,6 +180,7 @@ enum SrsAudioCodecId
|
|||
SrsAudioCodecIdReservedDeviceSpecificSound = 15,
|
||||
};
|
||||
std::string srs_audio_codec_id2str(SrsAudioCodecId codec);
|
||||
SrsAudioCodecId srs_audio_codec_str2id(const std::string& codec);
|
||||
|
||||
/**
|
||||
* The audio AAC frame trait(characteristic).
|
||||
|
|
@ -506,6 +514,7 @@ enum SrsHevcNaluType {
|
|||
};
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
||||
#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
|
||||
|
|
@ -1340,9 +1349,10 @@ public:
|
|||
public:
|
||||
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);
|
||||
|
||||
#ifdef SRS_H265
|
||||
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);
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -750,18 +750,18 @@ ISrsRtpPayloader::~ISrsRtpPayloader()
|
|||
{
|
||||
}
|
||||
|
||||
ISrsRtspPacketDecodeHandler::ISrsRtspPacketDecodeHandler()
|
||||
ISrsRtpPacketDecodeHandler::ISrsRtpPacketDecodeHandler()
|
||||
{
|
||||
}
|
||||
|
||||
ISrsRtspPacketDecodeHandler::~ISrsRtspPacketDecodeHandler()
|
||||
ISrsRtpPacketDecodeHandler::~ISrsRtpPacketDecodeHandler()
|
||||
{
|
||||
}
|
||||
|
||||
SrsRtpPacket::SrsRtpPacket()
|
||||
{
|
||||
payload_ = NULL;
|
||||
payload_type_ = SrsRtspPacketPayloadTypeUnknown;
|
||||
payload_type_ = SrsRtpPacketPayloadTypeUnknown;
|
||||
shared_buffer_ = NULL;
|
||||
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;
|
||||
}
|
||||
|
|
@ -936,7 +936,7 @@ srs_error_t SrsRtpPacket::decode(SrsBuffer* buf)
|
|||
// By default, we always use the RAW payload.
|
||||
if (!payload_) {
|
||||
payload_ = new SrsRtpRawPayload();
|
||||
payload_type_ = SrsRtspPacketPayloadTypeRaw;
|
||||
payload_type_ = SrsRtpPacketPayloadTypeRaw;
|
||||
}
|
||||
|
||||
if ((err = payload_->decode(buf)) != srs_success) {
|
||||
|
|
@ -946,7 +946,6 @@ srs_error_t SrsRtpPacket::decode(SrsBuffer* buf)
|
|||
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)
|
||||
{
|
||||
if (nalu_type == kStapA) {
|
||||
|
|
@ -968,49 +967,45 @@ bool srs_rtp_packet_h264_is_keyframe(uint8_t nalu_type, ISrsRtpPayloader* payloa
|
|||
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)
|
||||
{
|
||||
if(nalu_type == kStapHevc) {
|
||||
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;
|
||||
}
|
||||
} else if(nalu_type == kFuHevc) {
|
||||
} else if (nalu_type == kFuHevc) {
|
||||
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;
|
||||
}
|
||||
} 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 false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SrsRtpPacket::is_keyframe()
|
||||
bool SrsRtpPacket::is_keyframe(SrsVideoCodecId codec_id)
|
||||
{
|
||||
// False if audio packet
|
||||
if(SrsFrameTypeAudio == frame_type) {
|
||||
if (SrsFrameTypeAudio == frame_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check H.264 keyframe types
|
||||
if (nalu_type == kStapA || nalu_type == kFuA ||
|
||||
nalu_type == SrsAvcNaluTypeIDR || nalu_type == SrsAvcNaluTypeSPS || nalu_type == SrsAvcNaluTypePPS) {
|
||||
// For H264 video rtp packet
|
||||
if (codec_id == SrsVideoCodecIdAVC) {
|
||||
return srs_rtp_packet_h264_is_keyframe(nalu_type, payload_);
|
||||
}
|
||||
|
||||
#ifdef SRS_H265
|
||||
// Check H.265 keyframe types
|
||||
// For H265 video rtp packet
|
||||
if (codec_id == SrsVideoCodecIdHEVC) {
|
||||
return srs_rtp_packet_h265_is_keyframe(nalu_type, payload_);
|
||||
#else
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
SrsRtpRawPayload::SrsRtpRawPayload()
|
||||
|
|
@ -1573,6 +1568,7 @@ ISrsRtpPayloader* SrsRtpFUAPayload2::copy()
|
|||
return cp;
|
||||
}
|
||||
|
||||
#ifdef SRS_H265
|
||||
SrsRtpSTAPPayloadHevc::SrsRtpSTAPPayloadHevc()
|
||||
{
|
||||
++_srs_pps_objs_rothers->sugar;
|
||||
|
|
@ -1931,3 +1927,4 @@ ISrsRtpPayloader* SrsRtpFUAPayloadHevc2::copy()
|
|||
|
||||
return cp;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -264,27 +264,27 @@ public:
|
|||
};
|
||||
|
||||
// The payload type, for performance to avoid dynamic cast.
|
||||
enum SrsRtspPacketPayloadType
|
||||
enum SrsRtpPacketPayloadType
|
||||
{
|
||||
SrsRtspPacketPayloadTypeRaw,
|
||||
SrsRtspPacketPayloadTypeFUA2,
|
||||
SrsRtspPacketPayloadTypeFUAHevc2,
|
||||
SrsRtspPacketPayloadTypeFUA,
|
||||
SrsRtspPacketPayloadTypeFUAHevc,
|
||||
SrsRtspPacketPayloadTypeNALU,
|
||||
SrsRtspPacketPayloadTypeSTAP,
|
||||
SrsRtspPacketPayloadTypeSTAPHevc,
|
||||
SrsRtspPacketPayloadTypeUnknown,
|
||||
SrsRtpPacketPayloadTypeRaw,
|
||||
SrsRtpPacketPayloadTypeFUA2,
|
||||
SrsRtpPacketPayloadTypeFUAHevc2,
|
||||
SrsRtpPacketPayloadTypeFUA,
|
||||
SrsRtpPacketPayloadTypeFUAHevc,
|
||||
SrsRtpPacketPayloadTypeNALU,
|
||||
SrsRtpPacketPayloadTypeSTAP,
|
||||
SrsRtpPacketPayloadTypeSTAPHevc,
|
||||
SrsRtpPacketPayloadTypeUnknown,
|
||||
};
|
||||
|
||||
class ISrsRtspPacketDecodeHandler
|
||||
class ISrsRtpPacketDecodeHandler
|
||||
{
|
||||
public:
|
||||
ISrsRtspPacketDecodeHandler();
|
||||
virtual ~ISrsRtspPacketDecodeHandler();
|
||||
ISrsRtpPacketDecodeHandler();
|
||||
virtual ~ISrsRtpPacketDecodeHandler();
|
||||
public:
|
||||
// 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.
|
||||
|
|
@ -295,7 +295,7 @@ public:
|
|||
SrsRtpHeader header;
|
||||
private:
|
||||
ISrsRtpPayloader* payload_;
|
||||
SrsRtspPacketPayloadType payload_type_;
|
||||
SrsRtpPacketPayloadType payload_type_;
|
||||
private:
|
||||
// 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.
|
||||
|
|
@ -315,7 +315,7 @@ private:
|
|||
// The cached payload size for packet.
|
||||
int cached_payload_size;
|
||||
// The helper handler for decoder, use RAW payload if NULL.
|
||||
ISrsRtspPacketDecodeHandler* decode_handler;
|
||||
ISrsRtpPacketDecodeHandler* decode_handler;
|
||||
private:
|
||||
int64_t avsync_time_;
|
||||
public:
|
||||
|
|
@ -334,14 +334,14 @@ public:
|
|||
void enable_twcc_decode() { header.enable_twcc_decode(); } // SrsRtpPacket::enable_twcc_decode
|
||||
// Get and set the payload of packet.
|
||||
// @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_; }
|
||||
// Set the padding of RTP packet.
|
||||
void set_padding(int size);
|
||||
// Increase the padding of RTP packet.
|
||||
void add_padding(int size);
|
||||
// Set the decode handler.
|
||||
void set_decode_handler(ISrsRtspPacketDecodeHandler* h);
|
||||
void set_decode_handler(ISrsRtpPacketDecodeHandler* h);
|
||||
// Whether the packet is Audio packet.
|
||||
bool is_audio();
|
||||
// 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 decode(SrsBuffer* buf);
|
||||
public:
|
||||
bool is_keyframe();
|
||||
bool is_keyframe(SrsVideoCodecId codec_id);
|
||||
// Get and set the packet sync time in milliseconds.
|
||||
void set_avsync_time(int64_t avsync_time) { avsync_time_ = avsync_time; }
|
||||
int64_t get_avsync_time() const { return avsync_time_; }
|
||||
|
|
@ -478,6 +478,7 @@ public:
|
|||
virtual ISrsRtpPayloader* copy();
|
||||
};
|
||||
|
||||
#ifdef SRS_H265
|
||||
class SrsRtpSTAPPayloadHevc : public ISrsRtpPayloader
|
||||
{
|
||||
public:
|
||||
|
|
@ -541,5 +542,6 @@ public:
|
|||
virtual srs_error_t decode(SrsBuffer* buf);
|
||||
virtual ISrsRtpPayloader* copy();
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -3146,8 +3146,7 @@ srs_error_t SrsTsMessageCache::do_cache_hevc(SrsVideoFrame* frame)
|
|||
|
||||
// Insert aud before NALU for HEVC.
|
||||
SrsHevcNaluType nalu_type = (SrsHevcNaluType)SrsHevcNaluTypeParse(sample->bytes[0]);
|
||||
bool is_idr = (SrsHevcNaluType_CODED_SLICE_BLA <= nalu_type) && (nalu_type <= SrsHevcNaluType_RESERVED_23);
|
||||
if (is_idr && !frame->has_sps_pps && !is_sps_pps_appended) {
|
||||
if (SrsIsIRAP(nalu_type) && !frame->has_sps_pps && !is_sps_pps_appended) {
|
||||
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];
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
SrsRawAacStream::SrsRawAacStream()
|
||||
|
|
|
|||
|
|
@ -95,6 +95,15 @@ public:
|
|||
// @param flv output the muxed flv packet.
|
||||
// @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);
|
||||
// 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
|
||||
|
||||
|
|
|
|||
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