diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 04d1c47d..26d54bbc 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 04d1c47d2568f5ce1ff84260cefaf2754e514a5e +Subproject commit 26d54bbc7b1860a450434dce49bbc8fcbcbae88b diff --git a/CMakeLists.txt b/CMakeLists.txt index 72a4544e..cd0124a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,8 @@ option(ENABLE_SCTP "Enable SCTP" ON) option(ENABLE_WEBRTC "Enable WebRTC" ON) option(ENABLE_X264 "Enable x264" OFF) option(ENABLE_WEPOLL "Enable wepoll" ON) -option(DISABLE_REPORT "Disable report to report.zlmediakit.com" off) +option(ENABLE_VIDEOSTACK "Enable video stack" OFF) +option(DISABLE_REPORT "Disable report to report.zlmediakit.com" OFF) option(USE_SOLUTION_FOLDERS "Enable solution dir supported" ON) ############################################################################## # 设置socket默认缓冲区大小为256k.如果设置为0则不设置socket的默认缓冲区大小,使用系统内核默认值(设置为0仅对linux有效) @@ -203,7 +204,7 @@ elseif(WIN32) if (MSVC) set(COMPILE_OPTIONS_DEFAULT # TODO: /wd4819 应该是不会生效 - "/wd4566;/wd4819" + "/wd4566;/wd4819;/utf-8" # warning C4530: C++ exception handler used, but unwind semantics are not enabled. "/EHsc") # disable Windows logo @@ -535,6 +536,6 @@ file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/default.pem" DESTINATION ${EXECUTABLE_OUT # 拷贝VideoStack 无视频流时默认填充的背景图片 # Copy the default background image used by VideoStack when there is no video stream -if (ENABLE_FFMPEG AND ENABLE_X264) +if (ENABLE_VIDEOSTACK AND ENABLE_FFMPEG AND ENABLE_X264) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/novideo.yuv" DESTINATION ${EXECUTABLE_OUTPUT_PATH}) endif () diff --git a/README.md b/README.md index 44c22aa9..59fd30a4 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ bash build_docker_images.sh - 请关注微信公众号获取最新消息推送: - - 也可以自愿有偿加入知识星球咨询和获取资料: + - 也可以自愿有偿加入知识星球咨询、获取资料以及加入微信技术群: diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp index 758e38a3..8fdb38af 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -528,7 +528,7 @@ API_EXPORT void API_CALL mk_auth_invoker_clone_release(const mk_auth_invoker ctx } ///////////////////////////////////////////WebRtcTransport///////////////////////////////////////////// -API_EXPORT void API_CALL mk_rtc_sendDatachannel(const mk_rtc_transport ctx, uint16_t streamId, uint32_t ppid, const char *msg, size_t len) { +API_EXPORT void API_CALL mk_rtc_send_datachannel(const mk_rtc_transport ctx, uint16_t streamId, uint32_t ppid, const char *msg, size_t len) { #ifdef ENABLE_WEBRTC assert(ctx && msg); WebRtcTransport *transport = (WebRtcTransport *)ctx; diff --git a/conf/config.ini b/conf/config.ini index 3c8b67c8..872d2d32 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -219,7 +219,7 @@ timeout_sec=15 retry_count=3 [http] -#http服务器字符编码,windows上默认gb2312 +#http服务器字符编码集 charSet=utf-8 #http链接超时时间 keepAliveSecond=30 diff --git a/ext-codec/G711Rtp.cpp b/ext-codec/G711Rtp.cpp index 16a9c5c2..766d600f 100644 --- a/ext-codec/G711Rtp.cpp +++ b/ext-codec/G711Rtp.cpp @@ -46,7 +46,7 @@ bool G711RtpEncoder::inputFrame(const Frame::Ptr &frame) { const size_t rtp_size = max_size; n++; stamp += _pkt_dur_ms; - RtpCodec::inputRtp(getRtpInfo().makeRtp(TrackAudio, ptr, rtp_size, mark, stamp), true); + RtpCodec::inputRtp(getRtpInfo().makeRtp(TrackAudio, ptr, rtp_size, mark, stamp), false); ptr += rtp_size; remain_size -= rtp_size; } diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index ae3879b0..c3ddd249 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -2044,6 +2044,47 @@ }, "response": [] }, + { + "name": "获取rtp发送列表(listRtpSender)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/listRtpSender?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=test", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "listRtpSender" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}", + "description": "api操作密钥(配置文件配置)" + }, + { + "key": "vhost", + "value": "{{defaultVhost}}", + "description": "虚拟主机,例如__defaultVhost__" + }, + { + "key": "app", + "value": "live", + "description": "应用名,例如 live" + }, + { + "key": "stream", + "value": "test", + "description": "流id,例如 obs" + } + ] + } + }, + "response": [] + }, { "name": "获取版本信息(version)", "request": { diff --git a/server/VideoStack.cpp b/server/VideoStack.cpp index d5168147..21cb7ff6 100644 --- a/server/VideoStack.cpp +++ b/server/VideoStack.cpp @@ -1,4 +1,4 @@ -#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG) +#if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined(ENABLE_FFMPEG) #include "VideoStack.h" #include "Codec/Transcode.h" #include "Common/Device.h" diff --git a/server/VideoStack.h b/server/VideoStack.h index 99455b40..cc0bfaa8 100644 --- a/server/VideoStack.h +++ b/server/VideoStack.h @@ -1,5 +1,5 @@ #pragma once -#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG) +#if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined(ENABLE_FFMPEG) #include "Codec/Transcode.h" #include "Common/Device.h" #include "Player/MediaPlayer.h" diff --git a/server/WebApi.cpp b/server/WebApi.cpp index a1ef1f65..8ea758f3 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -62,7 +62,7 @@ #include "ZLMVersion.h" #endif -#if defined(ENABLE_X264) && defined (ENABLE_FFMPEG) +#if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined (ENABLE_FFMPEG) #include "VideoStack.h" #endif @@ -208,7 +208,7 @@ static ApiArgsType getAllArgs(const Parser &parser) { if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) { auto contentArgs = parser.parseArgs(parser.content()); for (auto &pr : contentArgs) { - allArgs[pr.first] = HttpSession::urlDecodeComponent(pr.second); + allArgs[pr.first] = strCoding::UrlDecodeComponent(pr.second); } } else if (parser["Content-Type"].find("application/json") == 0) { try { @@ -1315,7 +1315,7 @@ void installWebApi() { if (!src) { throw ApiRetException("can not find the source stream", API::NotFound); } - auto type = allArgs["type"].as(); + auto type = allArgs["type"].empty() ? (int)MediaSourceEvent::SendRtpArgs::kRtpPS : allArgs["type"].as(); if (!allArgs["use_ps"].empty()) { // 兼容之前的use_ps参数 type = allArgs["use_ps"].as(); @@ -1347,6 +1347,26 @@ void installWebApi() { }); }); + api_regist("/index/api/listRtpSender",[](API_ARGS_MAP_ASYNC){ + CHECK_SECRET(); + CHECK_ARGS("vhost", "app", "stream"); + + auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]); + if (!src) { + throw ApiRetException("can not find the source stream", API::NotFound); + } + + auto muxer = src->getMuxer(); + CHECK(muxer, "get muxer from media source failed"); + + src->getOwnerPoller()->async([=]() mutable { + muxer->forEachRtpSender([&](const std::string &ssrc) mutable { + val["data"].append(ssrc); + }); + invoker(200, headerOut, val.toStyledString()); + }); + }); + api_regist("/index/api/startSendRtpPassive",[](API_ARGS_MAP_ASYNC){ CHECK_SECRET(); CHECK_ARGS("vhost", "app", "stream", "ssrc"); @@ -1355,7 +1375,7 @@ void installWebApi() { if (!src) { throw ApiRetException("can not find the source stream", API::NotFound); } - auto type = allArgs["type"].as(); + auto type = allArgs["type"].empty() ? (int)MediaSourceEvent::SendRtpArgs::kRtpPS : allArgs["type"].as(); if (!allArgs["use_ps"].empty()) { // 兼容之前的use_ps参数 type = allArgs["use_ps"].as(); @@ -1910,7 +1930,7 @@ void installWebApi() { } }); -#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG) +#if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined(ENABLE_FFMPEG) VideoStackManager::Instance().loadBgImg("novideo.yuv"); NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) { auto id = sender.getMediaTuple().stream; diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index fa46e4fa..fb00a668 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -173,6 +173,12 @@ std::string MultiMediaSourceMuxer::shortUrl() const { return _tuple.shortUrl(); } +void MultiMediaSourceMuxer::forEachRtpSender(const std::function &cb) const { + for (auto &pr : _rtp_sender) { + cb(pr.first); + } +} + MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) { if (!option.stream_replace.empty()) { // 支持在on_publish hook中替换stream_id diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index 9ca34370..a9775c8e 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -133,6 +133,8 @@ public: const MediaTuple &getMediaTuple() const; std::string shortUrl() const; + void forEachRtpSender(const std::function &cb) const; + protected: /////////////////////////////////MediaSink override///////////////////////////////// diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 7e7de860..47f3c2d8 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -294,8 +294,8 @@ void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const st splitUrl(ip, ip, port); _url = std::move(url); - _user = strCoding::UrlDecodeComponent(user); - _passwd = strCoding::UrlDecodeComponent(passwd); + _user = strCoding::UrlDecodeUserOrPass(user); + _passwd = strCoding::UrlDecodeUserOrPass(passwd); _host = std::move(ip); _port = port; _is_ssl = is_ssl; diff --git a/src/Common/config.cpp b/src/Common/config.cpp index a34ee84f..5a538ab4 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -181,12 +181,7 @@ static onceToken token([]() { mINI::Instance()[kKeepAliveSecond] = 15; mINI::Instance()[kDirMenu] = true; mINI::Instance()[kVirtualPath] = ""; - -#if defined(_WIN32) - mINI::Instance()[kCharSet] = "gb2312"; -#else mINI::Instance()[kCharSet] = "utf-8"; -#endif mINI::Instance()[kRootPath] = "./www"; mINI::Instance()[kNotFound] = StrPrinter << "" diff --git a/src/Common/strCoding.cpp b/src/Common/strCoding.cpp index 1a0f0236..49a345ef 100644 --- a/src/Common/strCoding.cpp +++ b/src/Common/strCoding.cpp @@ -52,9 +52,7 @@ char HexStrToBin(const char *str) { } return (high << 4) | low; } - -string strCoding::UrlEncodePath(const string &str) { - const char *dont_escape = "!#&'*+:=?@/._-$,;~()"; +static string UrlEncodeCommon(const string &str,const char* dont_escape){ string out; size_t len = str.size(); for (size_t i = 0; i < len; ++i) { @@ -69,26 +67,7 @@ string strCoding::UrlEncodePath(const string &str) { } return out; } - -string strCoding::UrlEncodeComponent(const string &str) { - const char *dont_escape = "!'()*-._~"; - string out; - size_t len = str.size(); - for (size_t i = 0; i < len; ++i) { - char ch = str[i]; - if (isalnum((uint8_t) ch) || strchr(dont_escape, (uint8_t) ch) != NULL) { - out.push_back(ch); - } else { - char buf[4]; - snprintf(buf, 4, "%%%X%X", (uint8_t) ch >> 4, (uint8_t) ch & 0x0F); - out.append(buf); - } - } - return out; -} - -string strCoding::UrlDecodePath(const string &str) { - const char *dont_unescape = "#$&+,/:;=?@"; +static string UrlDecodeCommon(const string &str,const char* dont_unescape){ string output; size_t i = 0, len = str.length(); while (i < len) { @@ -114,6 +93,36 @@ string strCoding::UrlDecodePath(const string &str) { return output; } +string strCoding::UrlEncodePath(const string &str) { + const char *dont_escape = "!#&'*+:=?@/._-$,;~()"; + return UrlEncodeCommon(str,dont_escape); +} + +string strCoding::UrlEncodeComponent(const string &str) { + const char *dont_escape = "!'()*-._~"; + return UrlEncodeCommon(str,dont_escape); +} + +std::string strCoding::UrlEncodeUserOrPass(const std::string &str) { + // from rfc https://datatracker.ietf.org/doc/html/rfc3986 + // §2.3 Unreserved characters (mark) + //'-', '_', '.', '~' + // §2.2 Reserved characters (reserved) + // '$', '&', '+', ',', '/', ':', ';', '=', '?', '@', + // §3.2.1 + // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in + // userinfo, so we must escape only '@', '/', and '?'. + // The parsing of userinfo treats ':' as special so we must escape + // that too. + const char *dont_escape = "$&+,;=-._~"; + return UrlEncodeCommon(str,dont_escape); +} + +string strCoding::UrlDecodePath(const string &str) { + const char *dont_unescape = "#$&+,/:;=?@"; + return UrlDecodeCommon(str,dont_unescape); +} + std::string strCoding::UrlDecodeComponent(const std::string &str) { string output; size_t i = 0, len = str.length(); @@ -143,6 +152,11 @@ std::string strCoding::UrlDecodeComponent(const std::string &str) { return output; } + +std::string strCoding::UrlDecodeUserOrPass(const std::string &str) { + const char *dont_unescape = ""; + return UrlDecodeCommon(str,dont_unescape); +} ///////////////////////////////windows专用/////////////////////////////////// #if defined(_WIN32) void UnicodeToGB2312(char* pOut, wchar_t uData) diff --git a/src/Common/strCoding.h b/src/Common/strCoding.h index e715e74d..bfddf7ff 100644 --- a/src/Common/strCoding.h +++ b/src/Common/strCoding.h @@ -22,6 +22,8 @@ public: static std::string UrlEncodeComponent(const std::string &str); // url参数 utf8编码 static std::string UrlDecodePath(const std::string &str); //url路径 utf8解码 static std::string UrlDecodeComponent(const std::string &str); // url参数 utf8解码 + static std::string UrlEncodeUserOrPass(const std::string &str); // url中用户名与密码编码 + static std::string UrlDecodeUserOrPass(const std::string &str); // url中用户名与密码解码 #if defined(_WIN32) static std::string UTF8ToGB2312(const std::string &str);//utf_8转为gb2312 static std::string GB2312ToUTF8(const std::string &str); //gb2312 转utf_8 diff --git a/src/Http/HttpClient.cpp b/src/Http/HttpClient.cpp index b9927934..84450d2b 100644 --- a/src/Http/HttpClient.cpp +++ b/src/Http/HttpClient.cpp @@ -66,7 +66,8 @@ void HttpClient::sendRequest(const string &url) { _http_persistent = true; if (_body && _body->remainSize()) { _header.emplace("Content-Length", to_string(_body->remainSize())); - _header.emplace("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + GET_CONFIG(string, charSet, Http::kCharSet); + _header.emplace("Content-Type", "application/x-www-form-urlencoded; charset=" + charSet); } bool host_changed = (_last_host != host + ":" + to_string(port)) || (_is_https != is_https); diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index ba74d803..ba25f3f4 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -683,34 +683,10 @@ void HttpSession::sendResponse(int code, AsyncSender::onSocketFlushed(data); } -string HttpSession::urlDecodePath(const string &str) { - auto ret = strCoding::UrlDecodePath(str); -#ifdef _WIN32 - GET_CONFIG(string, charSet, Http::kCharSet); - bool isGb2312 = !strcasecmp(charSet.data(), "gb2312"); - if (isGb2312) { - ret = strCoding::UTF8ToGB2312(ret); - } -#endif // _WIN32 - return ret; -} - -string HttpSession::urlDecodeComponent(const string &str) { - auto ret = strCoding::UrlDecodeComponent(str); -#ifdef _WIN32 - GET_CONFIG(string, charSet, Http::kCharSet); - bool isGb2312 = !strcasecmp(charSet.data(), "gb2312"); - if (isGb2312) { - ret = strCoding::UTF8ToGB2312(ret); - } -#endif // _WIN32 - return ret; -} - void HttpSession::urlDecode(Parser &parser) { - parser.setUrl(urlDecodePath(parser.url())); + parser.setUrl(strCoding::UrlDecodePath(parser.url())); for (auto &pr : _parser.getUrlArgs()) { - const_cast(pr.second) = urlDecodeComponent(pr.second); + const_cast(pr.second) = strCoding::UrlDecodeComponent(pr.second); } } diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 0ffbf137..9f328063 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -44,8 +44,6 @@ public: void onRecv(const toolkit::Buffer::Ptr &) override; void onError(const toolkit::SockException &err) override; void onManager() override; - static std::string urlDecodePath(const std::string &str); - static std::string urlDecodeComponent(const std::string &str); void setTimeoutSec(size_t second); void setMaxReqSize(size_t max_req_size); diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index 11d5190a..bc11028f 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -28,7 +28,7 @@ void HlsMaker::makeIndexFile(bool include_delay, bool eof) { GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay); GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); std::deque> temp(_seg_dur_list); - if (!include_delay) { + if (!include_delay && _seg_number) { while (temp.size() > _seg_number) { temp.pop_front(); } diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index 1cf393e2..e83cc090 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -210,7 +210,8 @@ void RtspPlayer::handleResDESCRIBE(const Parser &parser) { if (play_track != TrackInvalid) { auto track = sdpParser.getTrack(play_track); _sdp_track.emplace_back(track); - sdp = track->toString(); + auto title_track = sdpParser.getTrack(TrackTitle); + sdp = (title_track ? title_track->toString() : "") + track->toString(); } else { _sdp_track = sdpParser.getAvailableTrack(); sdp = sdpParser.toString(); diff --git a/webrtc/RtpExt.cpp b/webrtc/RtpExt.cpp index d1ade570..e186cbf0 100644 --- a/webrtc/RtpExt.cpp +++ b/webrtc/RtpExt.cpp @@ -204,7 +204,8 @@ static unordered_map s_url_to_type = {RTP_EXT_M RtpExtType RtpExt::getExtType(const string &url) { auto it = s_url_to_type.find(url); if (it == s_url_to_type.end()) { - throw std::invalid_argument(string("未识别的rtp ext url类型:") + url); + WarnL << "unknown rtp ext url type: " << url; + return RtpExtType::padding; } return it->second; }