概述

        由于谷歌webRTC不支持H265,所以浏览器默认也不支持H265,但是H265比H264可以节省一半的带宽,更重要的是H265生态在国内比较成熟,如安防等很多行业芯片都支持H265编码,谷歌强推的VP9和AV1生态在国内不成熟,支持的芯片和设备比较少。

      谷歌不支持H265,所以支持webrtc的流媒体服务器也都不支持,SRS是国内外使用量最大的流媒体服务器也不支持,怎么办?我们可以修改它使其支持。

修改代码

   修改SRS支持webRTC的H265需要修改SDP和视频源的支持,也需要修改PLI等其他的支持,本篇文章先讲修改SDP和视频源,其他的在后面文章中讲。

SRS支持webRTC的信令是自定义的,首先就要修改SDP支持。

修改SDP支持多个编码

bool SrsMediaDesc::find_encoding_name(const std::string& encoding_name) const{

    std::string lower_name(encoding_name), upper_name(encoding_name);
    transform(encoding_name.begin(), encoding_name.end(), lower_name.begin(), ::tolower);
    transform(encoding_name.begin(), encoding_name.end(), upper_name.begin(), ::toupper);

    for (size_t i = 0; i < payload_types_.size(); ++i) {
        if (payload_types_[i].encoding_name_ == std::string(lower_name.c_str()) ||
               payload_types_[i].encoding_name_ == std::string(upper_name.c_str())) {
           return true;
        }
    }

    return false;
}

修改推流时视频源使其支持H265

srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc, SrsRtcSourceDescription* stream_desc)
{
    srs_error_t err = srs_success;

    if (!stream_desc) {
        return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "stream description is NULL");
    }

    SrsRequest* req = ruc->req_;
    const SrsSdp& remote_sdp = ruc->remote_sdp_;

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

    for (int i = 0; i < (int)remote_sdp.media_descs_.size(); ++i) {
        const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_.at(i);

        SrsRtcTrackDescription* track_desc = new SrsRtcTrackDescription();
        SrsAutoFree(SrsRtcTrackDescription, track_desc);

        track_desc->set_direction("recvonly");
        track_desc->set_mid(remote_media_desc.mid_);
        // Whether feature enabled in remote extmap.
        int remote_twcc_id = 0;
        if (true) {
            map<int, string> extmaps = remote_media_desc.get_extmaps();
            for(map<int, string>::iterator it = extmaps.begin(); it != extmaps.end(); ++it) {
                if (it->second == kTWCCExt) {
                    remote_twcc_id = it->first;
                    break;
                }
            }
        }

        if (twcc_enabled && remote_twcc_id) {
            track_desc->add_rtp_extension_desc(remote_twcc_id, kTWCCExt);
        }

        if (remote_media_desc.is_audio()) {
            // TODO: check opus format specific param
            std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("opus");
            if (payloads.empty()) {
                return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type");
            }

            for (int j = 0; j < (int)payloads.size(); j++) {
                const SrsMediaPayloadType& payload = payloads.at(j);

                // if the payload is opus, and the encoding_param_ is channel
                SrsAudioPayload* audio_payload = new SrsAudioPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_, ::atol(payload.encoding_param_.c_str()));
                audio_payload->set_opus_param_desc(payload.format_specific_param_);

                // TODO: FIXME: Only support some transport algorithms.
                for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
                    const string& rtcp_fb = payload.rtcp_fb_.at(k);

                    if (nack_enabled) {
                        if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
                            audio_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }
                    if (twcc_enabled && remote_twcc_id) {
                        if (rtcp_fb == "transport-cc") {
                            audio_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }
                }

                track_desc->type_ = "audio";
                track_desc->set_codec_payload((SrsCodecPayload*)audio_payload);
                // Only choose one match opus codec.
                break;
            }
        } else if (remote_media_desc.is_video() && ruc->codec_ == "av1") {
            std::vector<SrsMediaPayloadType> 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");
            }

            for (int j = 0; j < (int)payloads.size(); j++) {
                const SrsMediaPayloadType& payload = payloads.at(j);

                // Generate video payload for av1.
                SrsVideoPayload* video_payload = new SrsVideoPayload(payload.payload_type_, payload.encoding_name_, payload.clock_rate_);

                // TODO: FIXME: Only support some transport algorithms.
                for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
                    const string& rtcp_fb = payload.rtcp_fb_.at(k);

                    if (nack_enabled) {
                        if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
                            video_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }
                    if (twcc_enabled && remote_twcc_id) {
                        if (rtcp_fb == "transport-cc") {
                            video_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }
                }

                track_desc->type_ = "video";
                track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
                break;
            }
        } else if (remote_media_desc.is_video()&&remote_media_desc.find_encoding_name("H264")) {
            std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H264");
            if (payloads.empty()) {
                return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.264 payload type");
            }

            std::deque<SrsMediaPayloadType> backup_payloads;
            for (int j = 0; j < (int)payloads.size(); j++) {
                const SrsMediaPayloadType& payload = payloads.at(j);

                if (payload.format_specific_param_.empty()) {
                    backup_payloads.push_front(payload);
                    continue;
                }
                H264SpecificParam h264_param;
                if ((err = srs_parse_h264_fmtp(payload.format_specific_param_, h264_param)) != srs_success) {
                    srs_error_reset(err); continue;
                }

                // If not exists 42e01f, we pick up any profile such as 42001f.
                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 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_);

                    // TODO: FIXME: Only support some transport algorithms.
                    for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
                        const string& rtcp_fb = payload.rtcp_fb_.at(k);

                        if (nack_enabled) {
                            if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
                                video_payload->rtcp_fbs_.push_back(rtcp_fb);
                            }
                        }
                        if (twcc_enabled && remote_twcc_id) {
                            if (rtcp_fb == "transport-cc") {
                                video_payload->rtcp_fbs_.push_back(rtcp_fb);
                            }
                        }
                    }

                    track_desc->type_ = "video";
                    track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
                    // Only choose first match H.264 payload type.
                    break;
                }

                backup_payloads.push_back(payload);
            }

            // Try my best to pick at least one media payload type.
            if (!track_desc->media_ && ! backup_payloads.empty()) {
                const SrsMediaPayloadType& payload = backup_payloads.front();

                // 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_);

                // TODO: FIXME: Only support some transport algorithms.
                for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
                    const string& rtcp_fb = payload.rtcp_fb_.at(k);

                    if (nack_enabled) {
                        if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
                            video_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }

                    if (twcc_enabled && remote_twcc_id) {
                        if (rtcp_fb == "transport-cc") {
                            video_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }
                }

                track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
                srs_warn("choose backup H.264 payload type=%d", payload.payload_type_);
            }

            // TODO: FIXME: Support RRTR?
            //local_media_desc.payload_types_.back().rtcp_fb_.push_back("rrtr");
        }else if (remote_media_desc.is_video()&&remote_media_desc.find_encoding_name("H265")) {
            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");
            }

            std::deque<SrsMediaPayloadType> backup_payloads;
            for (int j = 0; j < (int)payloads.size(); j++) {
                const SrsMediaPayloadType& payload = payloads.at(j);

                if (payload.format_specific_param_.empty()) {
                    backup_payloads.push_front(payload);
                    continue;
                }
                H264SpecificParam h264_param;
                if ((err = srs_parse_h264_fmtp(payload.format_specific_param_, h264_param)) != srs_success) {
                    srs_error_reset(err); continue;
                }

                // If not exists 42e01f, we pick up any profile such as 42001f.
                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 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_);

                    // TODO: FIXME: Only support some transport algorithms.
                    for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
                        const string& rtcp_fb = payload.rtcp_fb_.at(k);

                        if (nack_enabled) {
                            if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
                                video_payload->rtcp_fbs_.push_back(rtcp_fb);
                            }
                        }
                        if (twcc_enabled && remote_twcc_id) {
                            if (rtcp_fb == "transport-cc") {
                                video_payload->rtcp_fbs_.push_back(rtcp_fb);
                            }
                        }
                    }

                    track_desc->type_ = "video";
                    track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
                    // Only choose first match H.264 payload type.
                    break;
                }

                backup_payloads.push_back(payload);
            }

            // Try my best to pick at least one media payload type.
            if (!track_desc->media_ && ! backup_payloads.empty()) {
                const SrsMediaPayloadType& payload = backup_payloads.front();

                // 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_);

                // TODO: FIXME: Only support some transport algorithms.
                for (int k = 0; k < (int)payload.rtcp_fb_.size(); ++k) {
                    const string& rtcp_fb = payload.rtcp_fb_.at(k);

                    if (nack_enabled) {
                        if (rtcp_fb == "nack" || rtcp_fb == "nack pli") {
                            video_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }

                    if (twcc_enabled && remote_twcc_id) {
                        if (rtcp_fb == "transport-cc") {
                            video_payload->rtcp_fbs_.push_back(rtcp_fb);
                        }
                    }
                }

                track_desc->set_codec_payload((SrsCodecPayload*)video_payload);
                srs_warn("choose backup H.265 payload type=%d", payload.payload_type_);
            }

            // TODO: FIXME: Support RRTR?
            //local_media_desc.payload_types_.back().rtcp_fb_.push_back("rrtr");
        }

        // TODO: FIXME: use one parse payload from sdp.

        track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("red"));
        track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("rtx"));
        track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("ulpfec"));

        std::string track_id;
        for (int j = 0; j < (int)remote_media_desc.ssrc_infos_.size(); ++j) {
            const SrsSSRCInfo& ssrc_info = remote_media_desc.ssrc_infos_.at(j);

            // ssrc have same track id, will be description in the same track description.
            if(track_id != ssrc_info.msid_tracker_) {
                SrsRtcTrackDescription* track_desc_copy = track_desc->copy();
                track_desc_copy->ssrc_ = ssrc_info.ssrc_;
                track_desc_copy->id_ = ssrc_info.msid_tracker_;
                track_desc_copy->msid_ = ssrc_info.msid_;

                if (remote_media_desc.is_audio() && !stream_desc->audio_track_desc_) {
                    stream_desc->audio_track_desc_ = track_desc_copy;
                } else if (remote_media_desc.is_video()) {
                    stream_desc->video_track_descs_.push_back(track_desc_copy);
                }
            }
            track_id = ssrc_info.msid_tracker_;
        }

        // set track fec_ssrc and rtx_ssrc
        for (int j = 0; j < (int)remote_media_desc.ssrc_groups_.size(); ++j) {
            const SrsSSRCGroup& ssrc_group = remote_media_desc.ssrc_groups_.at(j);

            SrsRtcTrackDescription* track_desc = stream_desc->find_track_description_by_ssrc(ssrc_group.ssrcs_[0]);
            if (!track_desc) {
                continue;
            }

            if (ssrc_group.semantic_ == "FID") {
                track_desc->set_rtx_ssrc(ssrc_group.ssrcs_[1]);
            } else if (ssrc_group.semantic_ == "FEC") {
                track_desc->set_fec_ssrc(ssrc_group.ssrcs_[1]);
            }
        }
    }

    return err;
}

这样SRS信令就可以支持H265

客户端测试

使用yangwebrtc可以进行测试,yangwebrtc已经支持webrtc的H265

详见用yangwebrtc搭建低延迟并节省一半码率的H265的webrtc应用_m0_56595685的博客-CSDN博客https://blog.csdn.net/m0_56595685/article/details/121880362

下载源代码

GitHub - metartc/srs-webrtc265https://github.com/metartc/srs-webrtc265

srs-webrtc265: 支持webrtc的H265(hevc)的srs版本https://gitee.com/metartc/srs-webrtc265

git clone https://github.com/metartc/srs-webrtc265.git

或者

git clone https://gitee.com/metartc/srs-webrtc265.git

编译:

./configure

make

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐