diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index d3c31a9f41..d165b6e63d 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-05-13, Merge [#4289](https://github.com/ossrs/srs/pull/4289): rtmp2rtc: Support HEVC. v7.0.33 (#4289) * v7.0, 2025-04-30, Merge [#4308](https://github.com/ossrs/srs/pull/4308): Fix memory leaks from errors skipping resource release. v7.0.32 (#4308) * v7.0, 2025-04-26, Merge [#4292](https://github.com/ossrs/srs/pull/4309): Support custom deleter for SrsUniquePtr. v7.0.31 (#4309) * v7.0, 2025-03-21, Merge [#4292](https://github.com/ossrs/srs/pull/4292): Typo: "forked" process in log output. v7.0.30 (#4292) diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 47022b9ae5..1a4f9f7f79 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -1068,7 +1068,7 @@ srs_error_t SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts) // Refresh the codec ASAP. if (muxer->latest_vcodec() != frame->vcodec()->id) { - srs_trace("HLS: Switch video codec %d(%s) to %d(%s)", muxer->latest_acodec(), srs_video_codec_id2str(muxer->latest_vcodec()).c_str(), + srs_trace("HLS: Switch video codec %d(%s) to %d(%s)", muxer->latest_vcodec(), srs_video_codec_id2str(muxer->latest_vcodec()).c_str(), frame->vcodec()->id, srs_video_codec_id2str(frame->vcodec()->id).c_str()); muxer->set_latest_vcodec(frame->vcodec()->id); } diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index af2fbcfdcc..410db41851 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -2598,6 +2598,51 @@ bool srs_sdp_has_h264_profile(const SrsSdp& sdp, const string& profile) return false; } +bool srs_sdp_has_h265_profile(const SrsMediaPayloadType& payload_type, const string& profile) +{ + srs_error_t err = srs_success; + + if (payload_type.format_specific_param_.empty()) { + return false; + } + + H265SpecificParam h265_param; + if ((err = srs_parse_h265_fmtp(payload_type.format_specific_param_, h265_param)) != srs_success) { + srs_error_reset(err); + return false; + } + + if (h265_param.profile_id == profile) { + return true; + } + + return false; +} + +bool srs_sdp_has_h265_profile(const SrsSdp& sdp, const string& profile) +{ + for (size_t i = 0; i < sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& desc = sdp.media_descs_[i]; + if (!desc.is_video()) { + continue; + } + + std::vector payloads = desc.find_media_with_encoding_name("H265"); + if (payloads.empty()) { + continue; + } + + for (std::vector::iterator it = payloads.begin(); it != payloads.end(); ++it) { + const SrsMediaPayloadType& payload_type = *it; + if (srs_sdp_has_h265_profile(payload_type, profile)) { + return true; + } + } + } + + return false; +} + srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc, SrsRtcSourceDescription* stream_desc) { srs_error_t err = srs_success; @@ -3041,8 +3086,6 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost); bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost); - // TODO: FIME: Should check packetization-mode=1 also. - bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f"); SrsSharedPtr source; if ((err = _srs_rtc_sources->fetch_or_create(req, source)) != srs_success) { @@ -3083,56 +3126,87 @@ 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() && ruc->codec_ == "av1") { - std::vector 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 - // @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166 - payloads = remote_media_desc.find_media_with_encoding_name("AV1X"); - } - if (payloads.empty()) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid AV1 payload type"); + } else if (remote_media_desc.is_video()) { + std::string prefer_codec = ruc->codec_; + if (prefer_codec.empty()) { + // Get the source codec if not specified. + std::vector 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; + } + } else { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no video track in source"); + } } - remote_payload = payloads.at(0); - track_descs = source->get_track_desc("video", "AV1"); - if (track_descs.empty()) { - // Be compatible with the Chrome M96, still check the AV1X encoding name - // @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166 - track_descs = source->get_track_desc("video", "AV1X"); - } - } else if (remote_media_desc.is_video() && ruc->codec_ == "hevc") { - std::vector 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"); - } + if (prefer_codec == "av1") { + std::vector 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 + // @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166 + payloads = remote_media_desc.find_media_with_encoding_name("AV1X"); + } + if (payloads.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid AV1 payload type"); + } - remote_payload = payloads.at(0); + remote_payload = payloads.at(0); + track_descs = source->get_track_desc("video", "AV1"); + if (track_descs.empty()) { + // Be compatible with the Chrome M96, still check the AV1X encoding name + // @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166 + track_descs = source->get_track_desc("video", "AV1X"); + } + } else if (prefer_codec == "hevc") { + std::vector 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"); + } + + // @see https://www.rfc-editor.org/rfc/rfc7798#section-7.2.1 + bool has_main_profile = srs_sdp_has_h265_profile(remote_sdp, "1"); + remote_payload = payloads.at(0); + + for (int j = 0; j < (int)payloads.size(); j++) { + const SrsMediaPayloadType& payload = payloads.at(j); + + // For H.265, we only check if profile-id=1 (Main Profile) + // Format example: level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST + if (!has_main_profile || srs_sdp_has_h265_profile(payload, "1")) { + remote_payload = payload; + break; + } + } - // TODO: FIXME: pick up a profile for HEVC. - // @see https://www.rfc-editor.org/rfc/rfc7798#section-7.2.1 + track_descs = source->get_track_desc("video", "H265"); + } else { + vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); + if (payloads.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type"); + } - track_descs = source->get_track_desc("video", "H265"); - } else if (remote_media_desc.is_video()) { - // TODO: check opus format specific param - vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); - if (payloads.empty()) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type"); - } + // TODO: FIME: Should check packetization-mode=1 also. + bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f"); - remote_payload = payloads.at(0); - for (int j = 0; j < (int)payloads.size(); j++) { - const SrsMediaPayloadType& payload = payloads.at(j); + remote_payload = payloads.at(0); + for (int j = 0; j < (int)payloads.size(); j++) { + const SrsMediaPayloadType& payload = payloads.at(j); - // If exists 42e01f profile, choose it; otherwise, use the first payload. - // TODO: FIME: Should check packetization-mode=1 also. - if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) { - remote_payload = payload; - break; + // If exists 42e01f profile, choose it; otherwise, use the first payload. + // TODO: FIME: Should check packetization-mode=1 also. + if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) { + remote_payload = payload; + break; + } } - } - track_descs = source->get_track_desc("video", "H264"); + track_descs = source->get_track_desc("video", "H264"); + } } for (int j = 0; j < (int)track_descs.size(); ++j) { @@ -3238,7 +3312,11 @@ void video_track_generate_play_offer(SrsRtcTrackDescription* track, string mid, SrsVideoPayload* payload = (SrsVideoPayload*)track->media_; - local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + if (payload->name_ == "H265") { + 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()); + } if (track->red_) { SrsRedPayload* red_payload = (SrsRedPayload*)track->red_; diff --git a/trunk/src/app/srs_app_rtc_sdp.cpp b/trunk/src/app/srs_app_rtc_sdp.cpp index e6f298a4ec..d87c5e0970 100644 --- a/trunk/src/app/srs_app_rtc_sdp.cpp +++ b/trunk/src/app/srs_app_rtc_sdp.cpp @@ -92,6 +92,42 @@ srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264 return err; } +srs_error_t srs_parse_h265_fmtp(const std::string& fmtp, H265SpecificParam& h265_param) +{ + srs_error_t err = srs_success; + + std::vector vec = srs_string_split(fmtp, ";"); + for (size_t i = 0; i < vec.size(); ++i) { + std::vector kv = srs_string_split(vec[i], "="); + if (kv.size() != 2) continue; + + if (kv[0] == "level-id") { + h265_param.level_id = kv[1]; + } else if (kv[0] == "profile-id") { + h265_param.profile_id = kv[1]; + } else if (kv[0] == "tier-flag") { + h265_param.tier_flag = kv[1]; + } else if (kv[0] == "tx-mode") { + h265_param.tx_mode = kv[1]; + } + } + + if (h265_param.level_id.empty()) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: level-id"); + } + if (h265_param.profile_id.empty()) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: profile-id"); + } + if (h265_param.tier_flag.empty()) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: tier-flag"); + } + if (h265_param.tx_mode.empty()) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: tx-mode"); + } + + return err; +} + SrsSessionInfo::SrsSessionInfo() { } diff --git a/trunk/src/app/srs_app_rtc_sdp.hpp b/trunk/src/app/srs_app_rtc_sdp.hpp index d2aba8a489..39dbb8f0d8 100644 --- a/trunk/src/app/srs_app_rtc_sdp.hpp +++ b/trunk/src/app/srs_app_rtc_sdp.hpp @@ -97,8 +97,16 @@ struct H264SpecificParam std::string level_asymmerty_allow; }; -extern srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param); +struct H265SpecificParam +{ + std::string level_id; + std::string profile_id; + std::string tier_flag; + std::string tx_mode; +}; +extern srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param); +extern srs_error_t srs_parse_h265_fmtp(const std::string& fmtp, H265SpecificParam& h265_param); class SrsMediaPayloadType { public: diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index 9292ccc1b9..1e5deef0da 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -785,7 +785,15 @@ std::vector SrsRtcSource::get_track_desc(std::string ty if (type == "video") { std::vector::iterator it = stream_desc_->video_track_descs_.begin(); while (it != stream_desc_->video_track_descs_.end() ){ - track_descs.push_back(*it); + if (media_name.empty()) { + track_descs.push_back(*it); + } else { + string name = (*it)->media_->name_; + std::transform(name.begin(), name.end(), name.begin(), static_cast(std::toupper)); + if (name == media_name) { + track_descs.push_back(*it); + } + } ++it; } } @@ -1074,11 +1082,16 @@ srs_error_t SrsRtcRtpBuilder::on_video(SrsSharedPtrMessage* msg) return err; } - // WebRTC does NOT support HEVC. -#ifdef SRS_H265 - if (format->vcodec->id == SrsVideoCodecIdHEVC) { + // support video codec: h264/h265 + SrsVideoCodecId vcodec = format->vcodec->id; + if (vcodec != SrsVideoCodecIdAVC && vcodec != SrsVideoCodecIdHEVC) { return err; } + +#ifdef SRS_H265 + if ((err = bridge_->update_codec(vcodec)) != srs_success) { + return srs_error_wrap(err, "update codec"); + } #endif bool has_idr = false; @@ -1161,10 +1174,16 @@ srs_error_t SrsRtcRtpBuilder::filter(SrsSharedPtrMessage* msg, SrsFormat* format // Because RTC does not support B-frame, so we will drop them. // TODO: Drop B-frame in better way, which not cause picture corruption. - if (!keep_bframe && format->vcodec->id == SrsVideoCodecIdAVC) { - bool is_b_frame; - if ((err = SrsVideoFrame::parse_avc_b_frame(sample, is_b_frame)) != srs_success) { - return srs_error_wrap(err, "parse bframe"); + if (!keep_bframe) { + bool is_b_frame = false; + if (format->vcodec->id == SrsVideoCodecIdAVC) { + if ((err = SrsVideoFrame::parse_avc_bframe(sample, is_b_frame)) != srs_success) { + return srs_error_wrap(err, "parse bframe"); + } + } else if (format->vcodec->id == SrsVideoCodecIdHEVC) { + if ((err = SrsVideoFrame::parse_hevc_bframe(sample, format, is_b_frame)) != srs_success) { + return srs_error_wrap(err, "parse bframe"); + } } if (is_b_frame) { continue; @@ -1186,53 +1205,60 @@ srs_error_t SrsRtcRtpBuilder::package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPac return err; } - // Note that the sps/pps may change, so we should copy it. - const vector& sps = format->vcodec->sequenceParameterSetNALUnit; - const vector& pps = format->vcodec->pictureParameterSetNALUnit; - if (sps.empty() || pps.empty()) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "sps/pps empty"); - } - pkt->header.set_payload_type(video_payload_type_); pkt->header.set_ssrc(video_ssrc_); pkt->frame_type = SrsFrameTypeVideo; - pkt->nalu_type = (SrsAvcNaluType)kStapA; pkt->header.set_marker(false); pkt->header.set_sequence(video_sequence++); pkt->header.set_timestamp(msg->timestamp * 90); - SrsRtpSTAPPayload* stap = new SrsRtpSTAPPayload(); - pkt->set_payload(stap, SrsRtspPacketPayloadTypeSTAP); - - uint8_t header = sps[0]; - stap->nri = (SrsAvcNaluType)header; + ISrsRtpPayloader* stap = NULL; + vector*> params; + int size = 0; + if (format->vcodec->id == SrsVideoCodecIdHEVC) { + for (size_t i = 0; i < format->vcodec->hevc_dec_conf_record_.nalu_vec.size(); i++) { + if (format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_unit_type == SrsHevcNaluType_VPS + || format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_unit_type == SrsHevcNaluType_SPS + || format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_unit_type == SrsHevcNaluType_PPS) { + vector& nalu = (vector&)format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_data_vec[0].nal_unit_data; + params.push_back(&nalu); + size += format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_data_vec[0].nal_unit_length; + } + } - // Copy the SPS/PPS bytes, because it may change. - int size = (int)(sps.size() + pps.size()); - char* payload = pkt->wrap(size); + stap = new SrsRtpSTAPPayloadHevc(); + pkt->set_payload(stap, SrsRtspPacketPayloadTypeSTAPHevc); + pkt->nalu_type = kStapHevc; + } else if (format->vcodec->id == SrsVideoCodecIdAVC) { + params.push_back(&format->vcodec->sequenceParameterSetNALUnit); + params.push_back(&format->vcodec->pictureParameterSetNALUnit); + size = format->vcodec->sequenceParameterSetNALUnit.size() + format->vcodec->pictureParameterSetNALUnit.size(); - if (true) { - SrsSample* sample = new SrsSample(); - sample->bytes = payload; - sample->size = (int)sps.size(); - stap->nalus.push_back(sample); + stap = new SrsRtpSTAPPayload(); + pkt->set_payload(stap, SrsRtspPacketPayloadTypeSTAP); + pkt->nalu_type = kStapA; + } - memcpy(payload, (char*)&sps[0], sps.size()); - payload += (int)sps.size(); + if (size == 0) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "vps/sps/pps empty"); } + char* payload = pkt->wrap(size); - if (true) { + for (vector*>::iterator it = params.begin(); it != params.end(); ++it) { + vector* param = *it; SrsSample* sample = new SrsSample(); sample->bytes = payload; - sample->size = (int)pps.size(); - stap->nalus.push_back(sample); + sample->size = param->size(); + if (format->vcodec->id == SrsVideoCodecIdHEVC) { + static_cast(stap)->nalus.push_back(sample); + } else { + static_cast(stap)->nalus.push_back(sample); + } - memcpy(payload, (char*)&pps[0], pps.size()); - payload += (int)pps.size(); + memcpy(payload, (char*)param->data(), param->size()); + payload += (int)param->size(); } - srs_info("RTC STAP-A seq=%u, sps %d, pps %d bytes", pkt->header.get_sequence(), sps.size(), pps.size()); - return err; } @@ -1240,8 +1266,14 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect { srs_error_t err = srs_success; + SrsFormat* format = meta->vsh_format(); + if (!format || !format->vcodec) { + return err; + } + bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC; + SrsRtpRawNALUs* raw_raw = new SrsRtpRawNALUs(); - SrsAvcNaluType first_nalu_type = SrsAvcNaluTypeReserved; + uint8_t first_nalu_type = 0; for (int i = 0; i < (int)samples.size(); i++) { SrsSample* sample = samples[i]; @@ -1250,8 +1282,8 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect continue; } - if (first_nalu_type == SrsAvcNaluTypeReserved) { - first_nalu_type = SrsAvcNaluType((uint8_t)(sample->bytes[0] & kNalTypeMask)); + if (first_nalu_type == 0) { + first_nalu_type = is_hevc ? uint8_t(SrsHevcNaluTypeParse(sample->bytes[0])) : uint8_t(SrsAvcNaluTypeParse(sample->bytes[0])); } raw_raw->push_back(sample->copy()); @@ -1272,7 +1304,7 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect pkt->header.set_payload_type(video_payload_type_); pkt->header.set_ssrc(video_ssrc_); pkt->frame_type = SrsFrameTypeVideo; - pkt->nalu_type = (SrsAvcNaluType)first_nalu_type; + pkt->nalu_type = first_nalu_type; pkt->header.set_sequence(video_sequence++); pkt->header.set_timestamp(msg->timestamp * 90); pkt->set_payload(raw_raw, SrsRtspPacketPayloadTypeNALU); @@ -1282,40 +1314,54 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect // because more than one RTP packet will refer to it. SrsUniquePtr raw(raw_raw); + int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize; + // Package NALUs in FU-A RTP packets. int fu_payload_size = kRtpMaxPayloadSize; // The first byte is store in FU-A header. - uint8_t header = raw->skip_first_byte(); - uint8_t nal_type = header & kNalTypeMask; - int nb_left = nn_bytes - 1; + uint8_t header = raw->skip_bytes(header_size); + + int nb_left = nn_bytes - header_size; int num_of_packet = 1 + (nn_bytes - 1) / fu_payload_size; for (int i = 0; i < num_of_packet; ++i) { int packet_size = srs_min(nb_left, fu_payload_size); - SrsRtpFUAPayload* fua = new SrsRtpFUAPayload(); - if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { - srs_freep(fua); - return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); - } - SrsRtpPacket* pkt = new SrsRtpPacket(); pkts.push_back(pkt); pkt->header.set_payload_type(video_payload_type_); pkt->header.set_ssrc(video_ssrc_); pkt->frame_type = SrsFrameTypeVideo; - pkt->nalu_type = (SrsAvcNaluType)kFuA; + pkt->nalu_type = kFuA; pkt->header.set_sequence(video_sequence++); pkt->header.set_timestamp(msg->timestamp * 90); - fua->nri = (SrsAvcNaluType)header; - fua->nalu_type = (SrsAvcNaluType)nal_type; - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); + if (is_hevc) { + SrsRtpFUAPayloadHevc* fua = new SrsRtpFUAPayloadHevc(); + if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { + srs_freep(fua); + return srs_error_wrap(err, "read hevc samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); + } + fua->nalu_type = SrsHevcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUAHevc); + } else { + SrsRtpFUAPayload* fua = new SrsRtpFUAPayload(); + if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { + srs_freep(fua); + return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); + } + fua->nalu_type = SrsAvcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA); + } - pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA); pkt->wrap(msg); nb_left -= packet_size; @@ -1354,11 +1400,19 @@ srs_error_t SrsRtcRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* { srs_error_t err = srs_success; - char* p = sample->bytes + 1; - int nb_left = sample->size - 1; - uint8_t header = sample->bytes[0]; - uint8_t nal_type = header & kNalTypeMask; + SrsFormat* format = meta->vsh_format(); + if (!format || !format->vcodec) { + return err; + } + + bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC; + int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize; + srs_assert(sample->size >= header_size); + char* p = sample->bytes + header_size; + int nb_left = sample->size - header_size; + uint8_t header = sample->bytes[0]; + int num_of_packet = 1 + (nb_left - 1) / fu_payload_size; for (int i = 0; i < num_of_packet; ++i) { int packet_size = srs_min(nb_left, fu_payload_size); @@ -1371,17 +1425,32 @@ srs_error_t SrsRtcRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* pkt->frame_type = SrsFrameTypeVideo; pkt->header.set_sequence(video_sequence++); pkt->header.set_timestamp(msg->timestamp * 90); + pkt->nalu_type = is_hevc ? kFuHevc : kFuA; + + if (is_hevc) { + // H265 FU-A header + SrsRtpFUAPayloadHevc2* fua = new SrsRtpFUAPayloadHevc2(); + pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUAHevc2); - SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2(); - pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA2); + fua->nalu_type = SrsHevcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + fua->payload = p; + fua->size = packet_size; + } else { + // H264 FU-A header + SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2(); + pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA2); - fua->nri = (SrsAvcNaluType)header; - fua->nalu_type = (SrsAvcNaluType)nal_type; - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); + fua->nri = (SrsAvcNaluType)header; + fua->nalu_type = SrsAvcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); - fua->payload = p; - fua->size = packet_size; + fua->payload = p; + fua->size = packet_size; + } pkt->wrap(msg); @@ -2065,6 +2134,7 @@ SrsVideoPayload* SrsVideoPayload::copy() cp->sample_ = sample_; cp->rtcp_fbs_ = rtcp_fbs_; cp->h264_param_ = h264_param_; + cp->h265_param_ = h265_param_; return cp; } @@ -2078,14 +2148,55 @@ SrsMediaPayloadType SrsVideoPayload::generate_media_payload_type() media_payload_type.rtcp_fb_ = rtcp_fbs_; std::ostringstream format_specific_param; + bool has_param = false; + if (!h264_param_.level_asymmerty_allow.empty()) { format_specific_param << "level-asymmetry-allowed=" << h264_param_.level_asymmerty_allow; + has_param = true; } if (!h264_param_.packetization_mode.empty()) { - format_specific_param << ";packetization-mode=" << h264_param_.packetization_mode; + if (has_param) format_specific_param << ";"; + format_specific_param << "packetization-mode=" << h264_param_.packetization_mode; + has_param = true; } if (!h264_param_.profile_level_id.empty()) { - format_specific_param << ";profile-level-id=" << h264_param_.profile_level_id; + if (has_param) format_specific_param << ";"; + format_specific_param << "profile-level-id=" << h264_param_.profile_level_id; + } + + media_payload_type.format_specific_param_ = format_specific_param.str(); + + return media_payload_type; +} + +SrsMediaPayloadType SrsVideoPayload::generate_media_payload_type_h265() +{ + SrsMediaPayloadType media_payload_type(pt_); + + media_payload_type.encoding_name_ = name_; + media_payload_type.clock_rate_ = sample_; + media_payload_type.rtcp_fb_ = rtcp_fbs_; + + std::ostringstream format_specific_param; + bool has_param = false; + + if (!h265_param_.level_id.empty()) { + format_specific_param << "level-id=" << h265_param_.level_id; + has_param = true; + } + if (!h265_param_.profile_id.empty()) { + if (has_param) format_specific_param << ";"; + format_specific_param << "profile-id=" << h265_param_.profile_id; + has_param = true; + } + if (!h265_param_.tier_flag.empty()) { + if (has_param) format_specific_param << ";"; + format_specific_param << "tier-flag=" << h265_param_.tier_flag; + has_param = true; + } + if (!h265_param_.tx_mode.empty()) { + if (has_param) format_specific_param << ";"; + format_specific_param << "tx-mode=" << h265_param_.tx_mode; } media_payload_type.format_specific_param_ = format_specific_param.str(); @@ -2130,6 +2241,31 @@ srs_error_t SrsVideoPayload::set_h264_param_desc(std::string fmtp) return err; } +// level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST +srs_error_t SrsVideoPayload::set_h265_param_desc(std::string fmtp) +{ + std::vector attributes = split_str(fmtp, ";"); + for (size_t i = 0; i < attributes.size(); ++i) { + std::string attribute = attributes.at(i); + std::vector kv = split_str(attribute, "="); + if (kv.size() != 2) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h265 param=%s", attribute.c_str()); + } + if (kv[0] == "level-id") { + h265_param_.level_id = kv[1]; + } else if (kv[0] == "profile-id") { + h265_param_.profile_id = kv[1]; + } else if (kv[0] == "tier-flag") { + h265_param_.tier_flag = kv[1]; + } else if (kv[0] == "tx-mode") { + h265_param_.tx_mode = kv[1]; + } else { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h265 param=%s", kv[0].c_str()); + } + } + return srs_success; +} + SrsAudioPayload::SrsAudioPayload() { channel_ = 0; @@ -2722,7 +2858,7 @@ void SrsRtcVideoRecvTrack::on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer } uint8_t v = (uint8_t)(buf->head()[0] & kNalTypeMask); - pkt->nalu_type = SrsAvcNaluType(v); + pkt->nalu_type = v; if (v == kStapA) { *ppayload = new SrsRtpSTAPPayload(); diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index 3efe3904d8..13e03efb0c 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -396,6 +396,7 @@ class SrsVideoPayload : public SrsCodecPayload { public: H264SpecificParam h264_param_; + H265SpecificParam h265_param_; public: SrsVideoPayload(); @@ -404,8 +405,10 @@ class SrsVideoPayload : public SrsCodecPayload public: virtual SrsVideoPayload* copy(); virtual SrsMediaPayloadType generate_media_payload_type(); + virtual SrsMediaPayloadType generate_media_payload_type_h265(); public: srs_error_t set_h264_param_desc(std::string fmtp); + srs_error_t set_h265_param_desc(std::string fmtp); }; // TODO: FIXME: Rename it. diff --git a/trunk/src/app/srs_app_stream_bridge.cpp b/trunk/src/app/srs_app_stream_bridge.cpp index de2915d924..80e0b999b2 100644 --- a/trunk/src/app/srs_app_stream_bridge.cpp +++ b/trunk/src/app/srs_app_stream_bridge.cpp @@ -85,7 +85,7 @@ SrsFrameToRtcBridge::SrsFrameToRtcBridge(SrsSharedPtr source) // video track ssrc if (true) { - std::vector descs = source->get_track_desc("video", "H264"); + std::vector descs = source->get_track_desc("video", ""); if (!descs.empty()) { video_ssrc = descs.at(0)->ssrc_; } @@ -95,6 +95,8 @@ SrsFrameToRtcBridge::SrsFrameToRtcBridge(SrsSharedPtr source) rtp_builder_ = new SrsRtcRtpBuilder(this, audio_ssrc, audio_payload_type, video_ssrc, video_payload_type); #endif + + video_codec_id_ = SrsVideoCodecIdReserved; } SrsFrameToRtcBridge::~SrsFrameToRtcBridge() @@ -155,6 +157,39 @@ srs_error_t SrsFrameToRtcBridge::on_rtp(SrsRtpPacket* pkt) { return source_->on_rtp(pkt); } + +srs_error_t SrsFrameToRtcBridge::update_codec(SrsVideoCodecId id) +{ + srs_error_t err = srs_success; + + if (video_codec_id_ == id) { + return err; + } + + std::vector video_track_descs = source_->get_track_desc("video", ""); + if (video_track_descs.empty()) { + return srs_error_new(ERROR_RTC_NO_TRACK, "no track found for conversion"); + } + + SrsRtcTrackDescription* video_track_desc = video_track_descs.at(0); + SrsVideoPayload* video_payload = (SrsVideoPayload*)video_track_desc->media_; + + if (id == SrsVideoCodecIdHEVC) { + video_payload->name_ = "H265"; + video_payload->set_h265_param_desc("level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST"); + } else { + video_payload->name_ = "H264"; + video_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f"); + } + + srs_trace("RTC: Switch video codec %d(%s) to %d(%s)", video_codec_id_, srs_video_codec_id2str(video_codec_id_).c_str(), + id, srs_video_codec_id2str(id).c_str()); + + video_codec_id_ = id; + + return err; +} + #endif SrsCompositeBridge::SrsCompositeBridge() diff --git a/trunk/src/app/srs_app_stream_bridge.hpp b/trunk/src/app/srs_app_stream_bridge.hpp index dfa4857c86..d5391c97cd 100644 --- a/trunk/src/app/srs_app_stream_bridge.hpp +++ b/trunk/src/app/srs_app_stream_bridge.hpp @@ -65,6 +65,8 @@ class SrsFrameToRtcBridge : public ISrsStreamBridge #if defined(SRS_FFMPEG_FIT) SrsRtcRtpBuilder* rtp_builder_; #endif +private: + SrsVideoCodecId video_codec_id_; public: SrsFrameToRtcBridge(SrsSharedPtr source); virtual ~SrsFrameToRtcBridge(); @@ -74,6 +76,7 @@ class SrsFrameToRtcBridge : public ISrsStreamBridge virtual void on_unpublish(); virtual srs_error_t on_frame(SrsSharedPtrMessage* frame); srs_error_t on_rtp(SrsRtpPacket* pkt); + srs_error_t update_codec(SrsVideoCodecId id); }; #endif diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index ae5fb1d33d..6e9507f888 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 32 +#define VERSION_REVISION 33 #endif \ No newline at end of file diff --git a/trunk/src/kernel/srs_kernel_codec.cpp b/trunk/src/kernel/srs_kernel_codec.cpp index ba02573824..f1a73ae326 100644 --- a/trunk/src/kernel/srs_kernel_codec.cpp +++ b/trunk/src/kernel/srs_kernel_codec.cpp @@ -676,7 +676,7 @@ srs_error_t SrsVideoFrame::add_sample(char* bytes, int size) // By default, use AVC(H.264) to parse NALU. // For video, parse the nalu type, set the IDR flag. - SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(bytes[0] & 0x1f); + SrsAvcNaluType nal_unit_type = SrsAvcNaluTypeParse(bytes[0]); if (nal_unit_type == SrsAvcNaluTypeIDR) { has_idr = true; @@ -703,29 +703,25 @@ srs_error_t SrsVideoFrame::parse_avc_nalu_type(const SrsSample* sample, SrsAvcNa srs_error_t err = srs_success; if (sample == NULL || sample->size < 1) { - return srs_error_new(ERROR_AVC_NALU_EMPTY, "empty nalu"); + return srs_error_new(ERROR_NALU_EMPTY, "empty nalu"); } uint8_t header = sample->bytes[0]; - avc_nalu_type = (SrsAvcNaluType)(header & kNalTypeMask); + avc_nalu_type = SrsAvcNaluTypeParse(header); return err; } -srs_error_t SrsVideoFrame::parse_avc_b_frame(const SrsSample* sample, bool& is_b_frame) +srs_error_t SrsVideoFrame::parse_avc_bframe(const SrsSample* sample, bool& is_b_frame) { srs_error_t err = srs_success; - if (sample == NULL || sample->size < 1) { - return srs_error_new(ERROR_AVC_NALU_EMPTY, "empty nalu"); - } - SrsAvcNaluType nalu_type; if ((err = parse_avc_nalu_type(sample, nalu_type)) != srs_success) { return srs_error_wrap(err, "parse avc nalu type error"); } - if (nalu_type != SrsAvcNaluTypeNonIDR && nalu_type != SrsAvcNaluTypeDataPartitionA && nalu_type != SrsAvcNaluTypeIDR) { + if (nalu_type == SrsAvcNaluTypeIDR || nalu_type == SrsAvcNaluTypeSPS || nalu_type == SrsAvcNaluTypePPS) { is_b_frame = false; return err; } @@ -755,6 +751,87 @@ srs_error_t SrsVideoFrame::parse_avc_b_frame(const SrsSample* sample, bool& is_b return err; } +srs_error_t SrsVideoFrame::parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type) +{ + srs_error_t err = srs_success; + + if (sample == NULL || sample->size < 1) { + return srs_error_new(ERROR_NALU_EMPTY, "empty hevc nalu"); + } + + uint8_t header = sample->bytes[0]; + hevc_nalu_type = SrsHevcNaluTypeParse(header); + + return err; +} + +srs_error_t SrsVideoFrame::parse_hevc_bframe(const SrsSample* sample, SrsFormat *format, bool& is_b_frame) +{ + srs_error_t err = srs_success; + + SrsHevcNaluType nalu_type; + if ((err = parse_hevc_nalu_type(sample, nalu_type)) != srs_success) { + return srs_error_wrap(err, "parse hevc nalu type error"); + } + + if (nalu_type > SrsHevcNaluType_CODED_SLICE_TFD) { + is_b_frame = false; + return err; + } + + SrsUniquePtr stream(new SrsBuffer(sample->bytes, sample->size)); + stream->skip(2); + + // @see 7.3.6.1 General slice segment header syntax + // @doc ITU-T-H.265-2021.pdf, page 66. + SrsBitBuffer bs(stream.get()); + + uint8_t first_slice_segment_in_pic_flag = bs.read_bit(); + + uint32_t slice_pic_parameter_set_id; + if ((err = bs.read_bits_ue(slice_pic_parameter_set_id)) != srs_success) { + return srs_error_wrap(err, "read slice pic parameter set id"); + } + + if (slice_pic_parameter_set_id >= SrsHevcMax_PPS_COUNT) { + return srs_error_new(ERROR_HEVC_DECODE_ERROR, "slice pic parameter set id out of range: %d", slice_pic_parameter_set_id); + } + + SrsHevcRbspPps *pps = &(format->vcodec->hevc_dec_conf_record_.pps_table[slice_pic_parameter_set_id]); + if (!pps) { + return srs_error_new(ERROR_HEVC_DECODE_ERROR, "pps not found"); + } + + uint8_t dependent_slice_segment_flag = 0; + if (!first_slice_segment_in_pic_flag) { + if (pps->dependent_slice_segments_enabled_flag) { + dependent_slice_segment_flag = bs.read_bit(); + } + } + + if (dependent_slice_segment_flag) { + return srs_error_new(ERROR_HEVC_DECODE_ERROR, "dependent slice segment flag is not supported"); + } + + for (int i = 0; i < pps->num_extra_slice_header_bits; i++) { + bs.skip_bits(1); + } + + uint32_t slice_type; + if ((err = bs.read_bits_ue(slice_type)) != srs_success) { + return srs_error_wrap(err, "read slice type"); + } + + is_b_frame = slice_type == SrsHevcSliceTypeB; + if (is_b_frame) { + srs_verbose("nalu_type=%d, slice type=%d", nalu_type, slice_type); + } + + // no need to evaluate the rest + + return err; +} + SrsFormat::SrsFormat() { acodec = NULL; @@ -2263,7 +2340,7 @@ srs_error_t SrsFormat::avc_demux_sps() // 7.4.1 NAL unit semantics // ISO_IEC_14496-10-AVC-2012.pdf, page 61. // nal_unit_type specifies the type of RBSP data structure contained in the NAL unit as specified in Table 7-1. - SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(nutv & 0x1f); + SrsAvcNaluType nal_unit_type = SrsAvcNaluTypeParse(nutv); if (nal_unit_type != 7) { return srs_error_new(ERROR_HLS_DECODE_ERROR, "for sps, nal_unit_type shall be equal to 7"); } diff --git a/trunk/src/kernel/srs_kernel_codec.hpp b/trunk/src/kernel/srs_kernel_codec.hpp index 9899271b6e..f267fdc017 100644 --- a/trunk/src/kernel/srs_kernel_codec.hpp +++ b/trunk/src/kernel/srs_kernel_codec.hpp @@ -14,6 +14,12 @@ class SrsBuffer; class SrsBitBuffer; +class SrsFormat; + +// @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 +const int SrsHevcNaluHeaderSize = 2; /** * The video codec id. @@ -421,6 +427,8 @@ enum SrsAvcNaluType // Coded slice extension slice_layer_extension_rbsp( ) SrsAvcNaluTypeCodedSliceExt = 20, }; +// @see https://datatracker.ietf.org/doc/html/rfc6184#section-1.3 +#define SrsAvcNaluTypeParse(code) (SrsAvcNaluType)(code & 0x1F) std::string srs_avc_nalu2str(SrsAvcNaluType nalu_type); #ifdef SRS_H265 @@ -496,8 +504,19 @@ enum SrsHevcNaluType { SrsHevcNaluType_UNSPECIFIED_63, SrsHevcNaluType_INVALID, }; +// @see https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4 #define SrsHevcNaluTypeParse(code) (SrsHevcNaluType)((code & 0x7E) >> 1) +/** + * @see Table 7-7 – Name association to slice_type + * @doc ITU-T-H.265-2021.pdf, page 116. + */ +enum SrsHevcSliceType { + SrsHevcSliceTypeB = 0, + SrsHevcSliceTypeP = 1, + SrsHevcSliceTypeI = 2, +}; + struct SrsHevcNalData { uint16_t nal_unit_length; std::vector nal_unit_data; @@ -1320,7 +1339,10 @@ class SrsVideoFrame : public SrsFrame virtual SrsVideoCodecConfig* vcodec(); public: static srs_error_t parse_avc_nalu_type(const SrsSample* sample, SrsAvcNaluType& avc_nalu_type); - static srs_error_t parse_avc_b_frame(const SrsSample* sample, bool& is_b_frame); + static srs_error_t parse_avc_bframe(const SrsSample* sample, bool& is_b_frame); + + 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); }; /** diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 69a14a5907..afc7f904f1 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -279,7 +279,7 @@ XX(ERROR_HEVC_DECODE_ERROR , 3099, "HevcDecode", "HEVC decode av stream failed") \ XX(ERROR_MP4_HVCC_CHANGE , 3100, "Mp4HvcCChange", "MP4 does not support video HvcC change") \ XX(ERROR_HEVC_API_NO_PREFIXED , 3101, "HevcAnnexbPrefix", "No annexb prefix for HEVC decoder") \ - XX(ERROR_AVC_NALU_EMPTY , 3102, "AvcNaluEmpty", "AVC NALU is empty") + XX(ERROR_NALU_EMPTY , 3102, "NaluEmpty", "NALU is empty") /**************************************************/ /* HTTP/StreamConverter protocol error. */ diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp index 7b08532d9b..1b1d91b15f 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp @@ -758,7 +758,7 @@ SrsRtpPacket::SrsRtpPacket() shared_buffer_ = NULL; actual_buffer_size_ = 0; - nalu_type = SrsAvcNaluTypeReserved; + nalu_type = 0; frame_type = SrsFrameTypeReserved; cached_payload_size = 0; decode_handler = NULL; @@ -961,6 +961,23 @@ bool SrsRtpPacket::is_keyframe() if((SrsAvcNaluTypeIDR == nalu_type) || (SrsAvcNaluTypeSPS == nalu_type) || (SrsAvcNaluTypePPS == nalu_type)) { return true; } +#ifdef SRS_H265 + if(nalu_type == kStapHevc) { + SrsRtpSTAPPayloadHevc* stap_payload = dynamic_cast(payload_); + if(NULL != stap_payload->get_vps() || NULL != stap_payload->get_sps() || NULL != stap_payload->get_pps()) { + return true; + } + } else if(nalu_type == kFuHevc) { + SrsRtpFUAPayloadHevc2* fua_payload = dynamic_cast(payload_); + if(fua_payload->nalu_type >= SrsHevcNaluType_CODED_SLICE_BLA && fua_payload->nalu_type <= SrsHevcNaluType_RESERVED_23) { + return true; + } + } else { + if((SrsHevcNaluType_VPS == nalu_type) || (SrsHevcNaluType_SPS == nalu_type) || (SrsHevcNaluType_PPS == nalu_type)) { + return true; + } + } +#endif } return false; @@ -1064,10 +1081,10 @@ void SrsRtpRawNALUs::push_back(SrsSample* sample) nalus.push_back(sample); } -uint8_t SrsRtpRawNALUs::skip_first_byte() +uint8_t SrsRtpRawNALUs::skip_bytes(int count) { - srs_assert (cursor >= 0 && nn_bytes > 0 && cursor < nn_bytes); - cursor++; + srs_assert (cursor >= 0 && nn_bytes > 0 && cursor + count < nn_bytes); + cursor += count; return uint8_t(nalus[0]->bytes[0]); } @@ -1194,7 +1211,7 @@ SrsSample* SrsRtpSTAPPayload::get_sps() continue; } - SrsAvcNaluType nalu_type = (SrsAvcNaluType)(p->bytes[0] & kNalTypeMask); + SrsAvcNaluType nalu_type = SrsAvcNaluTypeParse(p->bytes[0]); if (nalu_type == SrsAvcNaluTypeSPS) { return p; } @@ -1212,7 +1229,7 @@ SrsSample* SrsRtpSTAPPayload::get_pps() continue; } - SrsAvcNaluType nalu_type = (SrsAvcNaluType)(p->bytes[0] & kNalTypeMask); + SrsAvcNaluType nalu_type = SrsAvcNaluTypeParse(p->bytes[0]); if (nalu_type == SrsAvcNaluTypePPS) { return p; } @@ -1398,7 +1415,7 @@ srs_error_t SrsRtpFUAPayload::decode(SrsBuffer* buf) v = buf->read_1bytes(); start = v & kStart; end = v & kEnd; - nalu_type = SrsAvcNaluType(v & kNalTypeMask); + nalu_type = SrsAvcNaluTypeParse(v); if (!buf->require(1)) { return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); @@ -1499,7 +1516,7 @@ srs_error_t SrsRtpFUAPayload2::decode(SrsBuffer* buf) v = buf->read_1bytes(); start = v & kStart; end = v & kEnd; - nalu_type = SrsAvcNaluType(v & kNalTypeMask); + nalu_type = SrsAvcNaluTypeParse(v); if (!buf->require(1)) { return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); @@ -1525,3 +1542,362 @@ ISrsRtpPayloader* SrsRtpFUAPayload2::copy() return cp; } + +SrsRtpSTAPPayloadHevc::SrsRtpSTAPPayloadHevc() +{ + ++_srs_pps_objs_rothers->sugar; +} + +SrsRtpSTAPPayloadHevc::~SrsRtpSTAPPayloadHevc() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + srs_freep(p); + } +} + +SrsSample* SrsRtpSTAPPayloadHevc::get_vps() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + if (!p || !p->size) { + continue; + } + + SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(p->bytes[0]); + if (nalu_type == SrsHevcNaluType_VPS) { + return p; + } + } + + return NULL; +} + +SrsSample* SrsRtpSTAPPayloadHevc::get_sps() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + if (!p || !p->size) { + continue; + } + + SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(p->bytes[0]); + if (nalu_type == SrsHevcNaluType_SPS) { + return p; + } + } + + return NULL; +} + +SrsSample* SrsRtpSTAPPayloadHevc::get_pps() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + if (!p || !p->size) { + continue; + } + + SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(p->bytes[0]); + if (nalu_type == SrsHevcNaluType_PPS) { + return p; + } + } + + return NULL; +} + +uint64_t SrsRtpSTAPPayloadHevc::nb_bytes() +{ + int size = 2; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + size += 2 + p->size; + } + + return size; +} + +srs_error_t SrsRtpSTAPPayloadHevc::encode(SrsBuffer* buf) +{ + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + + // STAP header, RTP payload format for aggregation packets + // @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 + buf->write_1bytes(kStapHevc << 1); + buf->write_1bytes(1); + + // NALUs. + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + + if (!buf->require(2 + p->size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2 + p->size); + } + + buf->write_2bytes(p->size); + buf->write_bytes(p->bytes, p->size); + } + + return srs_success; +} + +srs_error_t SrsRtpSTAPPayloadHevc::decode(SrsBuffer* buf) +{ + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + + // STAP header, RTP payload format for aggregation packets + // @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 + uint8_t v = buf->read_1bytes(); + buf->skip(1); + + // forbidden_zero_bit shoul be zero. + // @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 + uint8_t f = (v & 0x80); + if (f == 0x80) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "forbidden_zero_bit should be zero"); + } + + // NALUs. + while (!buf->empty()) { + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + + int size = buf->read_2bytes(); + if (!buf->require(size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", size); + } + + SrsSample* sample = new SrsSample(); + sample->bytes = buf->head(); + sample->size = size; + buf->skip(size); + + nalus.push_back(sample); + } + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpSTAPPayloadHevc::copy() +{ + SrsRtpSTAPPayloadHevc* cp = new SrsRtpSTAPPayloadHevc(); + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + cp->nalus.push_back(p->copy()); + } + + return cp; +} + +SrsRtpFUAPayloadHevc::SrsRtpFUAPayloadHevc() +{ + start = end = false; + nalu_type = (SrsHevcNaluType)0; + + ++_srs_pps_objs_rothers->sugar; +} + +SrsRtpFUAPayloadHevc::~SrsRtpFUAPayloadHevc() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + srs_freep(p); + } +} + +uint64_t SrsRtpFUAPayloadHevc::nb_bytes() +{ + int size = 3; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + size += p->size; + } + + return size; +} + +srs_error_t SrsRtpFUAPayloadHevc::encode(SrsBuffer* buf) +{ + if (!buf->require(3)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3); + } + + // PayloadHdr, @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 + buf->write_1bytes(kFuHevc << 1); + buf->write_1bytes(1); + + // FU header, @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 + uint8_t fu_header = (start ? kStart : 0) | (end ? kEnd : 0); + fu_header |= nalu_type; + buf->write_1bytes(fu_header); + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + + if (!buf->require(p->size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", p->size); + } + + buf->write_bytes(p->bytes, p->size); + } + + return srs_success; +} + +srs_error_t SrsRtpFUAPayloadHevc::decode(SrsBuffer* buf) +{ + if (!buf->require(3)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3); + } + + // skip PayloadHdr, 2 bytes + buf->skip(2); + + uint8_t fu_header = buf->read_1bytes(); + start = fu_header & kStart; + end = fu_header & kEnd; + nalu_type = SrsHevcNaluType(fu_header & 0x3F); + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + SrsSample* sample = new SrsSample(); + sample->bytes = buf->head(); + sample->size = buf->left(); + buf->skip(sample->size); + + nalus.push_back(sample); + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpFUAPayloadHevc::copy() +{ + SrsRtpFUAPayloadHevc* cp = new SrsRtpFUAPayloadHevc(); + + cp->start = start; + cp->end = end; + cp->nalu_type = nalu_type; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + cp->nalus.push_back(p->copy()); + } + + return cp; +} + +SrsRtpFUAPayloadHevc2::SrsRtpFUAPayloadHevc2() +{ + start = end = false; + nalu_type = (SrsHevcNaluType)0; + + payload = NULL; + size = 0; + + ++_srs_pps_objs_rfua->sugar; +} + +SrsRtpFUAPayloadHevc2::~SrsRtpFUAPayloadHevc2() +{ +} + +uint64_t SrsRtpFUAPayloadHevc2::nb_bytes() +{ + // PayloadHdr(2) + FU header(1) + return 3 + size; +} + +srs_error_t SrsRtpFUAPayloadHevc2::encode(SrsBuffer* buf) +{ + if (!buf->require(3 + size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3 + size); + } + + // Fast encoding. + char* p = buf->head(); + + // PayloadHdr, @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 + /* + * create the HEVC payload header and transmit the buffer as fragmentation units (FU) + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |F| Type | LayerId | TID | + * +-------------+-----------------+ + * + * F = 0 + * Type = 49 (fragmentation unit (FU)) + * LayerId = 0 + * TID = 1 + */ + *p++ = kFuHevc << 1; + *p++ = 1; + + // FU header, @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 + uint8_t fu_header = (start ? kStart : 0) | (end ? kEnd : 0); + fu_header |= nalu_type; + *p++ = fu_header; + + memcpy(p, payload, size); + + // Consume bytes. + buf->skip(3 + size); + + return srs_success; +} + +srs_error_t SrsRtpFUAPayloadHevc2::decode(SrsBuffer* buf) +{ + if (!buf->require(3)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires 3 bytes"); + } + + // skip PayloadHdr, 2 bytes + buf->skip(2); + + uint8_t fu_header = buf->read_1bytes(); + start = fu_header & kStart; + end = fu_header & kEnd; + nalu_type = SrsHevcNaluType(fu_header & 0x3F); + + payload = buf->head(); + size = buf->left(); + buf->skip(size); + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpFUAPayloadHevc2::copy() +{ + SrsRtpFUAPayloadHevc2* cp = new SrsRtpFUAPayloadHevc2(); + + cp->start = start; + cp->end = end; + cp->nalu_type = nalu_type; + cp->payload = payload; + cp->size = size; + + return cp; +} diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp index b417208fbc..ff731db0b2 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp @@ -29,10 +29,14 @@ const uint8_t kNalTypeMask = 0x1F; // @see: https://tools.ietf.org/html/rfc6184#section-5.2 const uint8_t kStapA = 24; - // @see: https://tools.ietf.org/html/rfc6184#section-5.2 const uint8_t kFuA = 28; +// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 +const uint8_t kStapHevc = 48; +// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 +const uint8_t kFuHevc = 49; + // @see: https://tools.ietf.org/html/rfc6184#section-5.8 const uint8_t kStart = 0x80; // Fu-header start bit const uint8_t kEnd = 0x40; // Fu-header end bit @@ -253,9 +257,12 @@ enum SrsRtspPacketPayloadType { SrsRtspPacketPayloadTypeRaw, SrsRtspPacketPayloadTypeFUA2, + SrsRtspPacketPayloadTypeFUAHevc2, SrsRtspPacketPayloadTypeFUA, + SrsRtspPacketPayloadTypeFUAHevc, SrsRtspPacketPayloadTypeNALU, SrsRtspPacketPayloadTypeSTAP, + SrsRtspPacketPayloadTypeSTAPHevc, SrsRtspPacketPayloadTypeUnknown, }; @@ -289,7 +296,7 @@ class SrsRtpPacket // Helper fields. public: // The first byte as nalu type, for video decoder only. - SrsAvcNaluType nalu_type; + uint8_t nalu_type; // The frame type, for RTMP bridge or SFU source. SrsFrameType frame_type; // Fast cache for performance. @@ -376,7 +383,7 @@ class SrsRtpRawNALUs : public ISrsRtpPayloader public: void push_back(SrsSample* sample); public: - uint8_t skip_first_byte(); + uint8_t skip_bytes(int count); // We will manage the returned samples, if user want to manage it, please copy it. srs_error_t read_samples(std::vector& samples, int packet_size); // interface ISrsRtpPayloader @@ -460,4 +467,68 @@ class SrsRtpFUAPayload2 : public ISrsRtpPayloader virtual ISrsRtpPayloader* copy(); }; +class SrsRtpSTAPPayloadHevc : public ISrsRtpPayloader +{ +public: + // The NALU samples, we will manage the samples. + // @remark We only refer to the memory, user must free its bytes. + std::vector nalus; +public: + SrsRtpSTAPPayloadHevc(); + virtual ~SrsRtpSTAPPayloadHevc(); +public: + SrsSample* get_vps(); + SrsSample* get_sps(); + SrsSample* get_pps(); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +// FU, for one NALU with multiple fragments. +// With more than one payload for HEVC. +class SrsRtpFUAPayloadHevc : public ISrsRtpPayloader +{ +public: + // The FUA header. + bool start; + bool end; + SrsHevcNaluType nalu_type; + // The NALU samples, we manage the samples. + // @remark We only refer to the memory, user must free its bytes. + std::vector nalus; +public: + SrsRtpFUAPayloadHevc(); + virtual ~SrsRtpFUAPayloadHevc(); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +// FU, for one NALU with multiple fragments. +// With only one payload for HEVC. +class SrsRtpFUAPayloadHevc2 : public ISrsRtpPayloader +{ +public: + bool start; + bool end; + SrsHevcNaluType nalu_type; + char* payload; + int size; +public: + SrsRtpFUAPayloadHevc2(); + virtual ~SrsRtpFUAPayloadHevc2(); +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + #endif diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index b0d428aa7a..7b0b51a652 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -3567,6 +3567,204 @@ VOID TEST(KernelCodecTest, AVFrameNoConfig) HELPER_EXPECT_SUCCESS(f.add_sample((char*)"\x05", 1)); } } +VOID TEST(KernelCodecTest, VideoFrameH264) +{ + srs_error_t err; + + if (true) { + // I Frame + uint8_t data[] = {0x05, 0x00, 0x00, 0x00}; + SrsSample sample((char*)data, sizeof(data)); + + SrsAvcNaluType nalu_type = SrsAvcNaluTypeForbidden; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample, nalu_type)); + EXPECT_EQ(nalu_type, SrsAvcNaluTypeIDR); + + // P Frame + uint8_t data2[] = {0x01, 0x00, 0x00, 0x00}; + SrsSample sample2((char*)data2, sizeof(data2)); + + nalu_type = SrsAvcNaluTypeForbidden; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample2, nalu_type)); + EXPECT_EQ(nalu_type, SrsAvcNaluTypeNonIDR); + + // SPS + uint8_t data3[] = {0x07, 0x00, 0x00, 0x00}; + SrsSample sample3((char*)data3, sizeof(data3)); + + nalu_type = SrsAvcNaluTypeForbidden; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample3, nalu_type)); + EXPECT_EQ(nalu_type, SrsAvcNaluTypeSPS); + + // PPS + uint8_t data4[] = {0x08, 0x00, 0x00, 0x00}; + SrsSample sample4((char*)data4, sizeof(data4)); + + nalu_type = SrsAvcNaluTypeForbidden; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample4, nalu_type)); + EXPECT_EQ(nalu_type, SrsAvcNaluTypePPS); + + // Empty Sample + SrsSample empty_sample(NULL, 0); + HELPER_EXPECT_FAILED(SrsVideoFrame::parse_avc_nalu_type(&empty_sample, nalu_type)); + } + + if (true) { + // B Frame, slice_type=1(B Frame) + uint8_t data[] = {0x01, 0xA8, 0x00, 0x00}; + SrsSample sample((char*)data, sizeof(data)); + + bool is_b_frame = false; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample, is_b_frame)); + EXPECT_TRUE(is_b_frame); + + // Non-B Frame, slice_type=0(P Frame) + uint8_t data2[] = {0x01, 0x88, 0x00, 0x00}; + SrsSample sample2((char*)data2, sizeof(data2)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample2, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // SPS + uint8_t data3[] = {0x07, 0xA8, 0x00, 0x00}; + SrsSample sample3((char*)data3, sizeof(data3)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample3, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // PPS + uint8_t data4[] = {0x08, 0xA8, 0x00, 0x00}; + SrsSample sample4((char*)data4, sizeof(data4)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample4, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // IDR + uint8_t data5[] = {0x05, 0xA8, 0x00, 0x00}; + SrsSample sample5((char*)data5, sizeof(data5)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample5, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // Empty Sample + SrsSample empty_sample(NULL, 0); + HELPER_EXPECT_FAILED(SrsVideoFrame::parse_avc_bframe(&empty_sample, is_b_frame)); + } +} + +#ifdef SRS_H265 +VOID TEST(KernelCodecTest, VideoFrameH265) +{ + srs_error_t err; + + if (true) { + // I Frame + uint8_t data[] = {0x26, 0x01, 0x00, 0x00}; + SrsSample sample((char*)data, sizeof(data)); + + SrsHevcNaluType nalu_type = SrsHevcNaluType_INVALID; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample, nalu_type)); + EXPECT_EQ(nalu_type, SrsHevcNaluType_CODED_SLICE_IDR); + + // P Frame + uint8_t data2[] = {0x02, 0x01, 0x00, 0x00}; + SrsSample sample2((char*)data2, sizeof(data2)); + + nalu_type = SrsHevcNaluType_INVALID; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample2, nalu_type)); + EXPECT_EQ(nalu_type, SrsHevcNaluType_CODED_SLICE_TRAIL_R); + + // VPS + uint8_t data3[] = {0x40, 0x01, 0x00, 0x00}; + SrsSample sample3((char*)data3, sizeof(data3)); + + nalu_type = SrsHevcNaluType_INVALID; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample3, nalu_type)); + EXPECT_EQ(nalu_type, SrsHevcNaluType_VPS); + + // SPS + uint8_t data4[] = {0x42, 0x01, 0x00, 0x00}; + SrsSample sample4((char*)data4, sizeof(data4)); + + nalu_type = SrsHevcNaluType_INVALID; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample4, nalu_type)); + EXPECT_EQ(nalu_type, SrsHevcNaluType_SPS); + + // PPS + uint8_t data5[] = {0x44, 0x01, 0x00, 0x00}; + SrsSample sample5((char*)data5, sizeof(data5)); + + nalu_type = SrsHevcNaluType_INVALID; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample5, nalu_type)); + EXPECT_EQ(nalu_type, SrsHevcNaluType_PPS); + + // Empty Sample + SrsSample empty_sample(NULL, 0); + HELPER_EXPECT_FAILED(SrsVideoFrame::parse_hevc_nalu_type(&empty_sample, nalu_type)); + } + + if (true) { + SrsFormat format; + HELPER_EXPECT_SUCCESS(format.initialize()); + + // B Frame, slice_type=0(B Frame) + uint8_t data[] = {0x02, 0x01, 0xE0, 0x44}; + SrsSample sample((char*)data, sizeof(data)); + + bool is_b_frame = false; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample, &format, is_b_frame)); + EXPECT_TRUE(is_b_frame); + + // Non-B Frame, slice_type=1(P Frame) + uint8_t data2[] = {0x02, 0x01, 0xD0, 0x30}; + SrsSample sample2((char*)data2, sizeof(data2)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample2, &format, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // VPS + uint8_t data3[] = {0x40, 0x01, 0xE0, 0x44}; + SrsSample sample3((char*)data3, sizeof(data3)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample3, &format, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // SPS + uint8_t data4[] = {0x42, 0x01, 0xE0, 0x44}; + SrsSample sample4((char*)data4, sizeof(data4)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample4, &format, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // PPS + uint8_t data5[] = {0x44, 0x01, 0xE0, 0x44}; + SrsSample sample5((char*)data5, sizeof(data5)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample5, &format, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // IDR + uint8_t data6[] = {0x26, 0x01, 0xE0, 0x44}; + SrsSample sample6((char*)data6, sizeof(data6)); + + is_b_frame = true; + HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample6, &format, is_b_frame)); + EXPECT_FALSE(is_b_frame); + + // Empty Sample + SrsSample empty_sample(NULL, 0); + HELPER_EXPECT_FAILED(SrsVideoFrame::parse_hevc_bframe(&empty_sample, &format, is_b_frame)); + } +} +#endif VOID TEST(KernelCodecTest, IsSequenceHeaderSpecial) { @@ -4221,7 +4419,7 @@ VOID TEST(KernelCodecTest, HevcVideoFormat) if (true) { SrsFormat f; HELPER_EXPECT_SUCCESS(f.initialize()); - + // firstly demux sequence header HELPER_EXPECT_SUCCESS(f.on_video(0, (char*)ext_vps_sps_pps, sizeof(ext_vps_sps_pps))); EXPECT_EQ(1, f.video->frame_type); diff --git a/trunk/src/utest/srs_utest_rtc.cpp b/trunk/src/utest/srs_utest_rtc.cpp index b406790080..9603c512c8 100644 --- a/trunk/src/utest/srs_utest_rtc.cpp +++ b/trunk/src/utest/srs_utest_rtc.cpp @@ -62,7 +62,7 @@ VOID TEST(KernelRTCTest, RtpSTAPPayloadException) SrsAvcNaluType nalu_type = SrsAvcNaluTypeReserved; // Try to parse the NALU type for video decoder. if (!buf.empty()) { - nalu_type = SrsAvcNaluType((uint8_t)(buf.head()[0] & kNalTypeMask)); + nalu_type = SrsAvcNaluTypeParse(buf.head()[0]); } EXPECT_TRUE(nalu_type == kStapA);