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:
Haibo Chen(陈海博) 2025-07-03 20:24:42 +08:00 committed by GitHub
parent d73ac3670a
commit cbc98dc0d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 2674 additions and 409 deletions

1
.gitignore vendored
View File

@ -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
View File

@ -52,3 +52,4 @@ bug
*.svg
/3rdparty/st-srs/srs
/3rdparty/st-srs/.circleci
/test_detail.xml

View File

@ -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
```
然后运行回归测试用例,如果只跑一次,可以直接运行:

View File

@ -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,8 +84,20 @@ func (v *videoIngester) AddTrack(pc *webrtc.PeerConnection, fps int) error {
v.fps = fps
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"
case "h264":
mimeType = "video/H264"
default:
// Fallback to file extension detection
if strings.HasSuffix(v.sourceVideo, ".ivf") {
mimeType = "video/VP8"
}
}
var err error

View File

@ -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
}()
}()
}

View File

@ -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.
@ -1064,7 +1096,8 @@ func newTestPublisher(init testWebRTCAPIInitFunc, options ...testPublisherOption
sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
v := &testPublisher{
pcc: &webrtc.Configuration{},
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",

4
trunk/configure vendored
View File

@ -464,8 +464,8 @@ if [[ $SRS_UTEST == YES ]]; then
MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_kernel" "srs_utest_core"
"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_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3"
"srs_utest_st" "srs_utest_rtc2")
if [[ $SRS_SRT == YES ]]; then
MODULE_FILES+=("srs_utest_srt")
fi

View File

@ -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)

View File

@ -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;
}

View File

@ -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());

View File

@ -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:

View File

@ -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");
}

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 42
#define VERSION_REVISION 43
#endif

View File

@ -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()
{

View File

@ -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
};
/**

View File

@ -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_);
}
// For H265 video rtp packet
if (codec_id == SrsVideoCodecIdHEVC) {
return srs_rtp_packet_h265_is_keyframe(nalu_type, payload_);
}
#ifdef SRS_H265
// Check H.265 keyframe types
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

View File

@ -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

View File

@ -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;

View File

@ -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()

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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