diff --git a/.github/ISSUE_TEMPLATE/compile.md b/.github/ISSUE_TEMPLATE/compile.md new file mode 100644 index 00000000..4e052196 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/compile.md @@ -0,0 +1,57 @@ +--- +name: 编译问题反馈 +about: 反馈 ZLMediaKit 编译相关的问题 +title: "[编译问题]: " +labels: 编译问题 +assignees: '' + +--- + + + + + +### 相关日志及环境信息 + + + +**清除编译缓存后,完整执行 cmake && make 命令的输出** + +
+展开查看详细编译日志 +
+
+```
+详细日志粘在这里!
+```
+
+
+
+ +编译目录下的 `CMakeCache.txt` 文件内容,请直接上传为附件。 + +### 各种环境信息 + + + +* **代码提交记录/git commit hash**: +* **操作系统及版本**: +* **硬件信息**: +* **其他需要补充的信息**: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7f6d8bf8..db67603d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,6 +33,9 @@ jobs: with: cosign-release: 'v1.7.1' + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + # Workaround: https://github.com/docker/build-push-action/issues/461 - name: Setup Docker buildx @@ -67,6 +70,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: MODEL=Release + platforms: linux/amd64,linux/arm64 # Sign the resulting Docker image digest except on PRs. # This will only write to the public Rekor transparency log when the Docker diff --git a/.gitmodules b/.gitmodules index 68b49644..7ef0bc71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "3rdpart/media-server"] path = 3rdpart/media-server url = https://gitee.com/ireader/media-server +[submodule "3rdpart/jsoncpp"] + path = 3rdpart/jsoncpp + url = https://gitee.com/mirrors/jsoncpp.git \ No newline at end of file diff --git a/.gitmodules_github b/.gitmodules_github index 572aff62..ddd310e6 100644 --- a/.gitmodules_github +++ b/.gitmodules_github @@ -4,3 +4,6 @@ [submodule "3rdpart/media-server"] path = 3rdpart/media-server url = https://github.com/ireader/media-server +[submodule "3rdpart/jsoncpp"] + path = 3rdpart/jsoncpp + url = https://github.com/open-source-parsers/jsoncpp.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 95206ba5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: cpp -sudo: required -dist: trusty -compiler: -- clang -os: -- linux -script: -- ./build_for_linux.sh - - - diff --git a/3rdpart/CMakeLists.txt b/3rdpart/CMakeLists.txt index 5732f7a2..f489b1a8 100644 --- a/3rdpart/CMakeLists.txt +++ b/3rdpart/CMakeLists.txt @@ -24,15 +24,15 @@ ############################################################################## # jsoncpp -aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp JSONCPP_SRC_LIST) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json JSONCPP_SRC_LIST) add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST}) target_compile_options(jsoncpp PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(jsoncpp PRIVATE - "$" + "$/jsoncpp/include" PUBLIC - "$") + "$/jsoncpp/include") update_cached_list(MK_LINK_LIBRARIES jsoncpp) @@ -90,8 +90,12 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS) aux_source_directory(${MediaServer_MPEG_ROOT}/source MPEG_SRC_LIST) add_library(mpeg STATIC ${MPEG_SRC_LIST}) add_library(MediaServer::mpeg ALIAS mpeg) + # media-server库相关编译宏 + # MPEG_H26X_VERIFY - 视频流类型识别 + # MPEG_ZERO_PAYLOAD_LENGTH - 兼容hik流 + # MPEG_DAHUA_AAC_FROM_G711 - 兼容dahua流 target_compile_options(mpeg - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + PRIVATE ${COMPILE_OPTIONS_DEFAULT} -DMPEG_H26X_VERIFY -DMPEG_ZERO_PAYLOAD_LENGTH -DMPEG_DAHUA_AAC_FROM_G711) target_include_directories(mpeg PRIVATE "$" diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 82f82e72..617b6b1d 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 82f82e723eda127777aaf91ad0e4615e8d56e8d5 +Subproject commit 617b6b1db23f13e2592b29204b84b1b9dbbf81c0 diff --git a/3rdpart/jsoncpp b/3rdpart/jsoncpp new file mode 160000 index 00000000..8190e061 --- /dev/null +++ b/3rdpart/jsoncpp @@ -0,0 +1 @@ +Subproject commit 8190e061bc2d95da37479a638aa2c9e483e58ec6 diff --git a/3rdpart/jsoncpp/assertions.h b/3rdpart/jsoncpp/assertions.h deleted file mode 100644 index a80e4ebd..00000000 --- a/3rdpart/jsoncpp/assertions.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED -#define CPPTL_JSON_ASSERTIONS_H_INCLUDED - -#include -#include - -#if !defined(JSON_IS_AMALGAMATION) -#include "../jsoncpp/config.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -/** It should not be possible for a maliciously designed file to - * cause an abort() or seg-fault, so these macros are used only - * for pre-condition violations and internal logic errors. - */ -#if JSON_USE_EXCEPTION - -// @todo <= add detail about condition in exception -# define JSON_ASSERT(condition) \ - {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} - -# define JSON_FAIL_MESSAGE(message) \ - { \ - std::ostringstream oss; oss << message; \ - Json::throwLogicError(oss.str()); \ - } - -#else // JSON_USE_EXCEPTION - -# define JSON_ASSERT(condition) assert(condition) - -// The call to assert() will show the failure message in debug builds. In -// release builds we abort, for a core-dump or debugger. -# define JSON_FAIL_MESSAGE(message) \ - { \ - std::ostringstream oss; oss << message; \ - assert(false && oss.str().c_str()); \ - abort(); \ - } - - -#endif - -#define JSON_ASSERT_MESSAGE(condition, message) \ - if (!(condition)) { \ - JSON_FAIL_MESSAGE(message); \ - } - -#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED diff --git a/3rdpart/jsoncpp/autolink.h b/3rdpart/jsoncpp/autolink.h deleted file mode 100644 index 8ca57d0f..00000000 --- a/3rdpart/jsoncpp/autolink.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_AUTOLINK_H_INCLUDED -#define JSON_AUTOLINK_H_INCLUDED - -#include "../jsoncpp/config.h" - -#ifdef JSON_IN_CPPTL -#include -#endif - -#if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && \ - !defined(JSON_IN_CPPTL) -#define CPPTL_AUTOLINK_NAME "json" -#undef CPPTL_AUTOLINK_DLL -#ifdef JSON_DLL -#define CPPTL_AUTOLINK_DLL -#endif -#include "autolink.h" -#endif - -#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/3rdpart/jsoncpp/config.h b/3rdpart/jsoncpp/config.h deleted file mode 100644 index e09a515a..00000000 --- a/3rdpart/jsoncpp/config.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_CONFIG_H_INCLUDED -#define JSON_CONFIG_H_INCLUDED - -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of -/// std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 - -// If non-zero, the library uses exceptions to report bad input instead of C -// assertion macros. The default is to use exceptions. -#ifndef JSON_USE_EXCEPTION -#define JSON_USE_EXCEPTION 1 -#endif - -/// If defined, indicates that the source file is amalgated -/// to prevent private header inclusion. -/// Remarks: it is automatically defined in the generated amalgated header. -// #define JSON_IS_AMALGAMATION - -#ifdef JSON_IN_CPPTL -#include -#ifndef JSON_USE_CPPTL -#define JSON_USE_CPPTL 1 -#endif -#endif - -#ifdef JSON_IN_CPPTL -#define JSON_API CPPTL_API -#elif defined(JSON_DLL_BUILD) -#if defined(_MSC_VER) -#define JSON_API __declspec(dllexport) -#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING -#endif // if defined(_MSC_VER) -#elif defined(JSON_DLL) -#if defined(_MSC_VER) -#define JSON_API __declspec(dllimport) -#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING -#endif // if defined(_MSC_VER) -#endif // ifdef JSON_IN_CPPTL -#if !defined(JSON_API) -#define JSON_API -#endif - -// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for -// integer -// Storages, and 64 bits integer support is disabled. -// #define JSON_NO_INT64 1 - -#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 -// Microsoft Visual Studio 6 only support conversion from __int64 to double -// (no conversion from unsigned __int64). -#define JSON_USE_INT64_DOUBLE_CONVERSION 1 -// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' -// characters in the debug information) -// All projects I've ever seen with VS6 were using this globally (not bothering -// with pragma push/pop). -#pragma warning(disable : 4786) -#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 - -#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 -/// Indicates that the following function is deprecated. -#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) -#elif defined(__clang__) && defined(__has_feature) -#if __has_feature(attribute_deprecated_with_message) -#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) -#endif -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) -#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) -#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) -#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) -#endif - -#if !defined(JSONCPP_DEPRECATED) -#define JSONCPP_DEPRECATED(message) -#endif // if !defined(JSONCPP_DEPRECATED) - -namespace Json { -typedef int Int; -typedef unsigned int UInt; -#if defined(JSON_NO_INT64) -typedef int LargestInt; -typedef unsigned int LargestUInt; -#undef JSON_HAS_INT64 -#else // if defined(JSON_NO_INT64) -// For Microsoft Visual use specific types as long long is not supported -#if defined(_MSC_VER) // Microsoft Visual Studio -typedef __int64 Int64; -typedef unsigned __int64 UInt64; -#else // if defined(_MSC_VER) // Other platforms, use long long -typedef long long int Int64; -typedef unsigned long long int UInt64; -#endif // if defined(_MSC_VER) -typedef Int64 LargestInt; -typedef UInt64 LargestUInt; -#define JSON_HAS_INT64 -#endif // if defined(JSON_NO_INT64) -} // end namespace Json - -#endif // JSON_CONFIG_H_INCLUDED diff --git a/3rdpart/jsoncpp/features.h b/3rdpart/jsoncpp/features.h deleted file mode 100644 index c136dfa4..00000000 --- a/3rdpart/jsoncpp/features.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_FEATURES_H_INCLUDED -#define CPPTL_JSON_FEATURES_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "../jsoncpp/forwards.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - -/** \brief Configuration passed to reader and writer. - * This configuration object can be used to force the Reader or Writer - * to behave in a standard conforming way. - */ -class JSON_API Features { -public: - /** \brief A configuration that allows all features and assumes all strings - * are UTF-8. - * - C & C++ comments are allowed - * - Root object can be any JSON value - * - Assumes Value strings are encoded in UTF-8 - */ - static Features all(); - - /** \brief A configuration that is strictly compatible with the JSON - * specification. - * - Comments are forbidden. - * - Root object must be either an array or an object value. - * - Assumes Value strings are encoded in UTF-8 - */ - static Features strictMode(); - - /** \brief Initialize the configuration like JsonConfig::allFeatures; - */ - Features(); - - /// \c true if comments are allowed. Default: \c true. - bool allowComments_; - - /// \c true if root must be either an array or an object value. Default: \c - /// false. - bool strictRoot_; - - /// \c true if dropped null placeholders are allowed. Default: \c false. - bool allowDroppedNullPlaceholders_; - - /// \c true if numeric object key are allowed. Default: \c false. - bool allowNumericKeys_; -}; - -} // namespace Json - -#endif // CPPTL_JSON_FEATURES_H_INCLUDED diff --git a/3rdpart/jsoncpp/forwards.h b/3rdpart/jsoncpp/forwards.h deleted file mode 100644 index b3e39cb6..00000000 --- a/3rdpart/jsoncpp/forwards.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_FORWARDS_H_INCLUDED -#define JSON_FORWARDS_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "config.h" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - -// writer.h -class FastWriter; -class StyledWriter; - -// reader.h -class Reader; - -// features.h -class Features; - -// value.h -typedef unsigned int ArrayIndex; -class StaticString; -class Path; -class PathArgument; -class Value; -class ValueIteratorBase; -class ValueIterator; -class ValueConstIterator; - -} // namespace Json - -#endif // JSON_FORWARDS_H_INCLUDED diff --git a/3rdpart/jsoncpp/json.h b/3rdpart/jsoncpp/json.h deleted file mode 100644 index 71e99722..00000000 --- a/3rdpart/jsoncpp/json.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_JSON_H_INCLUDED -#define JSON_JSON_H_INCLUDED - -#include "autolink.h" -#include "features.h" -#include "reader.h" -#include "writer.h" -#include "value.h" - -#endif // JSON_JSON_H_INCLUDED diff --git a/3rdpart/jsoncpp/json_reader.cpp b/3rdpart/jsoncpp/json_reader.cpp deleted file mode 100644 index e5ca7751..00000000 --- a/3rdpart/jsoncpp/json_reader.cpp +++ /dev/null @@ -1,1979 +0,0 @@ -// Copyright 2007-2011 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -#include "assertions.h" -#include "reader.h" -#include "value.h" -#include "json_tool.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below -#define snprintf _snprintf -#endif - -#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 -// Disable warning about strdup being deprecated. -#pragma warning(disable : 4996) -#endif - -static int const stackLimit_g = 1000; -static int stackDepth_g = 0; // see readValue() - -namespace Json { - -#if __cplusplus >= 201103L -typedef std::unique_ptr CharReaderPtr; -#else -typedef std::auto_ptr CharReaderPtr; -#endif - -// Implementation of class Features -// //////////////////////////////// - -Features::Features() - : allowComments_(true), strictRoot_(false), - allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) {} - -Features Features::all() { return Features(); } - -Features Features::strictMode() { - Features features; - features.allowComments_ = false; - features.strictRoot_ = true; - features.allowDroppedNullPlaceholders_ = false; - features.allowNumericKeys_ = false; - return features; -} - -// Implementation of class Reader -// //////////////////////////////// - -static bool containsNewLine(Reader::Location begin, Reader::Location end) { - for (; begin < end; ++begin) - if (*begin == '\n' || *begin == '\r') - return true; - return false; -} - -// Class Reader -// ////////////////////////////////////////////////////////////////// - -Reader::Reader() - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(Features::all()), - collectComments_() {} - -Reader::Reader(const Features& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(features), collectComments_() { -} - -bool -Reader::parse(const std::string& document, Value& root, bool collectComments) { - document_ = document; - const char* begin = document_.c_str(); - const char* end = begin + document_.length(); - return parse(begin, end, root, collectComments); -} - -bool Reader::parse(std::istream& sin, Value& root, bool collectComments) { - // std::istream_iterator begin(sin); - // std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since std::string is reference-counted, this at least does not - // create an extra copy. - std::string doc; - std::getline(sin, doc, (char)EOF); - return parse(doc, root, collectComments); -} - -bool Reader::parse(const char* beginDoc, - const char* endDoc, - Value& root, - bool collectComments) { - if (!features_.allowComments_) { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; - commentsBefore_ = ""; - errors_.clear(); - while (!nodes_.empty()) - nodes_.pop(); - nodes_.push(&root); - - stackDepth_g = 0; // Yes, this is bad coding, but options are limited. - bool successful = readValue(); - Token token; - skipCommentTokens(token); - if (collectComments_ && !commentsBefore_.empty()) - root.setComment(commentsBefore_, commentAfter); - if (features_.strictRoot_) { - if (!root.isArray() && !root.isObject()) { - // Set error location to start of doc, ideally should be first token found - // in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( - "A valid JSON document must be either an array or an object value.", - token); - return false; - } - } - return successful; -} - -bool Reader::readValue() { - // This is a non-reentrant way to support a stackLimit. Terrible! - // But this deprecated class has a security problem: Bad input can - // cause a seg-fault. This seems like a fair, binary-compatible way - // to prevent the problem. - if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); - ++stackDepth_g; - - Token token; - skipCommentTokens(token); - bool successful = true; - - if (collectComments_ && !commentsBefore_.empty()) { - currentValue().setComment(commentsBefore_, commentBefore); - commentsBefore_ = ""; - } - - switch (token.type_) { - case tokenObjectBegin: - successful = readObject(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenArrayBegin: - successful = readArray(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenNumber: - successful = decodeNumber(token); - break; - case tokenString: - successful = decodeString(token); - break; - case tokenTrue: - { - Value v(true); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenFalse: - { - Value v(false); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNull: - { - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenArraySeparator: - case tokenObjectEnd: - case tokenArrayEnd: - if (features_.allowDroppedNullPlaceholders_) { - // "Un-read" the current token and mark the current value as a null - // token. - current_--; - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(current_ - begin_ - 1); - currentValue().setOffsetLimit(current_ - begin_); - break; - } // Else, fall through... - default: - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return addError("Syntax error: value, object or array expected.", token); - } - - if (collectComments_) { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - --stackDepth_g; - return successful; -} - -void Reader::skipCommentTokens(Token& token) { - if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); - } -} - -bool Reader::readToken(Token& token) { - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch (c) { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match("rue", 3); - break; - case 'f': - token.type_ = tokenFalse; - ok = match("alse", 4); - break; - case 'n': - token.type_ = tokenNull; - ok = match("ull", 3); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if (!ok) - token.type_ = tokenError; - token.end_ = current_; - return true; -} - -void Reader::skipSpaces() { - while (current_ != end_) { - Char c = *current_; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - ++current_; - else - break; - } -} - -bool Reader::match(Location pattern, int patternLength) { - if (end_ - current_ < patternLength) - return false; - int index = patternLength; - while (index--) - if (current_[index] != pattern[index]) - return false; - current_ += patternLength; - return true; -} - -bool Reader::readComment() { - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if (c == '*') - successful = readCStyleComment(); - else if (c == '/') - successful = readCppStyleComment(); - if (!successful) - return false; - - if (collectComments_) { - CommentPlacement placement = commentBefore; - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (c != '*' || !containsNewLine(commentBegin, current_)) - placement = commentAfterOnSameLine; - } - - addComment(commentBegin, current_, placement); - } - return true; -} - -static std::string normalizeEOL(Reader::Location begin, Reader::Location end) { - std::string normalized; - normalized.reserve(end - begin); - Reader::Location current = begin; - while (current != end) { - char c = *current++; - if (c == '\r') { - if (current != end && *current == '\n') - // convert dos EOL - ++current; - // convert Mac EOL - normalized += '\n'; - } else { - normalized += c; - } - } - return normalized; -} - -void -Reader::addComment(Location begin, Location end, CommentPlacement placement) { - assert(collectComments_); - const std::string& normalized = normalizeEOL(begin, end); - if (placement == commentAfterOnSameLine) { - assert(lastValue_ != 0); - lastValue_->setComment(normalized, placement); - } else { - commentsBefore_ += normalized; - } -} - -bool Reader::readCStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '*' && *current_ == '/') - break; - } - return getNextChar() == '/'; -} - -bool Reader::readCppStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '\n') - break; - if (c == '\r') { - // Consume DOS EOL. It will be normalized in addComment. - if (current_ != end_ && *current_ == '\n') - getNextChar(); - // Break on Moc OS 9 EOL. - break; - } - } - return true; -} - -void Reader::readNumber() { - const char *p = current_; - char c = '0'; // stopgap for already consumed character - // integral part - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : 0; - // fractional part - if (c == '.') { - c = (current_ = p) < end_ ? *p++ : 0; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : 0; - } - // exponential part - if (c == 'e' || c == 'E') { - c = (current_ = p) < end_ ? *p++ : 0; - if (c == '+' || c == '-') - c = (current_ = p) < end_ ? *p++ : 0; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : 0; - } -} - -bool Reader::readString() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '"') - break; - } - return c == '"'; -} - -bool Reader::readObject(Token& tokenStart) { - Token tokenName; - std::string name; - Value init(objectValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object - return true; - name = ""; - if (tokenName.type_ == tokenString) { - if (!decodeString(tokenName, name)) - return recoverFromError(tokenObjectEnd); - } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { - Value numberName; - if (!decodeNumber(tokenName, numberName)) - return recoverFromError(tokenObjectEnd); - name = numberName.asString(); - } else { - break; - } - - Token colon; - if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover( - "Missing ':' after object member name", colon, tokenObjectEnd); - } - Value& value = currentValue()[name]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenObjectEnd); - - Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover( - "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); - } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); - if (comma.type_ == tokenObjectEnd) - return true; - } - return addErrorAndRecover( - "Missing '}' or object member name", tokenName, tokenObjectEnd); -} - -bool Reader::readArray(Token& tokenStart) { - Value init(arrayValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - skipSpaces(); - if (*current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } - int index = 0; - for (;;) { - Value& value = currentValue()[index++]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenArrayEnd); - - Token token; - // Accept Comment after last item in the array. - ok = readToken(token); - while (token.type_ == tokenComment && ok) { - ok = readToken(token); - } - bool badTokenType = - (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); - if (!ok || badTokenType) { - return addErrorAndRecover( - "Missing ',' or ']' in array declaration", token, tokenArrayEnd); - } - if (token.type_ == tokenArrayEnd) - break; - } - return true; -} - -bool Reader::decodeNumber(Token& token) { - Value decoded; - if (!decodeNumber(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeNumber(Token& token, Value& decoded) { - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - bool isNegative = *current == '-'; - if (isNegative) - ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of them. - Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; - Value::LargestUInt value = 0; - while (current < token.end_) { - Char c = *current++; - if (c < '0' || c > '9') - return decodeDouble(token, decoded); - Value::UInt digit(c - '0'); - if (value >= threshold) { - // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, b) this is the last digit, and - // c) it's small enough to fit in that rounding delta, we're okay. - // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || - digit > maxIntegerValue % 10) { - return decodeDouble(token, decoded); - } - } - value = value * 10 + digit; - } - if (isNegative && value == maxIntegerValue) - decoded = Value::minLargestInt; - else if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxInt)) - decoded = Value::LargestInt(value); - else - decoded = value; - return true; -} - -bool Reader::decodeDouble(Token& token) { - Value decoded; - if (!decodeDouble(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeDouble(Token& token, Value& decoded) { - double value = 0; - std::string buffer(token.start_, token.end_); - std::istringstream is(buffer); - if (!(is >> value)) - return addError("'" + std::string(token.start_, token.end_) + - "' is not a number.", - token); - decoded = value; - return true; -} - -bool Reader::decodeString(Token& token) { - std::string decoded_string; - if (!decodeString(token, decoded_string)) - return false; - Value decoded(decoded_string); - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool Reader::decodeString(Token& token, std::string& decoded) { - decoded.reserve(token.end_ - token.start_ - 2); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while (current != end) { - Char c = *current++; - if (c == '"') - break; - else if (c == '\\') { - if (current == end) - return addError("Empty escape sequence in string", token, current); - Char escape = *current++; - switch (escape) { - case '"': - decoded += '"'; - break; - case '/': - decoded += '/'; - break; - case '\\': - decoded += '\\'; - break; - case 'b': - decoded += '\b'; - break; - case 'f': - decoded += '\f'; - break; - case 'n': - decoded += '\n'; - break; - case 'r': - decoded += '\r'; - break; - case 't': - decoded += '\t'; - break; - case 'u': { - unsigned int unicode; - if (!decodeUnicodeCodePoint(token, current, end, unicode)) - return false; - decoded += codePointToUTF8(unicode); - } break; - default: - return addError("Bad escape sequence in string", token, current); - } - } else { - decoded += c; - } - } - return true; -} - -bool Reader::decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode) { - - if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) { - // surrogate pairs - if (end - current < 6) - return addError( - "additional six characters expected to parse unicode surrogate pair.", - token, - current); - unsigned int surrogatePair; - if (*(current++) == '\\' && *(current++) == 'u') { - if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } else - return false; - } else - return addError("expecting another \\u token to begin the second half of " - "a unicode surrogate pair", - token, - current); - } - return true; -} - -bool Reader::decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode) { - if (end - current < 4) - return addError( - "Bad unicode escape sequence in string: four digits expected.", - token, - current); - unicode = 0; - for (int index = 0; index < 4; ++index) { - Char c = *current++; - unicode *= 16; - if (c >= '0' && c <= '9') - unicode += c - '0'; - else if (c >= 'a' && c <= 'f') - unicode += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - unicode += c - 'A' + 10; - else - return addError( - "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, - current); - } - return true; -} - -bool -Reader::addError(const std::string& message, Token& token, Location extra) { - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back(info); - return false; -} - -bool Reader::recoverFromError(TokenType skipUntilToken) { - int errorCount = int(errors_.size()); - Token skip; - for (;;) { - if (!readToken(skip)) - errors_.resize(errorCount); // discard errors caused by recovery - if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) - break; - } - errors_.resize(errorCount); - return false; -} - -bool Reader::addErrorAndRecover(const std::string& message, - Token& token, - TokenType skipUntilToken) { - addError(message, token); - return recoverFromError(skipUntilToken); -} - -Value& Reader::currentValue() { return *(nodes_.top()); } - -Reader::Char Reader::getNextChar() { - if (current_ == end_) - return 0; - return *current_++; -} - -void Reader::getLocationLineAndColumn(Location location, - int& line, - int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - -std::string Reader::getLocationLineAndColumn(Location location) const { - int line, column; - getLocationLineAndColumn(location, line, column); - char buffer[18 + 16 + 16 + 1]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) -#if defined(WINCE) - _snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); -#else - sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column); -#endif -#else - snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); -#endif - return buffer; -} - -// Deprecated. Preserved for backward compatibility -std::string Reader::getFormatedErrorMessages() const { - return getFormattedErrorMessages(); -} - -std::string Reader::getFormattedErrorMessages() const { - std::string formattedMessage; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; - formattedMessage += - "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if (error.extra_) - formattedMessage += - "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; - } - return formattedMessage; -} - -std::vector Reader::getStructuredErrors() const { - std::vector allErrors; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; - Reader::StructuredError structured; - structured.offset_start = error.token_.start_ - begin_; - structured.offset_limit = error.token_.end_ - begin_; - structured.message = error.message_; - allErrors.push_back(structured); - } - return allErrors; -} - -bool Reader::pushError(const Value& value, const std::string& message) { - size_t length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = end_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = 0; - errors_.push_back(info); - return true; -} - -bool Reader::pushError(const Value& value, const std::string& message, const Value& extra) { - size_t length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length - || extra.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = begin_ + extra.getOffsetStart(); - errors_.push_back(info); - return true; -} - -bool Reader::good() const { - return !errors_.size(); -} - -// exact copy of Features -class OurFeatures { -public: - static OurFeatures all(); - OurFeatures(); - bool allowComments_; - bool strictRoot_; - bool allowDroppedNullPlaceholders_; - bool allowNumericKeys_; - bool allowSingleQuotes_; - bool failIfExtra_; - bool rejectDupKeys_; - int stackLimit_; -}; // OurFeatures - -// exact copy of Implementation of class Features -// //////////////////////////////// - -OurFeatures::OurFeatures() - : allowComments_(true), strictRoot_(false) - , allowDroppedNullPlaceholders_(false), allowNumericKeys_(false) - , allowSingleQuotes_(false) - , failIfExtra_(false) -{ -} - -OurFeatures OurFeatures::all() { return OurFeatures(); } - -// Implementation of class Reader -// //////////////////////////////// - -// exact copy of Reader, renamed to OurReader -class OurReader { -public: - typedef char Char; - typedef const Char* Location; - struct StructuredError { - size_t offset_start; - size_t offset_limit; - std::string message; - }; - - OurReader(OurFeatures const& features); - bool parse(const char* beginDoc, - const char* endDoc, - Value& root, - bool collectComments = true); - std::string getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; - bool pushError(const Value& value, const std::string& message); - bool pushError(const Value& value, const std::string& message, const Value& extra); - bool good() const; - -private: - OurReader(OurReader const&); // no impl - void operator=(OurReader const&); // no impl - - enum TokenType { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo { - public: - Token token_; - std::string message_; - Location extra_; - }; - - typedef std::deque Errors; - - bool readToken(Token& token); - void skipSpaces(); - bool match(Location pattern, int patternLength); - bool readComment(); - bool readCStyleComment(); - bool readCppStyleComment(); - bool readString(); - bool readStringSingleQuote(); - void readNumber(); - bool readValue(); - bool readObject(Token& token); - bool readArray(Token& token); - bool decodeNumber(Token& token); - bool decodeNumber(Token& token, Value& decoded); - bool decodeString(Token& token); - bool decodeString(Token& token, std::string& decoded); - bool decodeDouble(Token& token); - bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode); - bool addError(const std::string& message, Token& token, Location extra = 0); - bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const std::string& message, - Token& token, - TokenType skipUntilToken); - void skipUntilSpace(); - Value& currentValue(); - Char getNextChar(); - void - getLocationLineAndColumn(Location location, int& line, int& column) const; - std::string getLocationLineAndColumn(Location location) const; - void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); - - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - std::string document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - std::string commentsBefore_; - int stackDepth_; - - OurFeatures const features_; - bool collectComments_; -}; // OurReader - -// complete copy of Read impl, for OurReader - -OurReader::OurReader(OurFeatures const& features) - : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), - lastValue_(), commentsBefore_(), features_(features), collectComments_() { -} - -bool OurReader::parse(const char* beginDoc, - const char* endDoc, - Value& root, - bool collectComments) { - if (!features_.allowComments_) { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; - commentsBefore_ = ""; - errors_.clear(); - while (!nodes_.empty()) - nodes_.pop(); - nodes_.push(&root); - - stackDepth_ = 0; - bool successful = readValue(); - Token token; - skipCommentTokens(token); - if (features_.failIfExtra_) { - if (token.type_ != tokenError && token.type_ != tokenEndOfStream) { - addError("Extra non-whitespace after JSON value.", token); - return false; - } - } - if (collectComments_ && !commentsBefore_.empty()) - root.setComment(commentsBefore_, commentAfter); - if (features_.strictRoot_) { - if (!root.isArray() && !root.isObject()) { - // Set error location to start of doc, ideally should be first token found - // in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( - "A valid JSON document must be either an array or an object value.", - token); - return false; - } - } - return successful; -} - -bool OurReader::readValue() { - if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); - ++stackDepth_; - Token token; - skipCommentTokens(token); - bool successful = true; - - if (collectComments_ && !commentsBefore_.empty()) { - currentValue().setComment(commentsBefore_, commentBefore); - commentsBefore_ = ""; - } - - switch (token.type_) { - case tokenObjectBegin: - successful = readObject(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenArrayBegin: - successful = readArray(token); - currentValue().setOffsetLimit(current_ - begin_); - break; - case tokenNumber: - successful = decodeNumber(token); - break; - case tokenString: - successful = decodeString(token); - break; - case tokenTrue: - { - Value v(true); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenFalse: - { - Value v(false); - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenNull: - { - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - } - break; - case tokenArraySeparator: - case tokenObjectEnd: - case tokenArrayEnd: - if (features_.allowDroppedNullPlaceholders_) { - // "Un-read" the current token and mark the current value as a null - // token. - current_--; - Value v; - currentValue().swapPayload(v); - currentValue().setOffsetStart(current_ - begin_ - 1); - currentValue().setOffsetLimit(current_ - begin_); - break; - } // else, fall through ... - default: - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return addError("Syntax error: value, object or array expected.", token); - } - - if (collectComments_) { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - --stackDepth_; - return successful; -} - -void OurReader::skipCommentTokens(Token& token) { - if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); - } -} - -bool OurReader::readToken(Token& token) { - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch (c) { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '\'': - if (features_.allowSingleQuotes_) { - token.type_ = tokenString; - ok = readStringSingleQuote(); - break; - } // else continue - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match("rue", 3); - break; - case 'f': - token.type_ = tokenFalse; - ok = match("alse", 4); - break; - case 'n': - token.type_ = tokenNull; - ok = match("ull", 3); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if (!ok) - token.type_ = tokenError; - token.end_ = current_; - return true; -} - -void OurReader::skipSpaces() { - while (current_ != end_) { - Char c = *current_; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - ++current_; - else - break; - } -} - -bool OurReader::match(Location pattern, int patternLength) { - if (end_ - current_ < patternLength) - return false; - int index = patternLength; - while (index--) - if (current_[index] != pattern[index]) - return false; - current_ += patternLength; - return true; -} - -bool OurReader::readComment() { - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if (c == '*') - successful = readCStyleComment(); - else if (c == '/') - successful = readCppStyleComment(); - if (!successful) - return false; - - if (collectComments_) { - CommentPlacement placement = commentBefore; - if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { - if (c != '*' || !containsNewLine(commentBegin, current_)) - placement = commentAfterOnSameLine; - } - - addComment(commentBegin, current_, placement); - } - return true; -} - -void -OurReader::addComment(Location begin, Location end, CommentPlacement placement) { - assert(collectComments_); - const std::string& normalized = normalizeEOL(begin, end); - if (placement == commentAfterOnSameLine) { - assert(lastValue_ != 0); - lastValue_->setComment(normalized, placement); - } else { - commentsBefore_ += normalized; - } -} - -bool OurReader::readCStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '*' && *current_ == '/') - break; - } - return getNextChar() == '/'; -} - -bool OurReader::readCppStyleComment() { - while (current_ != end_) { - Char c = getNextChar(); - if (c == '\n') - break; - if (c == '\r') { - // Consume DOS EOL. It will be normalized in addComment. - if (current_ != end_ && *current_ == '\n') - getNextChar(); - // Break on Moc OS 9 EOL. - break; - } - } - return true; -} - -void OurReader::readNumber() { - const char *p = current_; - char c = '0'; // stopgap for already consumed character - // integral part - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : 0; - // fractional part - if (c == '.') { - c = (current_ = p) < end_ ? *p++ : 0; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : 0; - } - // exponential part - if (c == 'e' || c == 'E') { - c = (current_ = p) < end_ ? *p++ : 0; - if (c == '+' || c == '-') - c = (current_ = p) < end_ ? *p++ : 0; - while (c >= '0' && c <= '9') - c = (current_ = p) < end_ ? *p++ : 0; - } -} -bool OurReader::readString() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '"') - break; - } - return c == '"'; -} - - -bool OurReader::readStringSingleQuote() { - Char c = 0; - while (current_ != end_) { - c = getNextChar(); - if (c == '\\') - getNextChar(); - else if (c == '\'') - break; - } - return c == '\''; -} - -bool OurReader::readObject(Token& tokenStart) { - Token tokenName; - std::string name; - Value init(objectValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; - if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object - return true; - name = ""; - if (tokenName.type_ == tokenString) { - if (!decodeString(tokenName, name)) - return recoverFromError(tokenObjectEnd); - } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { - Value numberName; - if (!decodeNumber(tokenName, numberName)) - return recoverFromError(tokenObjectEnd); - name = numberName.asString(); - } else { - break; - } - - Token colon; - if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { - return addErrorAndRecover( - "Missing ':' after object member name", colon, tokenObjectEnd); - } - if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30"); - if (features_.rejectDupKeys_ && currentValue().isMember(name)) { - std::string msg = "Duplicate key: '" + name + "'"; - return addErrorAndRecover( - msg, tokenName, tokenObjectEnd); - } - Value& value = currentValue()[name]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenObjectEnd); - - Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { - return addErrorAndRecover( - "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); - } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); - if (comma.type_ == tokenObjectEnd) - return true; - } - return addErrorAndRecover( - "Missing '}' or object member name", tokenName, tokenObjectEnd); -} - -bool OurReader::readArray(Token& tokenStart) { - Value init(arrayValue); - currentValue().swapPayload(init); - currentValue().setOffsetStart(tokenStart.start_ - begin_); - skipSpaces(); - if (*current_ == ']') // empty array - { - Token endArray; - readToken(endArray); - return true; - } - int index = 0; - for (;;) { - Value& value = currentValue()[index++]; - nodes_.push(&value); - bool ok = readValue(); - nodes_.pop(); - if (!ok) // error already set - return recoverFromError(tokenArrayEnd); - - Token token; - // Accept Comment after last item in the array. - ok = readToken(token); - while (token.type_ == tokenComment && ok) { - ok = readToken(token); - } - bool badTokenType = - (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); - if (!ok || badTokenType) { - return addErrorAndRecover( - "Missing ',' or ']' in array declaration", token, tokenArrayEnd); - } - if (token.type_ == tokenArrayEnd) - break; - } - return true; -} - -bool OurReader::decodeNumber(Token& token) { - Value decoded; - if (!decodeNumber(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeNumber(Token& token, Value& decoded) { - // Attempts to parse the number as an integer. If the number is - // larger than the maximum supported value of an integer then - // we decode the number as a double. - Location current = token.start_; - bool isNegative = *current == '-'; - if (isNegative) - ++current; - // TODO: Help the compiler do the div and mod at compile time or get rid of them. - Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(-Value::minLargestInt) - : Value::maxLargestUInt; - Value::LargestUInt threshold = maxIntegerValue / 10; - Value::LargestUInt value = 0; - while (current < token.end_) { - Char c = *current++; - if (c < '0' || c > '9') - return decodeDouble(token, decoded); - Value::UInt digit(c - '0'); - if (value >= threshold) { - // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, b) this is the last digit, and - // c) it's small enough to fit in that rounding delta, we're okay. - // Otherwise treat this number as a double to avoid overflow. - if (value > threshold || current != token.end_ || - digit > maxIntegerValue % 10) { - return decodeDouble(token, decoded); - } - } - value = value * 10 + digit; - } - if (isNegative) - decoded = -Value::LargestInt(value); - else if (value <= Value::LargestUInt(Value::maxInt)) - decoded = Value::LargestInt(value); - else - decoded = value; - return true; -} - -bool OurReader::decodeDouble(Token& token) { - Value decoded; - if (!decodeDouble(token, decoded)) - return false; - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeDouble(Token& token, Value& decoded) { - double value = 0; - const int bufferSize = 32; - int count; - int length = int(token.end_ - token.start_); - - // Sanity check to avoid buffer overflow exploits. - if (length < 0) { - return addError("Unable to parse token length", token); - } - - // Avoid using a string constant for the format control string given to - // sscanf, as this can cause hard to debug crashes on OS X. See here for more - // info: - // - // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html - char format[] = "%lf"; - - if (length <= bufferSize) { - Char buffer[bufferSize + 1]; - memcpy(buffer, token.start_, length); - buffer[length] = 0; - count = sscanf(buffer, format, &value); - } else { - std::string buffer(token.start_, token.end_); - count = sscanf(buffer.c_str(), format, &value); - } - - if (count != 1) - return addError("'" + std::string(token.start_, token.end_) + - "' is not a number.", - token); - decoded = value; - return true; -} - -bool OurReader::decodeString(Token& token) { - std::string decoded_string; - if (!decodeString(token, decoded_string)) - return false; - Value decoded(decoded_string); - currentValue().swapPayload(decoded); - currentValue().setOffsetStart(token.start_ - begin_); - currentValue().setOffsetLimit(token.end_ - begin_); - return true; -} - -bool OurReader::decodeString(Token& token, std::string& decoded) { - decoded.reserve(token.end_ - token.start_ - 2); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while (current != end) { - Char c = *current++; - if (c == '"') - break; - else if (c == '\\') { - if (current == end) - return addError("Empty escape sequence in string", token, current); - Char escape = *current++; - switch (escape) { - case '"': - decoded += '"'; - break; - case '/': - decoded += '/'; - break; - case '\\': - decoded += '\\'; - break; - case 'b': - decoded += '\b'; - break; - case 'f': - decoded += '\f'; - break; - case 'n': - decoded += '\n'; - break; - case 'r': - decoded += '\r'; - break; - case 't': - decoded += '\t'; - break; - case 'u': { - unsigned int unicode; - if (!decodeUnicodeCodePoint(token, current, end, unicode)) - return false; - decoded += codePointToUTF8(unicode); - } break; - default: - return addError("Bad escape sequence in string", token, current); - } - } else { - decoded += c; - } - } - return true; -} - -bool OurReader::decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode) { - - if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) { - // surrogate pairs - if (end - current < 6) - return addError( - "additional six characters expected to parse unicode surrogate pair.", - token, - current); - unsigned int surrogatePair; - if (*(current++) == '\\' && *(current++) == 'u') { - if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } else - return false; - } else - return addError("expecting another \\u token to begin the second half of " - "a unicode surrogate pair", - token, - current); - } - return true; -} - -bool OurReader::decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode) { - if (end - current < 4) - return addError( - "Bad unicode escape sequence in string: four digits expected.", - token, - current); - unicode = 0; - for (int index = 0; index < 4; ++index) { - Char c = *current++; - unicode *= 16; - if (c >= '0' && c <= '9') - unicode += c - '0'; - else if (c >= 'a' && c <= 'f') - unicode += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - unicode += c - 'A' + 10; - else - return addError( - "Bad unicode escape sequence in string: hexadecimal digit expected.", - token, - current); - } - return true; -} - -bool -OurReader::addError(const std::string& message, Token& token, Location extra) { - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back(info); - return false; -} - -bool OurReader::recoverFromError(TokenType skipUntilToken) { - int errorCount = int(errors_.size()); - Token skip; - for (;;) { - if (!readToken(skip)) - errors_.resize(errorCount); // discard errors caused by recovery - if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) - break; - } - errors_.resize(errorCount); - return false; -} - -bool OurReader::addErrorAndRecover(const std::string& message, - Token& token, - TokenType skipUntilToken) { - addError(message, token); - return recoverFromError(skipUntilToken); -} - -Value& OurReader::currentValue() { return *(nodes_.top()); } - -OurReader::Char OurReader::getNextChar() { - if (current_ == end_) - return 0; - return *current_++; -} - -void OurReader::getLocationLineAndColumn(Location location, - int& line, - int& column) const { - Location current = begin_; - Location lastLineStart = current; - line = 0; - while (current < location && current != end_) { - Char c = *current++; - if (c == '\r') { - if (*current == '\n') - ++current; - lastLineStart = current; - ++line; - } else if (c == '\n') { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - -std::string OurReader::getLocationLineAndColumn(Location location) const { - int line, column; - getLocationLineAndColumn(location, line, column); - char buffer[18 + 16 + 16 + 1]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) -#if defined(WINCE) - _snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); -#else - sprintf_s(buffer, sizeof(buffer), "Line %d, Column %d", line, column); -#endif -#else - snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); -#endif - return buffer; -} - -std::string OurReader::getFormattedErrorMessages() const { - std::string formattedMessage; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; - formattedMessage += - "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if (error.extra_) - formattedMessage += - "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; - } - return formattedMessage; -} - -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; - for (Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError) { - const ErrorInfo& error = *itError; - OurReader::StructuredError structured; - structured.offset_start = error.token_.start_ - begin_; - structured.offset_limit = error.token_.end_ - begin_; - structured.message = error.message_; - allErrors.push_back(structured); - } - return allErrors; -} - -bool OurReader::pushError(const Value& value, const std::string& message) { - size_t length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = end_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = 0; - errors_.push_back(info); - return true; -} - -bool OurReader::pushError(const Value& value, const std::string& message, const Value& extra) { - size_t length = end_ - begin_; - if(value.getOffsetStart() > length - || value.getOffsetLimit() > length - || extra.getOffsetLimit() > length) - return false; - Token token; - token.type_ = tokenError; - token.start_ = begin_ + value.getOffsetStart(); - token.end_ = begin_ + value.getOffsetLimit(); - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = begin_ + extra.getOffsetStart(); - errors_.push_back(info); - return true; -} - -bool OurReader::good() const { - return !errors_.size(); -} - - -class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; -public: - OurCharReader( - bool collectComments, - OurFeatures const& features) - : collectComments_(collectComments) - , reader_(features) - {} - virtual bool parse( - char const* beginDoc, char const* endDoc, - Value* root, std::string* errs) { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); - } - return ok; - } -}; - -CharReaderBuilder::CharReaderBuilder() -{ - setDefaults(&settings_); -} -CharReaderBuilder::~CharReaderBuilder() -{} -CharReader* CharReaderBuilder::newCharReader() const -{ - bool collectComments = settings_["collectComments"].asBool(); - OurFeatures features = OurFeatures::all(); - features.allowComments_ = settings_["allowComments"].asBool(); - features.strictRoot_ = settings_["strictRoot"].asBool(); - features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool(); - features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); - features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); - features.stackLimit_ = settings_["stackLimit"].asInt(); - features.failIfExtra_ = settings_["failIfExtra"].asBool(); - features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); - return new OurCharReader(collectComments, features); -} -static void getValidReaderKeys(std::set* valid_keys) -{ - valid_keys->clear(); - valid_keys->insert("collectComments"); - valid_keys->insert("allowComments"); - valid_keys->insert("strictRoot"); - valid_keys->insert("allowDroppedNullPlaceholders"); - valid_keys->insert("allowNumericKeys"); - valid_keys->insert("allowSingleQuotes"); - valid_keys->insert("stackLimit"); - valid_keys->insert("failIfExtra"); - valid_keys->insert("rejectDupKeys"); -} -bool CharReaderBuilder::validate(Json::Value* invalid) const -{ - Json::Value my_invalid; - if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL - Json::Value& inv = *invalid; - std::set valid_keys; - getValidReaderKeys(&valid_keys); - Value::Members keys = settings_.getMemberNames(); - size_t n = keys.size(); - for (size_t i = 0; i < n; ++i) { - std::string const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } - } - return 0u == inv.size(); -} -Value& CharReaderBuilder::operator[](std::string key) -{ - return settings_[key]; -} -// static -void CharReaderBuilder::strictMode(Json::Value* settings) -{ -//! [CharReaderBuilderStrictMode] - (*settings)["allowComments"] = false; - (*settings)["strictRoot"] = true; - (*settings)["allowDroppedNullPlaceholders"] = false; - (*settings)["allowNumericKeys"] = false; - (*settings)["allowSingleQuotes"] = false; - (*settings)["failIfExtra"] = true; - (*settings)["rejectDupKeys"] = true; -//! [CharReaderBuilderStrictMode] -} -// static -void CharReaderBuilder::setDefaults(Json::Value* settings) -{ -//! [CharReaderBuilderDefaults] - (*settings)["collectComments"] = true; - (*settings)["allowComments"] = true; - (*settings)["strictRoot"] = false; - (*settings)["allowDroppedNullPlaceholders"] = false; - (*settings)["allowNumericKeys"] = false; - (*settings)["allowSingleQuotes"] = false; - (*settings)["stackLimit"] = 1000; - (*settings)["failIfExtra"] = false; - (*settings)["rejectDupKeys"] = false; -//! [CharReaderBuilderDefaults] -} - -////////////////////////////////// -// global functions - -bool parseFromStream( - CharReader::Factory const& fact, std::istream& sin, - Value* root, std::string* errs) -{ - std::ostringstream ssin; - ssin << sin.rdbuf(); - std::string doc = ssin.str(); - char const* begin = doc.data(); - char const* end = begin + doc.size(); - // Note that we do not actually need a null-terminator. - CharReaderPtr const reader(fact.newCharReader()); - return reader->parse(begin, end, root, errs); -} - -std::istream& operator>>(std::istream& sin, Value& root) { - CharReaderBuilder b; - std::string errs; - bool ok = parseFromStream(b, sin, &root, &errs); - if (!ok) { - fprintf(stderr, - "Error from reader: %s", - errs.c_str()); - - throwRuntimeError("reader error"); - } - return sin; -} - -} // namespace Json diff --git a/3rdpart/jsoncpp/json_tool.h b/3rdpart/jsoncpp/json_tool.h deleted file mode 100644 index 0fc8b036..00000000 --- a/3rdpart/jsoncpp/json_tool.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED -#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED - -/* This header provides common string manipulation support, such as UTF-8, - * portable conversion from/to string... - * - * It is an internal header that must not be exposed. - */ - -namespace Json { - -/// Converts a unicode code-point to UTF-8. -static inline std::string codePointToUTF8(unsigned int cp) { - std::string result; - - // based on description from http://en.wikipedia.org/wiki/UTF-8 - - if (cp <= 0x7f) { - result.resize(1); - result[0] = static_cast(cp); - } else if (cp <= 0x7FF) { - result.resize(2); - result[1] = static_cast(0x80 | (0x3f & cp)); - result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); - } else if (cp <= 0xFFFF) { - result.resize(3); - result[2] = static_cast(0x80 | (0x3f & cp)); - result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); - } else if (cp <= 0x10FFFF) { - result.resize(4); - result[3] = static_cast(0x80 | (0x3f & cp)); - result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); - result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); - } - - return result; -} - -/// Returns true if ch is a control character (in range [1,31]). -static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; } - -enum { - /// Constant that specify the size of the buffer that must be passed to - /// uintToString. - uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 -}; - -// Defines a char buffer for use with uintToString(). -typedef char UIntToStringBuffer[uintToStringBufferSize]; - -/** Converts an unsigned integer to string. - * @param value Unsigned interger to convert to string - * @param current Input/Output string buffer. - * Must have at least uintToStringBufferSize chars free. - */ -static inline void uintToString(LargestUInt value, char*& current) { - *--current = 0; - do { - *--current = static_cast(value % 10U + static_cast('0')); - value /= 10; - } while (value != 0); -} - -/** Change ',' to '.' everywhere in buffer. - * - * We had a sophisticated way, but it did not work in WinCE. - * @see https://github.com/open-source-parsers/jsoncpp/pull/9 - */ -static inline void fixNumericLocale(char* begin, char* end) { - while (begin < end) { - if (*begin == ',') { - *begin = '.'; - } - ++begin; - } -} - -} // namespace Json { - -#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED diff --git a/3rdpart/jsoncpp/json_value.cpp b/3rdpart/jsoncpp/json_value.cpp deleted file mode 100644 index 5aee223d..00000000 --- a/3rdpart/jsoncpp/json_value.cpp +++ /dev/null @@ -1,1594 +0,0 @@ -// Copyright 2011 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -#include "../jsoncpp/assertions.h" -#include "../jsoncpp/value.h" -#include "../jsoncpp/writer.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include -#ifdef JSON_USE_CPPTL -#include -#endif -#include // size_t -#include // min() - -#define JSON_ASSERT_UNREACHABLE assert(false) - -namespace Json { - -// This is a walkaround to avoid the static initialization of Value::null. -// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of -// 8 (instead of 4) as a bit of future-proofing. -#if defined(__ARMEL__) -#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) -#else -#define ALIGNAS(byte_alignment) -#endif -static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; -const unsigned char& kNullRef = kNull[0]; -const Value& Value::null = reinterpret_cast(kNullRef); -const Value& Value::nullRef = null; - -const Int Value::minInt = Int(~(UInt(-1) / 2)); -const Int Value::maxInt = Int(UInt(-1) / 2); -const UInt Value::maxUInt = UInt(-1); -#if defined(JSON_HAS_INT64) -const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); -const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); -const UInt64 Value::maxUInt64 = UInt64(-1); -// The constant is hard-coded because some compiler have trouble -// converting Value::maxUInt64 to a double correctly (AIX/xlC). -// Assumes that UInt64 is a 64 bits integer. -static const double maxUInt64AsDouble = 18446744073709551615.0; -#endif // defined(JSON_HAS_INT64) -const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); -const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); -const LargestUInt Value::maxLargestUInt = LargestUInt(-1); - -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) -template -static inline bool InRange(double d, T min, U max) { - return d >= min && d <= max; -} -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) -static inline double integerToDouble(Json::UInt64 value) { - return static_cast(Int64(value / 2)) * 2.0 + Int64(value & 1); -} - -template static inline double integerToDouble(T value) { - return static_cast(value); -} - -template -static inline bool InRange(double d, T min, U max) { - return d >= integerToDouble(min) && d <= integerToDouble(max); -} -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - -/** Duplicates the specified string value. - * @param value Pointer to the string to duplicate. Must be zero-terminated if - * length is "unknown". - * @param length Length of the value. if equals to unknown, then it will be - * computed using strlen(value). - * @return Pointer on the duplicate instance of string. - */ -static inline char* duplicateStringValue(const char* value, size_t length) { - // Avoid an integer overflow in the call to malloc below by limiting length - // to a sane value. - if (length >= (size_t) Value::maxInt) - length = Value::maxInt - 1; - - char* newString = static_cast(malloc(length + 1)); - if (newString == NULL) { - throwRuntimeError("in Json::Value::duplicateStringValue(): " - "Failed to allocate string value buffer"); - } - memcpy(newString, value, length); - newString[length] = 0; - return newString; -} - -/* Record the length as a prefix. - */ -static inline char* duplicateAndPrefixStringValue(const char* value, - unsigned int length) { - // Avoid an integer overflow in the call to malloc below by limiting length - // to a sane value. - JSON_ASSERT_MESSAGE( - length <= (unsigned )Value::maxInt - sizeof(unsigned) - 1U, - "in Json::Value::duplicateAndPrefixStringValue(): " - "length too big for prefixing"); - unsigned actualLength = length + static_cast(sizeof(unsigned)) - + 1U; - char* newString = static_cast(malloc(actualLength)); - if (newString == 0) { - throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " - "Failed to allocate string value buffer"); - } - *reinterpret_cast(newString) = length; - memcpy(newString + sizeof(unsigned), value, length); - newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later - return newString; -} -inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, - unsigned* length, char const** value) { - if (!isPrefixed) { - *length = static_cast(strlen(prefixed)); - *value = prefixed; - } else { - *length = *reinterpret_cast(prefixed); - *value = prefixed + sizeof(unsigned); - } -} -/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue(). - */ -static inline void releaseStringValue(char* value) { - free(value); -} - -} // namespace Json - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ValueInternals... -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -#if !defined(JSON_IS_AMALGAMATION) - -#include "../jsoncpp/json_valueiterator.inl" -#endif // if !defined(JSON_IS_AMALGAMATION) - -namespace Json { - -Exception::Exception(std::string const& msg) : - msg_(msg) { -} -Exception::~Exception() throw () { -} -char const* Exception::what() const throw () { - return msg_.c_str(); -} -RuntimeError::RuntimeError(std::string const& msg) : - Exception(msg) { -} -LogicError::LogicError(std::string const& msg) : - Exception(msg) { -} -void throwRuntimeError(std::string const& msg) { - throw RuntimeError(msg); -} -void throwLogicError(std::string const& msg) { - throw LogicError(msg); -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CommentInfo -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -Value::CommentInfo::CommentInfo() : - comment_(0) { -} - -Value::CommentInfo::~CommentInfo() { - if (comment_) - releaseStringValue(comment_); -} - -void Value::CommentInfo::setComment(const char* text, size_t len) { - if (comment_) { - releaseStringValue(comment_); - comment_ = 0; - } - JSON_ASSERT(text != 0); - JSON_ASSERT_MESSAGE(text[0] == '\0' || text[0] == '/', - "in Json::Value::setComment(): Comments must start with /"); - // It seems that /**/ style comments are acceptable as well. - comment_ = duplicateStringValue(text, len); -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CZString -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -// Notes: policy_ indicates if the string was allocated when -// a string is stored. - -Value::CZString::CZString(ArrayIndex aindex) : - cstr_(0), index_(aindex) { -} - -Value::CZString::CZString(char const* str, unsigned ulength, - DuplicationPolicy allocate) : - cstr_(str) { - // allocate != duplicate - storage_.policy_ = allocate & 0x3; - storage_.length_ = ulength & 0x3FFFFFFF; -} - -Value::CZString::CZString(const CZString& other) : - cstr_( - other.storage_.policy_ != noDuplication && other.cstr_ != 0 ? - duplicateStringValue(other.cstr_, - other.storage_.length_) : - other.cstr_) { - storage_.policy_ = ( - other.cstr_ ? - (static_cast(other.storage_.policy_) - == noDuplication ? noDuplication : duplicate) : - static_cast(other.storage_.policy_)); - storage_.length_ = other.storage_.length_; -} - -Value::CZString::~CZString() { - if (cstr_ && storage_.policy_ == duplicate) - releaseStringValue(const_cast(cstr_)); -} - -void Value::CZString::swap(CZString& other) { - std::swap(cstr_, other.cstr_); - std::swap(index_, other.index_); -} - -Value::CZString& Value::CZString::operator=(CZString other) { - swap(other); - return *this; -} - -bool Value::CZString::operator<(const CZString& other) const { - if (!cstr_) - return index_ < other.index_; - //return strcmp(cstr_, other.cstr_) < 0; - // Assume both are strings. - unsigned this_len = this->storage_.length_; - unsigned other_len = other.storage_.length_; - unsigned min_len = std::min(this_len, other_len); - int comp = memcmp(this->cstr_, other.cstr_, min_len); - if (comp < 0) - return true; - if (comp > 0) - return false; - return (this_len < other_len); -} - -bool Value::CZString::operator==(const CZString& other) const { - if (!cstr_) - return index_ == other.index_; - //return strcmp(cstr_, other.cstr_) == 0; - // Assume both are strings. - unsigned this_len = this->storage_.length_; - unsigned other_len = other.storage_.length_; - if (this_len != other_len) - return false; - int comp = memcmp(this->cstr_, other.cstr_, this_len); - return comp == 0; -} - -ArrayIndex Value::CZString::index() const { - return index_; -} - -//const char* Value::CZString::c_str() const { return cstr_; } -const char* Value::CZString::data() const { - return cstr_; -} -unsigned Value::CZString::length() const { - return storage_.length_; -} -bool Value::CZString::isStaticString() const { - return storage_.policy_ == noDuplication; -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::Value -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/*! \internal Default constructor initialization must be equivalent to: - * memset( this, 0, sizeof(Value) ) - * This optimization is used in ValueInternalMap fast allocator. - */ -Value::Value(ValueType vtype) { - initBasic(vtype); - switch (vtype) { - case nullValue: - break; - case intValue: - case uintValue: - value_.int_ = 0; - break; - case realValue: - value_.real_ = 0.0; - break; - case stringValue: - value_.string_ = 0; - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(); - break; - case booleanValue: - value_.bool_ = false; - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - -Value::Value(Int value) { - initBasic(intValue); - value_.int_ = value; -} - -Value::Value(UInt value) { - initBasic(uintValue); - value_.uint_ = value; -} -#if defined(JSON_HAS_INT64) -Value::Value(Int64 value) { - initBasic(intValue); - value_.int_ = value; -} -Value::Value(UInt64 value) { - initBasic(uintValue); - value_.uint_ = value; -} -#endif // defined(JSON_HAS_INT64) - -Value::Value(double value) { - initBasic(realValue); - value_.real_ = value; -} - -Value::Value(const char* value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue(value, - static_cast(strlen(value))); -} - -Value::Value(const char* beginValue, const char* endValue) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue(beginValue, - static_cast(endValue - beginValue)); -} - -Value::Value(const std::string& value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue(value.data(), - static_cast(value.length())); -} - -Value::Value(const StaticString& value) { - initBasic(stringValue); - value_.string_ = const_cast(value.c_str()); -} - -#ifdef JSON_USE_CPPTL -Value::Value(const CppTL::ConstString& value) { - initBasic(stringValue, true); - value_.string_ = duplicateAndPrefixStringValue(value, static_cast(value.length())); -} -#endif - -Value::Value(bool value) { - initBasic(booleanValue); - value_.bool_ = value; -} - -Value::Value(Value const& other) : - type_(other.type_), allocated_(false), comments_(0), start_( - other.start_), limit_(other.limit_) { - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if (other.value_.string_ && other.allocated_) { - unsigned len; - char const* str; - decodePrefixedString(other.allocated_, other.value_.string_, &len, - &str); - value_.string_ = duplicateAndPrefixStringValue(str, len); - allocated_ = true; - } else { - value_.string_ = other.value_.string_; - allocated_ = false; - } - break; - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(*other.value_.map_); - break; - default: - JSON_ASSERT_UNREACHABLE; - } - if (other.comments_) { - comments_ = new CommentInfo[numberOfCommentPlacement]; - for (int comment = 0; comment < numberOfCommentPlacement; ++comment) { - const CommentInfo& otherComment = other.comments_[comment]; - if (otherComment.comment_) - comments_[comment].setComment(otherComment.comment_, - strlen(otherComment.comment_)); - } - } -} - -Value::~Value() { - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if (allocated_) - releaseStringValue(value_.string_); - break; - case arrayValue: - case objectValue: - delete value_.map_; - break; - default: - JSON_ASSERT_UNREACHABLE; - } - - if (comments_) - delete[] comments_; -} - -Value& Value::operator=(Value other) { - swap(other); - return *this; -} - -void Value::swapPayload(Value& other) { - ValueType temp = type_; - type_ = other.type_; - other.type_ = temp; - std::swap(value_, other.value_); - int temp2 = allocated_; - allocated_ = other.allocated_; - other.allocated_ = temp2 & 0x1; -} - -void Value::swap(Value& other) { - swapPayload(other); - std::swap(comments_, other.comments_); - std::swap(start_, other.start_); - std::swap(limit_, other.limit_); -} - -ValueType Value::type() const { - return type_; -} - -int Value::compare(const Value& other) const { - if (*this < other) - return -1; - if (*this > other) - return 1; - return 0; -} - -bool Value::operator<(const Value& other) const { - int typeDelta = type_ - other.type_; - if (typeDelta) - return typeDelta < 0 ? true : false; - switch (type_) { - case nullValue: - return false; - case intValue: - return value_.int_ < other.value_.int_; - case uintValue: - return value_.uint_ < other.value_.uint_; - case realValue: - return value_.real_ < other.value_.real_; - case booleanValue: - return value_.bool_ < other.value_.bool_; - case stringValue: { - if ((value_.string_ == 0) || (other.value_.string_ == 0)) { - if (other.value_.string_) - return true; - else - return false; - } - unsigned this_len; - unsigned other_len; - char const* this_str; - char const* other_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, - &this_str); - decodePrefixedString(other.allocated_, other.value_.string_, &other_len, - &other_str); - unsigned min_len = std::min(this_len, other_len); - int comp = memcmp(this_str, other_str, min_len); - if (comp < 0) - return true; - if (comp > 0) - return false; - return (this_len < other_len); - } - case arrayValue: - case objectValue: { - int delta = int(value_.map_->size() - other.value_.map_->size()); - if (delta) - return delta < 0; - return (*value_.map_) < (*other.value_.map_); - } - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable -} - -bool Value::operator<=(const Value& other) const { - return !(other < *this); -} - -bool Value::operator>=(const Value& other) const { - return !(*this < other); -} - -bool Value::operator>(const Value& other) const { - return other < *this; -} - -bool Value::operator==(const Value& other) const { - // if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; - if (type_ != temp) - return false; - switch (type_) { - case nullValue: - return true; - case intValue: - return value_.int_ == other.value_.int_; - case uintValue: - return value_.uint_ == other.value_.uint_; - case realValue: - return value_.real_ == other.value_.real_; - case booleanValue: - return value_.bool_ == other.value_.bool_; - case stringValue: { - if ((value_.string_ == 0) || (other.value_.string_ == 0)) { - return (value_.string_ == other.value_.string_); - } - unsigned this_len; - unsigned other_len; - char const* this_str; - char const* other_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, - &this_str); - decodePrefixedString(other.allocated_, other.value_.string_, &other_len, - &other_str); - if (this_len != other_len) - return false; - int comp = memcmp(this_str, other_str, this_len); - return comp == 0; - } - case arrayValue: - case objectValue: - return value_.map_->size() == other.value_.map_->size() - && (*value_.map_) == (*other.value_.map_); - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable -} - -bool Value::operator!=(const Value& other) const { - return !(*this == other); -} - -const char* Value::asCString() const { - JSON_ASSERT_MESSAGE(type_ == stringValue, - "in Json::Value::asCString(): requires stringValue"); - if (value_.string_ == 0) - return 0; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, - &this_str); - return this_str; -} - -bool Value::getString(char const** str, char const** cend) const { - if (type_ != stringValue) - return false; - if (value_.string_ == 0) - return false; - unsigned length; - decodePrefixedString(this->allocated_, this->value_.string_, &length, str); - *cend = *str + length; - return true; -} - -std::string Value::asString() const { - switch (type_) { - case nullValue: - return ""; - case stringValue: { - if (value_.string_ == 0) - return ""; - unsigned this_len; - char const* this_str; - decodePrefixedString(this->allocated_, this->value_.string_, &this_len, - &this_str); - return std::string(this_str, this_len); - } - case booleanValue: - return value_.bool_ ? "true" : "false"; - case intValue: - return valueToString(value_.int_); - case uintValue: - return valueToString(value_.uint_); - case realValue: - return valueToString(value_.real_); - default: { - JSON_FAIL_MESSAGE("Type is not convertible to string"); - return "Type is not convertible to string"; - } - } -} - -#ifdef JSON_USE_CPPTL -CppTL::ConstString Value::asConstString() const { - unsigned len; - char const* str; - decodePrefixedString(allocated_, value_.string_, - &len, &str); - return CppTL::ConstString(str, len); -} -#endif - -Value::Int Value::asInt() const { - switch (type_) { - case intValue: - JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range") - ; - return Int(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range") - ; - return Int(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), - "double out of Int range") - ; - return Int(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to Int."); - return false; -} - -Value::UInt Value::asUInt() const { - switch (type_) { - case intValue: - JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range") - ; - return UInt(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range") - ; - return UInt(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), - "double out of UInt range") - ; - return UInt(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to UInt."); - return false; -} - -#if defined(JSON_HAS_INT64) - -Value::Int64 Value::asInt64() const { - switch (type_) { - case intValue: - return Int64(value_.int_); - case uintValue: - JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range") - ; - return Int64(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), - "double out of Int64 range") - ; - return Int64(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to Int64."); - return false; -} - -Value::UInt64 Value::asUInt64() const { - switch (type_) { - case intValue: - JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range") - ; - return UInt64(value_.int_); - case uintValue: - return UInt64(value_.uint_); - case realValue: - JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), - "double out of UInt64 range") - ; - return UInt64(value_.real_); - case nullValue: - return 0; - case booleanValue: - return value_.bool_ ? 1 : 0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); - return false; -} -#endif // if defined(JSON_HAS_INT64) - -LargestInt Value::asLargestInt() const { -#if defined(JSON_NO_INT64) - return asInt(); -#else - return asInt64(); -#endif -} - -LargestUInt Value::asLargestUInt() const { -#if defined(JSON_NO_INT64) - return asUInt(); -#else - return asUInt64(); -#endif -} - -double Value::asDouble() const { - switch (type_) { - case intValue: - return static_cast(value_.int_); - case uintValue: -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast(value_.uint_); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return integerToDouble(value_.uint_); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return value_.real_; - case nullValue: - return 0.0; - case booleanValue: - return value_.bool_ ? 1.0 : 0.0; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to double."); - return false; -} - -float Value::asFloat() const { - switch (type_) { - case intValue: - return static_cast(value_.int_); - case uintValue: -#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return static_cast(value_.uint_); -#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - return integerToDouble(value_.uint_); -#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) - case realValue: - return static_cast(value_.real_); - case nullValue: - return 0.0; - case booleanValue: - return value_.bool_ ? 1.0f : 0.0f; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to float."); - return false; -} - -bool Value::asBool() const { - switch (type_) { - case booleanValue: - return value_.bool_; - case nullValue: - return false; - case intValue: - return value_.int_ ? true : false; - case uintValue: - return value_.uint_ ? true : false; - case realValue: - // This is kind of strange. Not recommended. - return (value_.real_ != 0.0) ? true : false; - default: - break; - } - JSON_FAIL_MESSAGE("Value is not convertible to bool."); - return false; -} - -bool Value::isConvertibleTo(ValueType other) const { - switch (other) { - case nullValue: - return (isNumeric() && asDouble() == 0.0) - || (type_ == booleanValue && value_.bool_ == false) - || (type_ == stringValue && asString() == "") - || (type_ == arrayValue && value_.map_->size() == 0) - || (type_ == objectValue && value_.map_->size() == 0) - || type_ == nullValue; - case intValue: - return isInt() - || (type_ == realValue && InRange(value_.real_, minInt, maxInt)) - || type_ == booleanValue || type_ == nullValue; - case uintValue: - return isUInt() - || (type_ == realValue && InRange(value_.real_, 0, maxUInt)) - || type_ == booleanValue || type_ == nullValue; - case realValue: - return isNumeric() || type_ == booleanValue || type_ == nullValue; - case booleanValue: - return isNumeric() || type_ == booleanValue || type_ == nullValue; - case stringValue: - return isNumeric() || type_ == booleanValue || type_ == stringValue - || type_ == nullValue; - case arrayValue: - return type_ == arrayValue || type_ == nullValue; - case objectValue: - return type_ == objectValue || type_ == nullValue; - } - JSON_ASSERT_UNREACHABLE; - return false; -} - -/// Number of values in array or object -ArrayIndex Value::size() const { - switch (type_) { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - case stringValue: - return 0; - case arrayValue: // size of the array is highest index + 1 - if (!value_.map_->empty()) { - ObjectValues::const_iterator itLast = value_.map_->end(); - --itLast; - return (*itLast).first.index() + 1; - } - return 0; - case objectValue: - return ArrayIndex(value_.map_->size()); - } - JSON_ASSERT_UNREACHABLE; - return 0; // unreachable; -} - -bool Value::empty() const { - if (isNull() || isArray() || isObject()) - return size() == 0u; - else - return false; -} - -bool Value::operator!() const { - return isNull(); -} - -void Value::clear() { - JSON_ASSERT_MESSAGE( - type_ == nullValue || type_ == arrayValue || type_ == objectValue, - "in Json::Value::clear(): requires complex value"); - start_ = 0; - limit_ = 0; - switch (type_) { - case arrayValue: - case objectValue: - value_.map_->clear(); - break; - default: - break; - } -} - -void Value::resize(ArrayIndex newSize) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, - "in Json::Value::resize(): requires arrayValue"); - if (type_ == nullValue) - *this = Value(arrayValue); - ArrayIndex oldSize = size(); - if (newSize == 0) - clear(); - else if (newSize > oldSize) - (*this)[newSize - 1]; - else { - for (ArrayIndex index = newSize; index < oldSize; ++index) { - value_.map_->erase(index); - } - assert(size() == newSize); - } -} - -Value& Value::operator[](ArrayIndex index) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, - "in Json::Value::operator[](ArrayIndex): requires arrayValue"); - if (type_ == nullValue) - *this = Value(arrayValue); - CZString key(index); - ObjectValues::iterator it = value_.map_->lower_bound(key); - if (it != value_.map_->end() && (*it).first == key) - return (*it).second; - - ObjectValues::value_type defaultValue(key, nullRef); - it = value_.map_->insert(it, defaultValue); - return (*it).second; -} - -Value& Value::operator[](int index) { - JSON_ASSERT_MESSAGE(index >= 0, - "in Json::Value::operator[](int index): index cannot be negative"); - return (*this)[ArrayIndex(index)]; -} - -const Value& Value::operator[](ArrayIndex index) const { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue, - "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); - if (type_ == nullValue) - return nullRef; - CZString key(index); - ObjectValues::const_iterator it = value_.map_->find(key); - if (it == value_.map_->end()) - return nullRef; - return (*it).second; -} - -const Value& Value::operator[](int index) const { - JSON_ASSERT_MESSAGE(index >= 0, - "in Json::Value::operator[](int index) const: index cannot be negative"); - return (*this)[ArrayIndex(index)]; -} - -void Value::initBasic(ValueType vtype, bool allocated) { - type_ = vtype; - allocated_ = allocated; - comments_ = 0; - start_ = 0; - limit_ = 0; -} - -// Access an object value by name, create a null member if it does not exist. -// @pre Type of '*this' is object or null. -// @param key is null-terminated. -Value& Value::resolveReference(const char* key) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, - "in Json::Value::resolveReference(): requires objectValue"); - if (type_ == nullValue) - *this = Value(objectValue); - CZString actualKey(key, static_cast(strlen(key)), - CZString::noDuplication); // NOTE! - ObjectValues::iterator it = value_.map_->lower_bound(actualKey); - if (it != value_.map_->end() && (*it).first == actualKey) - return (*it).second; - - ObjectValues::value_type defaultValue(actualKey, nullRef); - it = value_.map_->insert(it, defaultValue); - Value& value = (*it).second; - return value; -} - -// @param key is not null-terminated. -Value& Value::resolveReference(char const* key, char const* cend) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, - "in Json::Value::resolveReference(key, end): requires objectValue"); - if (type_ == nullValue) - *this = Value(objectValue); - CZString actualKey(key, static_cast(cend - key), - CZString::duplicateOnCopy); - ObjectValues::iterator it = value_.map_->lower_bound(actualKey); - if (it != value_.map_->end() && (*it).first == actualKey) - return (*it).second; - - ObjectValues::value_type defaultValue(actualKey, nullRef); - it = value_.map_->insert(it, defaultValue); - Value& value = (*it).second; - return value; -} - -Value Value::get(ArrayIndex index, const Value& defaultValue) const { - const Value* value = &((*this)[index]); - return value == &nullRef ? defaultValue : *value; -} - -bool Value::isValidIndex(ArrayIndex index) const { - return index < size(); -} - -Value const* Value::find(char const* key, char const* cend) const { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, - "in Json::Value::find(key, end, found): requires objectValue or nullValue"); - if (type_ == nullValue) - return NULL; - CZString actualKey(key, static_cast(cend - key), - CZString::noDuplication); - ObjectValues::const_iterator it = value_.map_->find(actualKey); - if (it == value_.map_->end()) - return NULL; - return &(*it).second; -} -const Value& Value::operator[](const char* key) const { - Value const* found = find(key, key + strlen(key)); - if (!found) - return nullRef; - return *found; -} -Value const& Value::operator[](std::string const& key) const { - Value const* found = find(key.data(), key.data() + key.length()); - if (!found) - return nullRef; - return *found; -} - -Value& Value::operator[](const char* key) { - return resolveReference(key, key + strlen(key)); -} - -Value& Value::operator[](const std::string& key) { - return resolveReference(key.data(), key.data() + key.length()); -} - -Value& Value::operator[](const StaticString& key) { - return resolveReference(key.c_str()); -} - -#ifdef JSON_USE_CPPTL -Value& Value::operator[](const CppTL::ConstString& key) { - return resolveReference(key.c_str(), key.end_c_str()); -} -Value const& Value::operator[](CppTL::ConstString const& key) const -{ - Value const* found = find(key.c_str(), key.end_c_str()); - if (!found) return nullRef; - return *found; -} -#endif - -Value& Value::append(const Value& value) { - return (*this)[size()] = value; -} - -Value Value::get(char const* key, char const* cend, - Value const& defaultValue) const { - Value const* found = find(key, cend); - return !found ? defaultValue : *found; -} -Value Value::get(char const* key, Value const& defaultValue) const { - return get(key, key + strlen(key), defaultValue); -} -Value Value::get(std::string const& key, Value const& defaultValue) const { - return get(key.data(), key.data() + key.length(), defaultValue); -} - -bool Value::removeMember(const char* key, const char* cend, Value* removed) { - if (type_ != objectValue) { - return false; - } - CZString actualKey(key, static_cast(cend - key), - CZString::noDuplication); - ObjectValues::iterator it = value_.map_->find(actualKey); - if (it == value_.map_->end()) - return false; - *removed = it->second; - value_.map_->erase(it); - return true; -} -bool Value::removeMember(const char* key, Value* removed) { - return removeMember(key, key + strlen(key), removed); -} -bool Value::removeMember(std::string const& key, Value* removed) { - return removeMember(key.data(), key.data() + key.length(), removed); -} -Value Value::removeMember(const char* key) { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, - "in Json::Value::removeMember(): requires objectValue"); - if (type_ == nullValue) - return nullRef; - - Value removed; // null - removeMember(key, key + strlen(key), &removed); - return removed; // still null if removeMember() did nothing -} -Value Value::removeMember(const std::string& key) { - return removeMember(key.c_str()); -} - -bool Value::removeIndex(ArrayIndex index, Value* removed) { - if (type_ != arrayValue) { - return false; - } - CZString key(index); - ObjectValues::iterator it = value_.map_->find(key); - if (it == value_.map_->end()) { - return false; - } - *removed = it->second; - ArrayIndex oldSize = size(); - // shift left all items left, into the place of the "removed" - for (ArrayIndex i = index; i < (oldSize - 1); ++i) { - CZString keey(i); - (*value_.map_)[keey] = (*this)[i + 1]; - } - // erase the last one ("leftover") - CZString keyLast(oldSize - 1); - ObjectValues::iterator itLast = value_.map_->find(keyLast); - value_.map_->erase(itLast); - return true; -} - -#ifdef JSON_USE_CPPTL -Value Value::get(const CppTL::ConstString& key, - const Value& defaultValue) const { - return get(key.c_str(), key.end_c_str(), defaultValue); -} -#endif - -bool Value::isMember(char const* key, char const* cend) const { - Value const* value = find(key, cend); - return NULL != value; -} -bool Value::isMember(char const* key) const { - return isMember(key, key + strlen(key)); -} -bool Value::isMember(std::string const& key) const { - return isMember(key.data(), key.data() + key.length()); -} - -#ifdef JSON_USE_CPPTL -bool Value::isMember(const CppTL::ConstString& key) const { - return isMember(key.c_str(), key.end_c_str()); -} -#endif - -Value::Members Value::getMemberNames() const { - JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue, - "in Json::Value::getMemberNames(), value must be objectValue"); - if (type_ == nullValue) - return Value::Members(); - Members members; - members.reserve(value_.map_->size()); - ObjectValues::const_iterator it = value_.map_->begin(); - ObjectValues::const_iterator itEnd = value_.map_->end(); - for (; it != itEnd; ++it) { - members.push_back( - std::string((*it).first.data(), (*it).first.length())); - } - return members; -} -// -//# ifdef JSON_USE_CPPTL -// EnumMemberNames -// Value::enumMemberNames() const -//{ -// if ( type_ == objectValue ) -// { -// return CppTL::Enum::any( CppTL::Enum::transform( -// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), -// MemberNamesTransform() ) ); -// } -// return EnumMemberNames(); -//} -// -// -// EnumValues -// Value::enumValues() const -//{ -// if ( type_ == objectValue || type_ == arrayValue ) -// return CppTL::Enum::anyValues( *(value_.map_), -// CppTL::Type() ); -// return EnumValues(); -//} -// -//# endif - -static bool IsIntegral(double d) { - double integral_part; - return modf(d, &integral_part) == 0.0; -} - -bool Value::isNull() const { - return type_ == nullValue; -} - -bool Value::isBool() const { - return type_ == booleanValue; -} - -bool Value::isInt() const { - switch (type_) { - case intValue: - return value_.int_ >= minInt && value_.int_ <= maxInt; - case uintValue: - return value_.uint_ <= UInt(maxInt); - case realValue: - return value_.real_ >= minInt && value_.real_ <= maxInt - && IsIntegral(value_.real_); - default: - break; - } - return false; -} - -bool Value::isUInt() const { - switch (type_) { - case intValue: - return value_.int_ >= 0 - && LargestUInt(value_.int_) <= LargestUInt(maxUInt); - case uintValue: - return value_.uint_ <= maxUInt; - case realValue: - return value_.real_ >= 0 && value_.real_ <= maxUInt - && IsIntegral(value_.real_); - default: - break; - } - return false; -} - -bool Value::isInt64() const { -#if defined(JSON_HAS_INT64) - switch (type_) { - case intValue: - return true; - case uintValue: - return value_.uint_ <= UInt64(maxInt64); - case realValue: - // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a - // double, so double(maxInt64) will be rounded up to 2^63. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) - && value_.real_ < double(maxInt64) && IsIntegral(value_.real_); - default: - break; - } -#endif // JSON_HAS_INT64 - return false; -} - -bool Value::isUInt64() const { -#if defined(JSON_HAS_INT64) - switch (type_) { - case intValue: - return value_.int_ >= 0; - case uintValue: - return true; - case realValue: - // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a - // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we - // require the value to be strictly less than the limit. - return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble - && IsIntegral(value_.real_); - default: - break; - } -#endif // JSON_HAS_INT64 - return false; -} - -bool Value::isIntegral() const { -#if defined(JSON_HAS_INT64) - return isInt64() || isUInt64(); -#else - return isInt() || isUInt(); -#endif -} - -bool Value::isDouble() const { - return type_ == realValue || isIntegral(); -} - -bool Value::isNumeric() const { - return isIntegral() || isDouble(); -} - -bool Value::isString() const { - return type_ == stringValue; -} - -bool Value::isArray() const { - return type_ == arrayValue; -} - -bool Value::isObject() const { - return type_ == objectValue; -} - -void Value::setComment(const char* comment, size_t len, - CommentPlacement placement) { - if (!comments_) - comments_ = new CommentInfo[numberOfCommentPlacement]; - if ((len > 0) && (comment[len - 1] == '\n')) { - // Always discard trailing newline, to aid indentation. - len -= 1; - } - comments_[placement].setComment(comment, len); -} - -void Value::setComment(const char* comment, CommentPlacement placement) { - setComment(comment, strlen(comment), placement); -} - -void Value::setComment(const std::string& comment, CommentPlacement placement) { - setComment(comment.c_str(), comment.length(), placement); -} - -bool Value::hasComment(CommentPlacement placement) const { - return comments_ != 0 && comments_[placement].comment_ != 0; -} - -std::string Value::getComment(CommentPlacement placement) const { - if (hasComment(placement)) - return comments_[placement].comment_; - return ""; -} - -void Value::setOffsetStart(size_t start) { - start_ = start; -} - -void Value::setOffsetLimit(size_t limit) { - limit_ = limit; -} - -size_t Value::getOffsetStart() const { - return start_; -} - -size_t Value::getOffsetLimit() const { - return limit_; -} - -std::string Value::toStyledString() const { - StyledWriter writer; - return writer.write(*this); -} - -Value::const_iterator Value::begin() const { - switch (type_) { - case arrayValue: - case objectValue: - if (value_.map_) - return const_iterator(value_.map_->begin()); - break; - default: - break; - } - return const_iterator(); -} - -Value::const_iterator Value::end() const { - switch (type_) { - case arrayValue: - case objectValue: - if (value_.map_) - return const_iterator(value_.map_->end()); - break; - default: - break; - } - return const_iterator(); -} - -Value::iterator Value::begin() { - switch (type_) { - case arrayValue: - case objectValue: - if (value_.map_) - return iterator(value_.map_->begin()); - break; - default: - break; - } - return iterator(); -} - -Value::iterator Value::end() { - switch (type_) { - case arrayValue: - case objectValue: - if (value_.map_) - return iterator(value_.map_->end()); - break; - default: - break; - } - return iterator(); -} - -// class PathArgument -// ////////////////////////////////////////////////////////////////// - -PathArgument::PathArgument() : - key_(), index_(), kind_(kindNone) { -} - -PathArgument::PathArgument(ArrayIndex index) : - key_(), index_(index), kind_(kindIndex) { -} - -PathArgument::PathArgument(const char* key) : - key_(key), index_(), kind_(kindKey) { -} - -PathArgument::PathArgument(const std::string& key) : - key_(key.c_str()), index_(), kind_(kindKey) { -} - -// class Path -// ////////////////////////////////////////////////////////////////// - -Path::Path(const std::string& path, const PathArgument& a1, - const PathArgument& a2, const PathArgument& a3, const PathArgument& a4, - const PathArgument& a5) { - InArgs in; - in.push_back(&a1); - in.push_back(&a2); - in.push_back(&a3); - in.push_back(&a4); - in.push_back(&a5); - makePath(path, in); -} - -void Path::makePath(const std::string& path, const InArgs& in) { - const char* current = path.c_str(); - const char* end = current + path.length(); - InArgs::const_iterator itInArg = in.begin(); - while (current != end) { - if (*current == '[') { - ++current; - if (*current == '%') - addPathInArg(path, in, itInArg, PathArgument::kindIndex); - else { - ArrayIndex index = 0; - for (; current != end && *current >= '0' && *current <= '9'; - ++current) - index = index * 10 + ArrayIndex(*current - '0'); - args_.push_back(index); - } - if (current == end || *current++ != ']') - invalidPath(path, int(current - path.c_str())); - } else if (*current == '%') { - addPathInArg(path, in, itInArg, PathArgument::kindKey); - ++current; - } else if (*current == '.') { - ++current; - } else { - const char* beginName = current; - while (current != end && !strchr("[.", *current)) - ++current; - args_.push_back(std::string(beginName, current)); - } - } -} - -void Path::addPathInArg(const std::string& /*path*/, const InArgs& in, - InArgs::const_iterator& itInArg, PathArgument::Kind kind) { - if (itInArg == in.end()) { - // Error: missing argument %d - } else if ((*itInArg)->kind_ != kind) { - // Error: bad argument type - } else { - args_.push_back(**itInArg); - } -} - -void Path::invalidPath(const std::string& /*path*/, int /*location*/) { - // Error: invalid path. -} - -const Value& Path::resolve(const Value& root) const { - const Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray() || !node->isValidIndex(arg.index_)) { - // Error: unable to resolve path (array value expected at position... - } - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) { - // Error: unable to resolve path (object value expected at position...) - } - node = &((*node)[arg.key_]); - if (node == &Value::nullRef) { - // Error: unable to resolve path (object has no member named '' at - // position...) - } - } - } - return *node; -} - -Value Path::resolve(const Value& root, const Value& defaultValue) const { - const Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray() || !node->isValidIndex(arg.index_)) - return defaultValue; - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) - return defaultValue; - node = &((*node)[arg.key_]); - if (node == &Value::nullRef) - return defaultValue; - } - } - return *node; -} - -Value& Path::make(Value& root) const { - Value* node = &root; - for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) { - const PathArgument& arg = *it; - if (arg.kind_ == PathArgument::kindIndex) { - if (!node->isArray()) { - // Error: node is not an array at position ... - } - node = &((*node)[arg.index_]); - } else if (arg.kind_ == PathArgument::kindKey) { - if (!node->isObject()) { - // Error: node is not an object at position... - } - node = &((*node)[arg.key_]); - } - } - return *node; -} - -} // namespace Json diff --git a/3rdpart/jsoncpp/json_valueiterator.inl b/3rdpart/jsoncpp/json_valueiterator.inl deleted file mode 100644 index b3bbc354..00000000 --- a/3rdpart/jsoncpp/json_valueiterator.inl +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -// included by json_value.cpp - -namespace Json { - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIteratorBase -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIteratorBase::ValueIteratorBase() - : current_(), isNull_(true) { -} - -ValueIteratorBase::ValueIteratorBase( - const Value::ObjectValues::iterator& current) - : current_(current), isNull_(false) {} - -Value& ValueIteratorBase::deref() const { - return current_->second; -} - -void ValueIteratorBase::increment() { - ++current_; -} - -void ValueIteratorBase::decrement() { - --current_; -} - -ValueIteratorBase::difference_type -ValueIteratorBase::computeDistance(const SelfType& other) const { -#ifdef JSON_USE_CPPTL_SMALLMAP - return other.current_ - current_; -#else - // Iterator for null value are initialized using the default - // constructor, which initialize current_ to the default - // std::map::iterator. As begin() and end() are two instance - // of the default std::map::iterator, they can not be compared. - // To allow this, we handle this comparison specifically. - if (isNull_ && other.isNull_) { - return 0; - } - - // Usage of std::distance is not portable (does not compile with Sun Studio 12 - // RogueWave STL, - // which is the one used by default). - // Using a portable hand-made version for non random iterator instead: - // return difference_type( std::distance( current_, other.current_ ) ); - difference_type myDistance = 0; - for (Value::ObjectValues::iterator it = current_; it != other.current_; - ++it) { - ++myDistance; - } - return myDistance; -#endif -} - -bool ValueIteratorBase::isEqual(const SelfType& other) const { - if (isNull_) { - return other.isNull_; - } - return current_ == other.current_; -} - -void ValueIteratorBase::copy(const SelfType& other) { - current_ = other.current_; - isNull_ = other.isNull_; -} - -Value ValueIteratorBase::key() const { - const Value::CZString czstring = (*current_).first; - if (czstring.data()) { - if (czstring.isStaticString()) - return Value(StaticString(czstring.data())); - return Value(czstring.data(), czstring.data() + czstring.length()); - } - return Value(czstring.index()); -} - -UInt ValueIteratorBase::index() const { - const Value::CZString czstring = (*current_).first; - if (!czstring.data()) - return czstring.index(); - return Value::UInt(-1); -} - -std::string ValueIteratorBase::name() const { - char const* keey; - char const* end; - keey = memberName(&end); - if (!keey) return std::string(); - return std::string(keey, end); -} - -char const* ValueIteratorBase::memberName() const { - const char* cname = (*current_).first.data(); - return cname ? cname : ""; -} - -char const* ValueIteratorBase::memberName(char const** end) const { - const char* cname = (*current_).first.data(); - if (!cname) { - *end = NULL; - return NULL; - } - *end = cname + (*current_).first.length(); - return cname; -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueConstIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueConstIterator::ValueConstIterator() {} - -ValueConstIterator::ValueConstIterator( - const Value::ObjectValues::iterator& current) - : ValueIteratorBase(current) {} - -ValueConstIterator& ValueConstIterator:: -operator=(const ValueIteratorBase& other) { - copy(other); - return *this; -} - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIterator::ValueIterator() {} - -ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) - : ValueIteratorBase(current) {} - -ValueIterator::ValueIterator(const ValueConstIterator& other) - : ValueIteratorBase(other) {} - -ValueIterator::ValueIterator(const ValueIterator& other) - : ValueIteratorBase(other) {} - -ValueIterator& ValueIterator::operator=(const SelfType& other) { - copy(other); - return *this; -} - -} // namespace Json diff --git a/3rdpart/jsoncpp/json_writer.cpp b/3rdpart/jsoncpp/json_writer.cpp deleted file mode 100644 index 514525e2..00000000 --- a/3rdpart/jsoncpp/json_writer.cpp +++ /dev/null @@ -1,1182 +0,0 @@ -// Copyright 2011 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#if !defined(JSON_IS_AMALGAMATION) -#include "../jsoncpp/writer.h" -#include "../jsoncpp/json_tool.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0 -#include -#define isfinite _finite -#elif defined(__sun) && defined(__SVR4) //Solaris -#include -#define isfinite finite -#else -#include -#define isfinite std::isfinite -#endif - -#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below -#define snprintf _snprintf -#elif defined(__ANDROID__) -#define snprintf snprintf -#elif __cplusplus >= 201103L -#define snprintf std::snprintf -#endif - -#if defined(__BORLANDC__) -#include -#define isfinite _finite -#define snprintf _snprintf -#endif - -#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 -// Disable warning about strdup being deprecated. -#pragma warning(disable : 4996) -#endif - -namespace Json { - -#if __cplusplus >= 201103L -typedef std::unique_ptr StreamWriterPtr; -#else -typedef std::auto_ptr StreamWriterPtr; -#endif - -static bool containsControlCharacter(const char* str) { - while (*str) { - if (isControlCharacter(*(str++))) - return true; - } - return false; -} - -static bool containsControlCharacter0(const char* str, unsigned len) { - char const* end = str + len; - while (end != str) { - if (isControlCharacter(*str) || 0==*str) - return true; - ++str; - } - return false; -} - -std::string valueToString(LargestInt value) { - UIntToStringBuffer buffer; - char* current = buffer + sizeof(buffer); - if (value == Value::minLargestInt) { - uintToString(LargestUInt(Value::maxLargestInt) + 1, current); - *--current = '-'; - } else if (value < 0) { - uintToString(LargestUInt(-value), current); - *--current = '-'; - } else { - uintToString(LargestUInt(value), current); - } - assert(current >= buffer); - return current; -} - -std::string valueToString(LargestUInt value) { - UIntToStringBuffer buffer; - char* current = buffer + sizeof(buffer); - uintToString(value, current); - assert(current >= buffer); - return current; -} - -#if defined(JSON_HAS_INT64) - -std::string valueToString(Int value) { - return valueToString(LargestInt(value)); -} - -std::string valueToString(UInt value) { - return valueToString(LargestUInt(value)); -} - -#endif // # if defined(JSON_HAS_INT64) - -std::string valueToString(double value) { - // Allocate a buffer that is more than large enough to store the 16 digits of - // precision requested below. - char buffer[32]; - int len = -1; - -// Print into the buffer. We need not request the alternative representation -// that always has a decimal point because JSON doesn't distingish the -// concepts of reals and integers. -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with - // visual studio 2005 to - // avoid warning. -#if defined(WINCE) - len = _snprintf(buffer, sizeof(buffer), "%.17g", value); -#else - len = sprintf_s(buffer, sizeof(buffer), "%.17g", value); -#endif -#else - if (isfinite(value)) { - len = snprintf(buffer, sizeof(buffer), "%.17g", value); - } else { - // IEEE standard states that NaN values will not compare to themselves - if (value != value) { - len = snprintf(buffer, sizeof(buffer), "null"); - } else if (value < 0) { - len = snprintf(buffer, sizeof(buffer), "-1e+9999"); - } else { - len = snprintf(buffer, sizeof(buffer), "1e+9999"); - } - // For those, we do not need to call fixNumLoc, but it is fast. - } -#endif - assert(len >= 0); - fixNumericLocale(buffer, buffer + len); - return buffer; -} - -std::string valueToString(bool value) { return value ? "true" : "false"; } - -std::string valueToQuotedString(const char* value) { - if (value == NULL) - return ""; - // Not sure how to handle unicode... - if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && - !containsControlCharacter(value)) - return std::string("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to std::string is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - std::string::size_type maxsize = - strlen(value) * 2 + 3; // allescaped+quotes+NULL - std::string result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - for (const char* c = value; *c != 0; ++c) { - switch (*c) { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - // case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something. - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - result += oss.str(); - } else { - result += *c; - } - break; - } - } - result += "\""; - return result; -} - -// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp -static char const* strnpbrk(char const* s, char const* accept, size_t n) { - assert((s || !n) && accept); - - char const* const end = s + n; - for (char const* cur = s; cur < end; ++cur) { - int const c = *cur; - for (char const* a = accept; *a; ++a) { - if (*a == c) { - return cur; - } - } - } - return NULL; -} -static std::string valueToQuotedStringN(const char* value, unsigned length) { - if (value == NULL) - return ""; - // Not sure how to handle unicode... - if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL && - !containsControlCharacter0(value, length)) - return std::string("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to std::string is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - std::string::size_type maxsize = - length * 2 + 3; // allescaped+quotes+NULL - std::string result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - char const* end = value + length; - for (const char* c = value; c != end; ++c) { - switch (*c) { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - // case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something.) - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - result += oss.str(); - } else { - result += *c; - } - break; - } - } - result += "\""; - return result; -} - -// Class Writer -// ////////////////////////////////////////////////////////////////// -Writer::~Writer() {} - -// Class FastWriter -// ////////////////////////////////////////////////////////////////// - -FastWriter::FastWriter() - : yamlCompatiblityEnabled_(false), dropNullPlaceholders_(false), - omitEndingLineFeed_(false) {} - -void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; } - -void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } - -void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } - -std::string FastWriter::write(const Value& root) { - document_ = ""; - writeValue(root); - if (!omitEndingLineFeed_) - document_ += "\n"; - return document_; -} - -void FastWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - if (!dropNullPlaceholders_) - document_ += "null"; - break; - case intValue: - document_ += valueToString(value.asLargestInt()); - break; - case uintValue: - document_ += valueToString(value.asLargestUInt()); - break; - case realValue: - document_ += valueToString(value.asDouble()); - break; - case stringValue: - { - // Is NULL possible for value.string_? - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) document_ += valueToQuotedStringN(str, static_cast(end-str)); - break; - } - case booleanValue: - document_ += valueToString(value.asBool()); - break; - case arrayValue: { - document_ += '['; - int size = value.size(); - for (int index = 0; index < size; ++index) { - if (index > 0) - document_ += ','; - writeValue(value[index]); - } - document_ += ']'; - } break; - case objectValue: { - Value::Members members(value.getMemberNames()); - document_ += '{'; - for (Value::Members::iterator it = members.begin(); it != members.end(); - ++it) { - const std::string& name = *it; - if (it != members.begin()) - document_ += ','; - document_ += valueToQuotedStringN(name.data(), static_cast(name.length())); - document_ += yamlCompatiblityEnabled_ ? ": " : ":"; - writeValue(value[name]); - } - document_ += '}'; - } break; - } -} - -// Class StyledWriter -// ////////////////////////////////////////////////////////////////// - -StyledWriter::StyledWriter() - : rightMargin_(74), indentSize_(3), addChildValues_() {} - -std::string StyledWriter::write(const Value& root) { - document_ = ""; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue(root); - writeValue(root); - writeCommentAfterValueOnSameLine(root); - document_ += "\n"; - return document_; -} - -void StyledWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - pushValue("null"); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: - { - // Is NULL possible for value.string_? - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - Value::Members::iterator it = members.begin(); - for (;;) { - const std::string& name = *it; - const Value& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); - document_ += " : "; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - document_ += ','; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void StyledWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isArrayMultiLine = isMultineArray(value); - if (isArrayMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - const Value& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - writeIndent(); - writeValue(childValue); - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - document_ += ','; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - document_ += "[ "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - document_ += ", "; - document_ += childValues_[index]; - } - document_ += " ]"; - } - } -} - -bool StyledWriter::isMultineArray(const Value& value) { - int size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (int index = 0; index < size && !isMultiLine; ++index) { - const Value& childValue = value[index]; - isMultiLine = - isMultiLine || ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (int index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += int(childValues_[index].length()); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - -void StyledWriter::pushValue(const std::string& value) { - if (addChildValues_) - childValues_.push_back(value); - else - document_ += value; -} - -void StyledWriter::writeIndent() { - if (!document_.empty()) { - char last = document_[document_.length() - 1]; - if (last == ' ') // already indented - return; - if (last != '\n') // Comments may add new-line - document_ += '\n'; - } - document_ += indentString_; -} - -void StyledWriter::writeWithIndent(const std::string& value) { - writeIndent(); - document_ += value; -} - -void StyledWriter::indent() { indentString_ += std::string(indentSize_, ' '); } - -void StyledWriter::unindent() { - assert(int(indentString_.size()) >= indentSize_); - indentString_.resize(indentString_.size() - indentSize_); -} - -void StyledWriter::writeCommentBeforeValue(const Value& root) { - if (!root.hasComment(commentBefore)) - return; - - document_ += "\n"; - writeIndent(); - const std::string& comment = root.getComment(commentBefore); - std::string::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - document_ += *iter; - if (*iter == '\n' && - (iter != comment.end() && *(iter + 1) == '/')) - writeIndent(); - ++iter; - } - - // Comments are stripped of trailing newlines, so add one here - document_ += "\n"; -} - -void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { - if (root.hasComment(commentAfterOnSameLine)) - document_ += " " + root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { - document_ += "\n"; - document_ += root.getComment(commentAfter); - document_ += "\n"; - } -} - -bool StyledWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); -} - -// Class StyledStreamWriter -// ////////////////////////////////////////////////////////////////// - -StyledStreamWriter::StyledStreamWriter(std::string indentation) - : document_(NULL), rightMargin_(74), indentation_(indentation), - addChildValues_() {} - -void StyledStreamWriter::write(std::ostream& out, const Value& root) { - document_ = &out; - addChildValues_ = false; - indentString_ = ""; - indented_ = true; - writeCommentBeforeValue(root); - if (!indented_) writeIndent(); - indented_ = true; - writeValue(root); - writeCommentAfterValueOnSameLine(root); - *document_ << "\n"; - document_ = NULL; // Forget the stream, for safety. -} - -void StyledStreamWriter::writeValue(const Value& value) { - switch (value.type()) { - case nullValue: - pushValue("null"); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: - { - // Is NULL possible for value.string_? - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - Value::Members::iterator it = members.begin(); - for (;;) { - const std::string& name = *it; - const Value& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); - *document_ << " : "; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void StyledStreamWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isArrayMultiLine = isMultineArray(value); - if (isArrayMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - const Value& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - if (!indented_) writeIndent(); - indented_ = true; - writeValue(childValue); - indented_ = false; - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - *document_ << "[ "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - *document_ << ", "; - *document_ << childValues_[index]; - } - *document_ << " ]"; - } - } -} - -bool StyledStreamWriter::isMultineArray(const Value& value) { - int size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (int index = 0; index < size && !isMultiLine; ++index) { - const Value& childValue = value[index]; - isMultiLine = - isMultiLine || ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (int index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += int(childValues_[index].length()); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - -void StyledStreamWriter::pushValue(const std::string& value) { - if (addChildValues_) - childValues_.push_back(value); - else - *document_ << value; -} - -void StyledStreamWriter::writeIndent() { - // blep intended this to look at the so-far-written string - // to determine whether we are already indented, but - // with a stream we cannot do that. So we rely on some saved state. - // The caller checks indented_. - *document_ << '\n' << indentString_; -} - -void StyledStreamWriter::writeWithIndent(const std::string& value) { - if (!indented_) writeIndent(); - *document_ << value; - indented_ = false; -} - -void StyledStreamWriter::indent() { indentString_ += indentation_; } - -void StyledStreamWriter::unindent() { - assert(indentString_.size() >= indentation_.size()); - indentString_.resize(indentString_.size() - indentation_.size()); -} - -void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { - if (!root.hasComment(commentBefore)) - return; - - if (!indented_) writeIndent(); - const std::string& comment = root.getComment(commentBefore); - std::string::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - *document_ << *iter; - if (*iter == '\n' && - (iter != comment.end() && *(iter + 1) == '/')) - // writeIndent(); // would include newline - *document_ << indentString_; - ++iter; - } - indented_ = false; -} - -void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { - if (root.hasComment(commentAfterOnSameLine)) - *document_ << ' ' << root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { - writeIndent(); - *document_ << root.getComment(commentAfter); - } - indented_ = false; -} - -bool StyledStreamWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); -} - -////////////////////////// -// BuiltStyledStreamWriter - -/// Scoped enums are not available until C++11. -struct CommentStyle { - /// Decide whether to write comments. - enum Enum { - None, ///< Drop all comments. - Most, ///< Recover odd behavior of previous versions (not implemented yet). - All ///< Keep all comments. - }; -}; - -struct BuiltStyledStreamWriter : public StreamWriter -{ - BuiltStyledStreamWriter( - std::string const& indentation, - CommentStyle::Enum cs, - std::string const& colonSymbol, - std::string const& nullSymbol, - std::string const& endingLineFeedSymbol); - virtual int write(Value const& root, std::ostream* sout); -private: - void writeValue(Value const& value); - void writeArrayValue(Value const& value); - bool isMultineArray(Value const& value); - void pushValue(std::string const& value); - void writeIndent(); - void writeWithIndent(std::string const& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(Value const& root); - void writeCommentAfterValueOnSameLine(Value const& root); - static bool hasCommentForValue(const Value& value); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::string indentString_; - int rightMargin_; - std::string indentation_; - CommentStyle::Enum cs_; - std::string colonSymbol_; - std::string nullSymbol_; - std::string endingLineFeedSymbol_; - bool addChildValues_ : 1; - bool indented_ : 1; -}; -BuiltStyledStreamWriter::BuiltStyledStreamWriter( - std::string const& indentation, - CommentStyle::Enum cs, - std::string const& colonSymbol, - std::string const& nullSymbol, - std::string const& endingLineFeedSymbol) - : rightMargin_(74) - , indentation_(indentation) - , cs_(cs) - , colonSymbol_(colonSymbol) - , nullSymbol_(nullSymbol) - , endingLineFeedSymbol_(endingLineFeedSymbol) - , addChildValues_(false) - , indented_(false) -{ -} -int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout) -{ - sout_ = sout; - addChildValues_ = false; - indented_ = true; - indentString_ = ""; - writeCommentBeforeValue(root); - if (!indented_) writeIndent(); - indented_ = true; - writeValue(root); - writeCommentAfterValueOnSameLine(root); - *sout_ << endingLineFeedSymbol_; - sout_ = NULL; - return 0; -} -void BuiltStyledStreamWriter::writeValue(Value const& value) { - switch (value.type()) { - case nullValue: - pushValue(nullSymbol_); - break; - case intValue: - pushValue(valueToString(value.asLargestInt())); - break; - case uintValue: - pushValue(valueToString(value.asLargestUInt())); - break; - case realValue: - pushValue(valueToString(value.asDouble())); - break; - case stringValue: - { - // Is NULL is possible for value.string_? - char const* str; - char const* end; - bool ok = value.getString(&str, &end); - if (ok) pushValue(valueToQuotedStringN(str, static_cast(end-str))); - else pushValue(""); - break; - } - case booleanValue: - pushValue(valueToString(value.asBool())); - break; - case arrayValue: - writeArrayValue(value); - break; - case objectValue: { - Value::Members members(value.getMemberNames()); - if (members.empty()) - pushValue("{}"); - else { - writeWithIndent("{"); - indent(); - Value::Members::iterator it = members.begin(); - for (;;) { - std::string const& name = *it; - Value const& childValue = value[name]; - writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedStringN(name.data(), static_cast(name.length()))); - *sout_ << colonSymbol_; - writeValue(childValue); - if (++it == members.end()) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *sout_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("}"); - } - } break; - } -} - -void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { - unsigned size = value.size(); - if (size == 0) - pushValue("[]"); - else { - bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value); - if (isMultiLine) { - writeWithIndent("["); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index = 0; - for (;;) { - Value const& childValue = value[index]; - writeCommentBeforeValue(childValue); - if (hasChildValue) - writeWithIndent(childValues_[index]); - else { - if (!indented_) writeIndent(); - indented_ = true; - writeValue(childValue); - indented_ = false; - } - if (++index == size) { - writeCommentAfterValueOnSameLine(childValue); - break; - } - *sout_ << ","; - writeCommentAfterValueOnSameLine(childValue); - } - unindent(); - writeWithIndent("]"); - } else // output on a single line - { - assert(childValues_.size() == size); - *sout_ << "["; - if (!indentation_.empty()) *sout_ << " "; - for (unsigned index = 0; index < size; ++index) { - if (index > 0) - *sout_ << ", "; - *sout_ << childValues_[index]; - } - if (!indentation_.empty()) *sout_ << " "; - *sout_ << "]"; - } - } -} - -bool BuiltStyledStreamWriter::isMultineArray(Value const& value) { - int size = value.size(); - bool isMultiLine = size * 3 >= rightMargin_; - childValues_.clear(); - for (int index = 0; index < size && !isMultiLine; ++index) { - Value const& childValue = value[index]; - isMultiLine = - isMultiLine || ((childValue.isArray() || childValue.isObject()) && - childValue.size() > 0); - } - if (!isMultiLine) // check if line length > max line length - { - childValues_.reserve(size); - addChildValues_ = true; - int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' - for (int index = 0; index < size; ++index) { - if (hasCommentForValue(value[index])) { - isMultiLine = true; - } - writeValue(value[index]); - lineLength += int(childValues_[index].length()); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - -void BuiltStyledStreamWriter::pushValue(std::string const& value) { - if (addChildValues_) - childValues_.push_back(value); - else - *sout_ << value; -} - -void BuiltStyledStreamWriter::writeIndent() { - // blep intended this to look at the so-far-written string - // to determine whether we are already indented, but - // with a stream we cannot do that. So we rely on some saved state. - // The caller checks indented_. - - if (!indentation_.empty()) { - // In this case, drop newlines too. - *sout_ << '\n' << indentString_; - } -} - -void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) { - if (!indented_) writeIndent(); - *sout_ << value; - indented_ = false; -} - -void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } - -void BuiltStyledStreamWriter::unindent() { - assert(indentString_.size() >= indentation_.size()); - indentString_.resize(indentString_.size() - indentation_.size()); -} - -void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { - if (cs_ == CommentStyle::None) return; - if (!root.hasComment(commentBefore)) - return; - - if (!indented_) writeIndent(); - const std::string& comment = root.getComment(commentBefore); - std::string::const_iterator iter = comment.begin(); - while (iter != comment.end()) { - *sout_ << *iter; - if (*iter == '\n' && - (iter != comment.end() && *(iter + 1) == '/')) - // writeIndent(); // would write extra newline - *sout_ << indentString_; - ++iter; - } - indented_ = false; -} - -void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) { - if (cs_ == CommentStyle::None) return; - if (root.hasComment(commentAfterOnSameLine)) - *sout_ << " " + root.getComment(commentAfterOnSameLine); - - if (root.hasComment(commentAfter)) { - writeIndent(); - *sout_ << root.getComment(commentAfter); - } -} - -// static -bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { - return value.hasComment(commentBefore) || - value.hasComment(commentAfterOnSameLine) || - value.hasComment(commentAfter); -} - -/////////////// -// StreamWriter - -StreamWriter::StreamWriter() - : sout_(NULL) -{ -} -StreamWriter::~StreamWriter() -{ -} -StreamWriter::Factory::~Factory() -{} -StreamWriterBuilder::StreamWriterBuilder() -{ - setDefaults(&settings_); -} -StreamWriterBuilder::~StreamWriterBuilder() -{} -StreamWriter* StreamWriterBuilder::newStreamWriter() const -{ - std::string indentation = settings_["indentation"].asString(); - std::string cs_str = settings_["commentStyle"].asString(); - bool eyc = settings_["enableYAMLCompatibility"].asBool(); - bool dnp = settings_["dropNullPlaceholders"].asBool(); - CommentStyle::Enum cs = CommentStyle::All; - if (cs_str == "All") { - cs = CommentStyle::All; - } else if (cs_str == "None") { - cs = CommentStyle::None; - } else { - throwRuntimeError("commentStyle must be 'All' or 'None'"); - } - std::string colonSymbol = " : "; - if (eyc) { - colonSymbol = ": "; - } else if (indentation.empty()) { - colonSymbol = ":"; - } - std::string nullSymbol = "null"; - if (dnp) { - nullSymbol = ""; - } - std::string endingLineFeedSymbol = ""; - return new BuiltStyledStreamWriter( - indentation, cs, - colonSymbol, nullSymbol, endingLineFeedSymbol); -} -static void getValidWriterKeys(std::set* valid_keys) -{ - valid_keys->clear(); - valid_keys->insert("indentation"); - valid_keys->insert("commentStyle"); - valid_keys->insert("enableYAMLCompatibility"); - valid_keys->insert("dropNullPlaceholders"); -} -bool StreamWriterBuilder::validate(Json::Value* invalid) const -{ - Json::Value my_invalid; - if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL - Json::Value& inv = *invalid; - std::set valid_keys; - getValidWriterKeys(&valid_keys); - Value::Members keys = settings_.getMemberNames(); - size_t n = keys.size(); - for (size_t i = 0; i < n; ++i) { - std::string const& key = keys[i]; - if (valid_keys.find(key) == valid_keys.end()) { - inv[key] = settings_[key]; - } - } - return 0u == inv.size(); -} -Value& StreamWriterBuilder::operator[](std::string key) -{ - return settings_[key]; -} -// static -void StreamWriterBuilder::setDefaults(Json::Value* settings) -{ - //! [StreamWriterBuilderDefaults] - (*settings)["commentStyle"] = "All"; - (*settings)["indentation"] = "\t"; - (*settings)["enableYAMLCompatibility"] = false; - (*settings)["dropNullPlaceholders"] = false; - //! [StreamWriterBuilderDefaults] -} - -std::string writeString(StreamWriter::Factory const& builder, Value const& root) { - std::ostringstream sout; - StreamWriterPtr const writer(builder.newStreamWriter()); - writer->write(root, &sout); - return sout.str(); -} - -std::ostream& operator<<(std::ostream& sout, Value const& root) { - StreamWriterBuilder builder; - StreamWriterPtr const writer(builder.newStreamWriter()); - writer->write(root, &sout); - return sout; -} - -} // namespace Json diff --git a/3rdpart/jsoncpp/reader.h b/3rdpart/jsoncpp/reader.h deleted file mode 100644 index 4148344a..00000000 --- a/3rdpart/jsoncpp/reader.h +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_READER_H_INCLUDED -#define CPPTL_JSON_READER_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "../jsoncpp/features.h" -#include "../jsoncpp/value.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include -#include -#include - -// Disable warning C4251: : needs to have dll-interface to -// be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(push) -#pragma warning(disable : 4251) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -namespace Json { - -/** \brief Unserialize a JSON document into a - *Value. - * - * \deprecated Use CharReader and CharReaderBuilder. - */ -class JSON_API Reader { -public: - typedef char Char; - typedef const Char* Location; - - /** \brief An error tagged with where in the JSON text it was encountered. - * - * The offsets give the [start, limit) range of bytes within the text. Note - * that this is bytes, not codepoints. - * - */ - struct StructuredError { - size_t offset_start; - size_t offset_limit; - std::string message; - }; - - /** \brief Constructs a Reader allowing all features - * for parsing. - */ - Reader(); - - /** \brief Constructs a Reader allowing the specified feature set - * for parsing. - */ - Reader(const Features& features); - - /** \brief Read a Value from a JSON - * document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them - * back during - * serialization, \c false to discard comments. - * This parameter is ignored if - * Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an - * error occurred. - */ - bool - parse(const std::string& document, Value& root, bool collectComments = true); - - /** \brief Read a Value from a JSON - document. - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the - document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - document to read. - * Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them - back during - * serialization, \c false to discard comments. - * This parameter is ignored if - Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an - error occurred. - */ - bool parse(const char* beginDoc, - const char* endDoc, - Value& root, - bool collectComments = true); - - /// \brief Parse from input stream. - /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse(std::istream& is, Value& root, bool collectComments = true); - - /** \brief Returns a user friendly string that list errors in the parsed - * document. - * \return Formatted error message with the list of errors with their location - * in - * the parsed document. An empty string is returned if no error - * occurred - * during parsing. - * \deprecated Use getFormattedErrorMessages() instead (typo fix). - */ - JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") - std::string getFormatedErrorMessages() const; - - /** \brief Returns a user friendly string that list errors in the parsed - * document. - * \return Formatted error message with the list of errors with their location - * in - * the parsed document. An empty string is returned if no error - * occurred - * during parsing. - */ - std::string getFormattedErrorMessages() const; - - /** \brief Returns a vector of structured erros encounted while parsing. - * \return A (possibly empty) vector of StructuredError objects. Currently - * only one error can be returned, but the caller should tolerate - * multiple - * errors. This can occur if the parser recovers from a non-fatal - * parse error and then encounters additional errors. - */ - std::vector getStructuredErrors() const; - - /** \brief Add a semantic error message. - * \param value JSON Value location associated with the error - * \param message The error message. - * \return \c true if the error was successfully added, \c false if the - * Value offset exceeds the document size. - */ - bool pushError(const Value& value, const std::string& message); - - /** \brief Add a semantic error message with extra context. - * \param value JSON Value location associated with the error - * \param message The error message. - * \param extra Additional JSON Value location to contextualize the error - * \return \c true if the error was successfully added, \c false if either - * Value offset exceeds the document size. - */ - bool pushError(const Value& value, const std::string& message, const Value& extra); - - /** \brief Return whether there are any errors. - * \return \c true if there are no errors to report \c false if - * errors have occurred. - */ - bool good() const; - -private: - enum TokenType { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo { - public: - Token token_; - std::string message_; - Location extra_; - }; - - typedef std::deque Errors; - - bool readToken(Token& token); - void skipSpaces(); - bool match(Location pattern, int patternLength); - bool readComment(); - bool readCStyleComment(); - bool readCppStyleComment(); - bool readString(); - void readNumber(); - bool readValue(); - bool readObject(Token& token); - bool readArray(Token& token); - bool decodeNumber(Token& token); - bool decodeNumber(Token& token, Value& decoded); - bool decodeString(Token& token); - bool decodeString(Token& token, std::string& decoded); - bool decodeDouble(Token& token); - bool decodeDouble(Token& token, Value& decoded); - bool decodeUnicodeCodePoint(Token& token, - Location& current, - Location end, - unsigned int& unicode); - bool decodeUnicodeEscapeSequence(Token& token, - Location& current, - Location end, - unsigned int& unicode); - bool addError(const std::string& message, Token& token, Location extra = 0); - bool recoverFromError(TokenType skipUntilToken); - bool addErrorAndRecover(const std::string& message, - Token& token, - TokenType skipUntilToken); - void skipUntilSpace(); - Value& currentValue(); - Char getNextChar(); - void - getLocationLineAndColumn(Location location, int& line, int& column) const; - std::string getLocationLineAndColumn(Location location) const; - void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); - - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - std::string document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value* lastValue_; - std::string commentsBefore_; - Features features_; - bool collectComments_; -}; // Reader - -/** Interface for reading JSON from a char array. - */ -class JSON_API CharReader { -public: - virtual ~CharReader() {} - /** \brief Read a Value from a JSON - document. - * The document must be a UTF-8 encoded string containing the document to read. - * - * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the - document to read. - * \param endDoc Pointer on the end of the UTF-8 encoded string of the - document to read. - * Must be >= beginDoc. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param errs [out] Formatted error messages (if not NULL) - * a user friendly string that lists errors in the parsed - * document. - * \return \c true if the document was successfully parsed, \c false if an - error occurred. - */ - virtual bool parse( - char const* beginDoc, char const* endDoc, - Value* root, std::string* errs) = 0; - - class Factory { - public: - virtual ~Factory() {} - /** \brief Allocate a CharReader via operator new(). - * \throw std::exception if something goes wrong (e.g. invalid settings) - */ - virtual CharReader* newCharReader() const = 0; - }; // Factory -}; // CharReader - -/** \brief Build a CharReader implementation. - -Usage: -\code - using namespace Json; - CharReaderBuilder builder; - builder["collectComments"] = false; - Value value; - std::string errs; - bool ok = parseFromStream(builder, std::cin, &value, &errs); -\endcode -*/ -class JSON_API CharReaderBuilder : public CharReader::Factory { -public: - // Note: We use a Json::Value so that we can add data-members to this class - // without a major version bump. - /** Configuration of this builder. - These are case-sensitive. - Available settings (case-sensitive): - - `"collectComments": false or true` - - true to collect comment and allow writing them - back during serialization, false to discard comments. - This parameter is ignored if allowComments is false. - - `"allowComments": false or true` - - true if comments are allowed. - - `"strictRoot": false or true` - - true if root must be either an array or an object value - - `"allowDroppedNullPlaceholders": false or true` - - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) - - `"allowNumericKeys": false or true` - - true if numeric object keys are allowed. - - `"allowSingleQuotes": false or true` - - true if '' are allowed for strings (both keys and values) - - `"stackLimit": integer` - - Exceeding stackLimit (recursive depth of `readValue()`) will - cause an exception. - - This is a security issue (seg-faults caused by deeply nested JSON), - so the default is low. - - `"failIfExtra": false or true` - - If true, `parse()` returns false when extra non-whitespace trails - the JSON value in the input string. - - `"rejectDupKeys": false or true` - - If true, `parse()` returns false when a key is duplicated within an object. - - You can examine 'settings_` yourself - to see the defaults. You can also write and read them just like any - JSON Value. - \sa setDefaults() - */ - Json::Value settings_; - - CharReaderBuilder(); - virtual ~CharReaderBuilder(); - - virtual CharReader* newCharReader() const; - - /** \return true if 'settings' are legal and consistent; - * otherwise, indicate bad settings via 'invalid'. - */ - bool validate(Json::Value* invalid) const; - - /** A simple way to update a specific setting. - */ - Value& operator[](std::string key); - - /** Called by ctor, but you can use this to reset settings_. - * \pre 'settings' != NULL (but Json::null is fine) - * \remark Defaults: - * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults - */ - static void setDefaults(Json::Value* settings); - /** Same as old Features::strictMode(). - * \pre 'settings' != NULL (but Json::null is fine) - * \remark Defaults: - * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode - */ - static void strictMode(Json::Value* settings); -}; - -/** Consume entire stream and use its begin/end. - * Someday we might have a real StreamReader, but for now this - * is convenient. - */ -bool JSON_API parseFromStream( - CharReader::Factory const&, - std::istream&, - Value* root, std::string* errs); - -/** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - Json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { - "dir": { - "file": { - // The input stream JSON would be nested here. - } - } - } - \endverbatim - \throw std::exception on parse error. - \see Json::operator<<() -*/ -JSON_API std::istream& operator>>(std::istream&, Value&); - -} // namespace Json - -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(pop) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#endif // CPPTL_JSON_READER_H_INCLUDED diff --git a/3rdpart/jsoncpp/value.h b/3rdpart/jsoncpp/value.h deleted file mode 100644 index 15439a9c..00000000 --- a/3rdpart/jsoncpp/value.h +++ /dev/null @@ -1,841 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef CPPTL_JSON_H_INCLUDED -#define CPPTL_JSON_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "forwards.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include - -#ifndef JSON_USE_CPPTL_SMALLMAP -#include -#else -#include -#endif -#ifdef JSON_USE_CPPTL -#include -#endif - -// Disable warning C4251: : needs to have dll-interface to -// be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(push) -#pragma warning(disable : 4251) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -/** \brief JSON (JavaScript Object Notation). - */ -namespace Json { - -/** Base class for all exceptions we throw. - * - * We use nothing but these internally. Of course, STL can throw others. - */ -class JSON_API Exception : public std::exception { -public: - Exception(std::string const& msg); - virtual ~Exception() throw(); - virtual char const* what() const throw(); -protected: - std::string const msg_; -}; - -/** Exceptions which the user cannot easily avoid. - * - * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input - * - * \remark derived from Json::Exception - */ -class JSON_API RuntimeError : public Exception { -public: - RuntimeError(std::string const& msg); -}; - -/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. - * - * These are precondition-violations (user bugs) and internal errors (our bugs). - * - * \remark derived from Json::Exception - */ -class JSON_API LogicError : public Exception { -public: - LogicError(std::string const& msg); -}; - -/// used internally -void throwRuntimeError(std::string const& msg); -/// used internally -void throwLogicError(std::string const& msg); - -/** \brief Type of the value held by a Value object. - */ -enum ValueType { - nullValue = 0, ///< 'null' value - intValue, ///< signed integer value - uintValue, ///< unsigned integer value - realValue, ///< double value - stringValue, ///< UTF-8 string value - booleanValue, ///< bool value - arrayValue, ///< array value (ordered list) - objectValue ///< object value (collection of name/value pairs). -}; - -enum CommentPlacement { - commentBefore = 0, ///< a comment placed on the line before a value - commentAfterOnSameLine, ///< a comment just after a value on the same line - commentAfter, ///< a comment on the line after a value (only make sense for - /// root value) - numberOfCommentPlacement -}; - -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator EnumMemberNames; -// typedef CppTL::AnyEnumerator EnumValues; -//# endif - -/** \brief Lightweight wrapper to tag static string. - * - * Value constructor and objectValue member assignement takes advantage of the - * StaticString and avoid the cost of string duplication when storing the - * string or the member name. - * - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ -class JSON_API StaticString { -public: - explicit StaticString(const char* czstring) : c_str_(czstring) {} - - operator const char*() const { return c_str_; } - - const char* c_str() const { return c_str_; } - -private: - const char* c_str_; -}; - -/** \brief Represents a JSON value. - * - * This class is a discriminated union wrapper that can represents a: - * - signed integer [range: Value::minInt - Value::maxInt] - * - unsigned integer (range: 0 - Value::maxUInt) - * - double - * - UTF-8 string - * - boolean - * - 'null' - * - an ordered list of Value - * - collection of name/value pairs (javascript object) - * - * The type of the held value is represented by a #ValueType and - * can be obtained using type(). - * - * Values of an #objectValue or #arrayValue can be accessed using operator[]() - * methods. - * Non-const methods will automatically create the a #nullValue element - * if it does not exist. - * The sequence of an #arrayValue will be automatically resized and initialized - * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. - * - * The get() methods can be used to obtain default value in the case the - * required element does not exist. - * - * It is possible to iterate over the list of a #objectValue values using - * the getMemberNames() method. - * - * \note #Value string-length fit in size_t, but keys must be < 2^30. - * (The reason is an implementation detail.) A #CharReader will raise an - * exception if a bound is exceeded to avoid security holes in your app, - * but the Value API does *not* check bounds. That is the responsibility - * of the caller. - */ -class JSON_API Value { - friend class ValueIteratorBase; -public: - typedef std::vector Members; - typedef ValueIterator iterator; - typedef ValueConstIterator const_iterator; - typedef Json::UInt UInt; - typedef Json::Int Int; -#if defined(JSON_HAS_INT64) - typedef Json::UInt64 UInt64; - typedef Json::Int64 Int64; -#endif // defined(JSON_HAS_INT64) - typedef Json::LargestInt LargestInt; - typedef Json::LargestUInt LargestUInt; - typedef Json::ArrayIndex ArrayIndex; - - static const Value& null; ///< We regret this reference to a global instance; prefer the simpler Value(). - static const Value& nullRef; ///< just a kludge for binary-compatibility; same as null - /// Minimum signed integer value that can be stored in a Json::Value. - static const LargestInt minLargestInt; - /// Maximum signed integer value that can be stored in a Json::Value. - static const LargestInt maxLargestInt; - /// Maximum unsigned integer value that can be stored in a Json::Value. - static const LargestUInt maxLargestUInt; - - /// Minimum signed int value that can be stored in a Json::Value. - static const Int minInt; - /// Maximum signed int value that can be stored in a Json::Value. - static const Int maxInt; - /// Maximum unsigned int value that can be stored in a Json::Value. - static const UInt maxUInt; - -#if defined(JSON_HAS_INT64) - /// Minimum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 minInt64; - /// Maximum signed 64 bits int value that can be stored in a Json::Value. - static const Int64 maxInt64; - /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. - static const UInt64 maxUInt64; -#endif // defined(JSON_HAS_INT64) - -private: -#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - class CZString { - public: - enum DuplicationPolicy { - noDuplication = 0, - duplicate, - duplicateOnCopy - }; - CZString(ArrayIndex index); - CZString(char const* str, unsigned length, DuplicationPolicy allocate); - CZString(CZString const& other); - ~CZString(); - CZString& operator=(CZString other); - bool operator<(CZString const& other) const; - bool operator==(CZString const& other) const; - ArrayIndex index() const; - //const char* c_str() const; ///< \deprecated - char const* data() const; - unsigned length() const; - bool isStaticString() const; - - private: - void swap(CZString& other); - - struct StringStorage { - unsigned policy_: 2; - unsigned length_: 30; // 1GB max - }; - - char const* cstr_; // actually, a prefixed string, unless policy is noDup - union { - ArrayIndex index_; - StringStorage storage_; - }; - }; - -public: -#ifndef JSON_USE_CPPTL_SMALLMAP - typedef std::map ObjectValues; -#else - typedef CppTL::SmallMap ObjectValues; -#endif // ifndef JSON_USE_CPPTL_SMALLMAP -#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - -public: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass arrayValue. - To create an empty object, pass objectValue. - Another Value can then be set to this one by assignment. -This is useful since clear() and resize() will not alter types. - - Examples: -\code -Json::Value null_value; // null -Json::Value arr_value(Json::arrayValue); // [] -Json::Value obj_value(Json::objectValue); // {} -\endcode - */ - Value(ValueType type = nullValue); - Value(Int value); - Value(UInt value); -#if defined(JSON_HAS_INT64) - Value(Int64 value); - Value(UInt64 value); -#endif // if defined(JSON_HAS_INT64) - Value(double value); - Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) - Value(const char* begin, const char* end); ///< Copy all, incl zeroes. - /** \brief Constructs a value from a static string. - - * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to this - * constructor. - * \note This works only for null-terminated strings. (We cannot change the - * size of this class, so we have nowhere to store the length, - * which might be computed later for various operations.) - * - * Example of usage: - * \code - * static StaticString foo("some text"); - * Json::Value aValue(foo); - * \endcode - */ - Value(const StaticString& value); - Value(const std::string& value); ///< Copy data() til size(). Embedded zeroes too. -#ifdef JSON_USE_CPPTL - Value(const CppTL::ConstString& value); -#endif - Value(bool value); - /// Deep copy. - Value(const Value& other); - ~Value(); - - /// Deep copy, then swap(other). - /// \note Over-write existing comments. To preserve comments, use #swapPayload(). - Value& operator=(Value other); - /// Swap everything. - void swap(Value& other); - /// Swap values but leave comments and source offsets in place. - void swapPayload(Value& other); - - ValueType type() const; - - /// Compare payload only, not comments etc. - bool operator<(const Value& other) const; - bool operator<=(const Value& other) const; - bool operator>=(const Value& other) const; - bool operator>(const Value& other) const; - bool operator==(const Value& other) const; - bool operator!=(const Value& other) const; - int compare(const Value& other) const; - - const char* asCString() const; ///< Embedded zeroes could cause you trouble! - std::string asString() const; ///< Embedded zeroes are possible. - /** Get raw char* of string-value. - * \return false if !string. (Seg-fault if str or end are NULL.) - */ - bool getString( - char const** begin, char const** end) const; -#ifdef JSON_USE_CPPTL - CppTL::ConstString asConstString() const; -#endif - Int asInt() const; - UInt asUInt() const; -#if defined(JSON_HAS_INT64) - Int64 asInt64() const; - UInt64 asUInt64() const; -#endif // if defined(JSON_HAS_INT64) - LargestInt asLargestInt() const; - LargestUInt asLargestUInt() const; - float asFloat() const; - double asDouble() const; - bool asBool() const; - - bool isNull() const; - bool isBool() const; - bool isInt() const; - bool isInt64() const; - bool isUInt() const; - bool isUInt64() const; - bool isIntegral() const; - bool isDouble() const; - bool isNumeric() const; - bool isString() const; - bool isArray() const; - bool isObject() const; - - bool isConvertibleTo(ValueType other) const; - - /// Number of values in array or object - ArrayIndex size() const; - - /// \brief Return true if empty array, empty object, or null; - /// otherwise, false. - bool empty() const; - - /// Return isNull() - bool operator!() const; - - /// Remove all object members and array elements. - /// \pre type() is arrayValue, objectValue, or nullValue - /// \post type() is unchanged - void clear(); - - /// Resize the array to size elements. - /// New elements are initialized to null. - /// May only be called on nullValue or arrayValue. - /// \pre type() is arrayValue or nullValue - /// \post type() is arrayValue - void resize(ArrayIndex size); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are - /// inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value& operator[](ArrayIndex index); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are - /// inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value& operator[](int index); - - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value& operator[](ArrayIndex index) const; - - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value& operator[](int index) const; - - /// If the array contains at least index+1 elements, returns the element - /// value, - /// otherwise returns defaultValue. - Value get(ArrayIndex index, const Value& defaultValue) const; - /// Return true if index < size(). - bool isValidIndex(ArrayIndex index) const; - /// \brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; - Value& append(const Value& value); - - /// Access an object value by name, create a null member if it does not exist. - /// \note Because of our implementation, keys are limited to 2^30 -1 chars. - /// Exceeding that will cause an exception. - Value& operator[](const char* key); - /// Access an object value by name, returns null if there is no member with - /// that name. - const Value& operator[](const char* key) const; - /// Access an object value by name, create a null member if it does not exist. - /// \param key may contain embedded nulls. - Value& operator[](const std::string& key); - /// Access an object value by name, returns null if there is no member with - /// that name. - /// \param key may contain embedded nulls. - const Value& operator[](const std::string& key) const; - /** \brief Access an object value by name, create a null member if it does not - exist. - - * If the object has no entry for that name, then the member name used to store - * the new entry is not duplicated. - * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - Value& operator[](const StaticString& key); -#ifdef JSON_USE_CPPTL - /// Access an object value by name, create a null member if it does not exist. - Value& operator[](const CppTL::ConstString& key); - /// Access an object value by name, returns null if there is no member with - /// that name. - const Value& operator[](const CppTL::ConstString& key) const; -#endif - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - Value get(const char* key, const Value& defaultValue) const; - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - /// \note key may contain embedded nulls. - Value get(const char* begin, const char* end, const Value& defaultValue) const; - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - /// \param key may contain embedded nulls. - Value get(const std::string& key, const Value& defaultValue) const; -#ifdef JSON_USE_CPPTL - /// Return the member named key if it exist, defaultValue otherwise. - /// \note deep copy - Value get(const CppTL::ConstString& key, const Value& defaultValue) const; -#endif - /// Most general and efficient version of isMember()const, get()const, - /// and operator[]const - /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 - Value const* find(char const* begin, char const* end) const; - /// Most general and efficient version of object-mutators. - /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 - /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. - Value const* demand(char const* begin, char const* end); - /// \brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \return the removed Value, or null. - /// \pre type() is objectValue or nullValue - /// \post type() is unchanged - /// \deprecated - Value removeMember(const char* key); - /// Same as removeMember(const char*) - /// \param key may contain embedded nulls. - /// \deprecated - Value removeMember(const std::string& key); - /// Same as removeMember(const char* begin, const char* end, Value* removed), - /// but 'key' is null-terminated. - bool removeMember(const char* key, Value* removed); - /** \brief Remove the named map member. - - Update 'removed' iff removed. - \param key may contain embedded nulls. - \return true iff removed (no exceptions) - */ - bool removeMember(std::string const& key, Value* removed); - /// Same as removeMember(std::string const& key, Value* removed) - bool removeMember(const char* begin, const char* end, Value* removed); - /** \brief Remove the indexed array element. - - O(n) expensive operations. - Update 'removed' iff removed. - \return true iff removed (no exceptions) - */ - bool removeIndex(ArrayIndex i, Value* removed); - - /// Return true if the object has a member named key. - /// \note 'key' must be null-terminated. - bool isMember(const char* key) const; - /// Return true if the object has a member named key. - /// \param key may contain embedded nulls. - bool isMember(const std::string& key) const; - /// Same as isMember(std::string const& key)const - bool isMember(const char* begin, const char* end) const; -#ifdef JSON_USE_CPPTL - /// Return true if the object has a member named key. - bool isMember(const CppTL::ConstString& key) const; -#endif - - /// \brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is objectValue or nullValue - /// \post if type() was nullValue, it remains nullValue - Members getMemberNames() const; - - //# ifdef JSON_USE_CPPTL - // EnumMemberNames enumMemberNames() const; - // EnumValues enumValues() const; - //# endif - - /// \deprecated Always pass len. - JSONCPP_DEPRECATED("Use setComment(std::string const&) instead.") - void setComment(const char* comment, CommentPlacement placement); - /// Comments must be //... or /* ... */ - void setComment(const char* comment, size_t len, CommentPlacement placement); - /// Comments must be //... or /* ... */ - void setComment(const std::string& comment, CommentPlacement placement); - bool hasComment(CommentPlacement placement) const; - /// Include delimiters and embedded newlines. - std::string getComment(CommentPlacement placement) const; - - std::string toStyledString() const; - - const_iterator begin() const; - const_iterator end() const; - - iterator begin(); - iterator end(); - - // Accessors for the [start, limit) range of bytes within the JSON text from - // which this value was parsed, if any. - void setOffsetStart(size_t start); - void setOffsetLimit(size_t limit); - size_t getOffsetStart() const; - size_t getOffsetLimit() const; - -private: - void initBasic(ValueType type, bool allocated = false); - - Value& resolveReference(const char* key); - Value& resolveReference(const char* key, const char* end); - - struct CommentInfo { - CommentInfo(); - ~CommentInfo(); - - void setComment(const char* text, size_t len); - - char* comment_; - }; - - // struct MemberNamesTransform - //{ - // typedef const char *result_type; - // const char *operator()( const CZString &name ) const - // { - // return name.c_str(); - // } - //}; - - union ValueHolder { - LargestInt int_; - LargestUInt uint_; - double real_; - bool bool_; - char* string_; // actually ptr to unsigned, followed by str, unless !allocated_ - ObjectValues* map_; - } value_; - ValueType type_ : 8; - unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. - // If not allocated_, string_ must be null-terminated. - CommentInfo* comments_; - - // [start, limit) byte offsets in the source JSON text from which this Value - // was extracted. - size_t start_; - size_t limit_; -}; - -/** \brief Experimental and untested: represents an element of the "path" to - * access a node. - */ -class JSON_API PathArgument { -public: - friend class Path; - - PathArgument(); - PathArgument(ArrayIndex index); - PathArgument(const char* key); - PathArgument(const std::string& key); - -private: - enum Kind { - kindNone = 0, - kindIndex, - kindKey - }; - std::string key_; - ArrayIndex index_; - Kind kind_; -}; - -/** \brief Experimental and untested: represents a "path" to access a node. - * - * Syntax: - * - "." => root node - * - ".[n]" => elements at index 'n' of root node (an array value) - * - ".name" => member named 'name' of root node (an object value) - * - ".name1.name2.name3" - * - ".[0][1][2].name1[3]" - * - ".%" => member name is provided as parameter - * - ".[%]" => index is provied as parameter - */ -class JSON_API Path { -public: - Path(const std::string& path, - const PathArgument& a1 = PathArgument(), - const PathArgument& a2 = PathArgument(), - const PathArgument& a3 = PathArgument(), - const PathArgument& a4 = PathArgument(), - const PathArgument& a5 = PathArgument()); - - const Value& resolve(const Value& root) const; - Value resolve(const Value& root, const Value& defaultValue) const; - /// Creates the "path" to access the specified node and returns a reference on - /// the node. - Value& make(Value& root) const; - -private: - typedef std::vector InArgs; - typedef std::vector Args; - - void makePath(const std::string& path, const InArgs& in); - void addPathInArg(const std::string& path, - const InArgs& in, - InArgs::const_iterator& itInArg, - PathArgument::Kind kind); - void invalidPath(const std::string& path, int location); - - Args args_; -}; - -/** \brief base class for Value iterators. - * - */ -class JSON_API ValueIteratorBase { -public: - typedef std::bidirectional_iterator_tag iterator_category; - typedef unsigned int size_t; - typedef int difference_type; - typedef ValueIteratorBase SelfType; - - bool operator==(const SelfType& other) const { return isEqual(other); } - - bool operator!=(const SelfType& other) const { return !isEqual(other); } - - difference_type operator-(const SelfType& other) const { - return other.computeDistance(*this); - } - - /// Return either the index or the member name of the referenced value as a - /// Value. - Value key() const; - - /// Return the index of the referenced Value, or -1 if it is not an arrayValue. - UInt index() const; - - /// Return the member name of the referenced Value, or "" if it is not an - /// objectValue. - /// \note Avoid `c_str()` on result, as embedded zeroes are possible. - std::string name() const; - - /// Return the member name of the referenced Value. "" if it is not an - /// objectValue. - /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. - JSONCPP_DEPRECATED("Use `key = name();` instead.") - char const* memberName() const; - /// Return the member name of the referenced Value, or NULL if it is not an - /// objectValue. - /// \note Better version than memberName(). Allows embedded nulls. - char const* memberName(char const** end) const; - -protected: - Value& deref() const; - - void increment(); - - void decrement(); - - difference_type computeDistance(const SelfType& other) const; - - bool isEqual(const SelfType& other) const; - - void copy(const SelfType& other); - -private: - Value::ObjectValues::iterator current_; - // Indicates that iterator is for a null value. - bool isNull_; - -public: - // For some reason, BORLAND needs these at the end, rather - // than earlier. No idea why. - ValueIteratorBase(); - explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); -}; - -/** \brief const iterator for object and array value. - * - */ -class JSON_API ValueConstIterator : public ValueIteratorBase { - friend class Value; - -public: - typedef const Value value_type; - //typedef unsigned int size_t; - //typedef int difference_type; - typedef const Value& reference; - typedef const Value* pointer; - typedef ValueConstIterator SelfType; - - ValueConstIterator(); - -private: -/*! \internal Use by Value to create an iterator. - */ - explicit ValueConstIterator(const Value::ObjectValues::iterator& current); -public: - SelfType& operator=(const ValueIteratorBase& other); - - SelfType operator++(int) { - SelfType temp(*this); - ++*this; - return temp; - } - - SelfType operator--(int) { - SelfType temp(*this); - --*this; - return temp; - } - - SelfType& operator--() { - decrement(); - return *this; - } - - SelfType& operator++() { - increment(); - return *this; - } - - reference operator*() const { return deref(); } - - pointer operator->() const { return &deref(); } -}; - -/** \brief Iterator for object and array value. - */ -class JSON_API ValueIterator : public ValueIteratorBase { - friend class Value; - -public: - typedef Value value_type; - typedef unsigned int size_t; - typedef int difference_type; - typedef Value& reference; - typedef Value* pointer; - typedef ValueIterator SelfType; - - ValueIterator(); - ValueIterator(const ValueConstIterator& other); - ValueIterator(const ValueIterator& other); - -private: -/*! \internal Use by Value to create an iterator. - */ - explicit ValueIterator(const Value::ObjectValues::iterator& current); -public: - SelfType& operator=(const SelfType& other); - - SelfType operator++(int) { - SelfType temp(*this); - ++*this; - return temp; - } - - SelfType operator--(int) { - SelfType temp(*this); - --*this; - return temp; - } - - SelfType& operator--() { - decrement(); - return *this; - } - - SelfType& operator++() { - increment(); - return *this; - } - - reference operator*() const { return deref(); } - - pointer operator->() const { return &deref(); } -}; - -} // namespace Json - - -namespace std { -/// Specialize std::swap() for Json::Value. -template<> -inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } -} - - -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(pop) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#endif // CPPTL_JSON_H_INCLUDED diff --git a/3rdpart/jsoncpp/version.h b/3rdpart/jsoncpp/version.h deleted file mode 100644 index 38388bdc..00000000 --- a/3rdpart/jsoncpp/version.h +++ /dev/null @@ -1,13 +0,0 @@ -// DO NOT EDIT. This file (and "version") is generated by CMake. -// Run CMake configure step to update it. -#ifndef JSON_VERSION_H_INCLUDED -# define JSON_VERSION_H_INCLUDED - -# define JSONCPP_VERSION_STRING "1.6.5" -# define JSONCPP_VERSION_MAJOR 1 -# define JSONCPP_VERSION_MINOR 6 -# define JSONCPP_VERSION_PATCH 5 -# define JSONCPP_VERSION_QUALIFIER -# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) - -#endif // JSON_VERSION_H_INCLUDED diff --git a/3rdpart/jsoncpp/version.h.in b/3rdpart/jsoncpp/version.h.in deleted file mode 100644 index 692ab5ed..00000000 --- a/3rdpart/jsoncpp/version.h.in +++ /dev/null @@ -1,13 +0,0 @@ -// DO NOT EDIT. This file (and "version") is generated by CMake. -// Run CMake configure step to update it. -#ifndef JSON_VERSION_H_INCLUDED -# define JSON_VERSION_H_INCLUDED - -# define JSONCPP_VERSION_STRING "@JSONCPP_VERSION@" -# define JSONCPP_VERSION_MAJOR @JSONCPP_VERSION_MAJOR@ -# define JSONCPP_VERSION_MINOR @JSONCPP_VERSION_MINOR@ -# define JSONCPP_VERSION_PATCH @JSONCPP_VERSION_PATCH@ -# define JSONCPP_VERSION_QUALIFIER -# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) - -#endif // JSON_VERSION_H_INCLUDED diff --git a/3rdpart/jsoncpp/writer.h b/3rdpart/jsoncpp/writer.h deleted file mode 100644 index 815f2183..00000000 --- a/3rdpart/jsoncpp/writer.h +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright 2007-2010 Baptiste Lepilleur -// Distributed under MIT license, or public domain if desired and -// recognized in your jurisdiction. -// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE - -#ifndef JSON_WRITER_H_INCLUDED -#define JSON_WRITER_H_INCLUDED - -#if !defined(JSON_IS_AMALGAMATION) -#include "value.h" -#endif // if !defined(JSON_IS_AMALGAMATION) -#include -#include -#include - -// Disable warning C4251: : needs to have dll-interface to -// be used by... -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(push) -#pragma warning(disable : 4251) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -namespace Json { - -class Value; - -/** - -Usage: -\code - using namespace Json; - void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { - std::unique_ptr const writer( - factory.newStreamWriter()); - writer->write(value, &std::cout); - std::cout << std::endl; // add lf and flush - } -\endcode -*/ -class JSON_API StreamWriter { -protected: - std::ostream* sout_; // not owned; will not delete -public: - StreamWriter(); - virtual ~StreamWriter(); - /** Write Value into document as configured in sub-class. - Do not take ownership of sout, but maintain a reference during function. - \pre sout != NULL - \return zero on success (For now, we always return zero, so check the stream instead.) - \throw std::exception possibly, depending on configuration - */ - virtual int write(Value const& root, std::ostream* sout) = 0; - - /** \brief A simple abstract factory. - */ - class JSON_API Factory { - public: - virtual ~Factory(); - /** \brief Allocate a CharReader via operator new(). - * \throw std::exception if something goes wrong (e.g. invalid settings) - */ - virtual StreamWriter* newStreamWriter() const = 0; - }; // Factory -}; // StreamWriter - -/** \brief Write into stringstream, then return string, for convenience. - * A StreamWriter will be created from the factory, used, and then deleted. - */ -std::string JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); - - -/** \brief Build a StreamWriter implementation. - -Usage: -\code - using namespace Json; - Value value = ...; - StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = " "; // or whatever you like - std::unique_ptr writer( - builder.newStreamWriter()); - writer->write(value, &std::cout); - std::cout << std::endl; // add lf and flush -\endcode -*/ -class JSON_API StreamWriterBuilder : public StreamWriter::Factory { -public: - // Note: We use a Json::Value so that we can add data-members to this class - // without a major version bump. - /** Configuration of this builder. - Available settings (case-sensitive): - - "commentStyle": "None" or "All" - - "indentation": "" - - "enableYAMLCompatibility": false or true - - slightly change the whitespace around colons - - "dropNullPlaceholders": false or true - - Drop the "null" string from the writer's output for nullValues. - Strictly speaking, this is not valid JSON. But when the output is being - fed to a browser's Javascript, it makes for smaller output and the - browser can handle the output just fine. - - You can examine 'settings_` yourself - to see the defaults. You can also write and read them just like any - JSON Value. - \sa setDefaults() - */ - Json::Value settings_; - - StreamWriterBuilder(); - virtual ~StreamWriterBuilder(); - - /** - * \throw std::exception if something goes wrong (e.g. invalid settings) - */ - virtual StreamWriter* newStreamWriter() const; - - /** \return true if 'settings' are legal and consistent; - * otherwise, indicate bad settings via 'invalid'. - */ - bool validate(Json::Value* invalid) const; - /** A simple way to update a specific setting. - */ - Value& operator[](std::string key); - - /** Called by ctor, but you can use this to reset settings_. - * \pre 'settings' != NULL (but Json::null is fine) - * \remark Defaults: - * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults - */ - static void setDefaults(Json::Value* settings); -}; - -/** \brief Abstract class for writers. - * \deprecated Use StreamWriter. (And really, this is an implementation detail.) - */ -class JSON_API Writer { -public: - virtual ~Writer(); - - virtual std::string write(const Value& root) = 0; -}; - -/** \brief Outputs a Value in JSON format - *without formatting (not human friendly). - * - * The JSON document is written in a single line. It is not intended for 'human' - *consumption, - * but may be usefull to support feature such as RPC where bandwith is limited. - * \sa Reader, Value - * \deprecated Use StreamWriterBuilder. - */ -class JSON_API FastWriter : public Writer { - -public: - FastWriter(); - virtual ~FastWriter() {} - - void enableYAMLCompatibility(); - - /** \brief Drop the "null" string from the writer's output for nullValues. - * Strictly speaking, this is not valid JSON. But when the output is being - * fed to a browser's Javascript, it makes for smaller output and the - * browser can handle the output just fine. - */ - void dropNullPlaceholders(); - - void omitEndingLineFeed(); - -public: // overridden from Writer - virtual std::string write(const Value& root); - -private: - void writeValue(const Value& value); - - std::string document_; - bool yamlCompatiblityEnabled_; - bool dropNullPlaceholders_; - bool omitEndingLineFeed_; -}; - -/** \brief Writes a Value in JSON format in a - *human friendly way. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per - *line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value - *types, - * and all the values fit on one lines, then print the array on a single - *line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their - *#CommentPlacement. - * - * \sa Reader, Value, Value::setComment() - * \deprecated Use StreamWriterBuilder. - */ -class JSON_API StyledWriter : public Writer { -public: - StyledWriter(); - virtual ~StyledWriter() {} - -public: // overridden from Writer - /** \brief Serialize a Value in JSON format. - * \param root Value to serialize. - * \return String containing the JSON document that represents the root value. - */ - virtual std::string write(const Value& root); - -private: - void writeValue(const Value& value); - void writeArrayValue(const Value& value); - bool isMultineArray(const Value& value); - void pushValue(const std::string& value); - void writeIndent(); - void writeWithIndent(const std::string& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(const Value& root); - void writeCommentAfterValueOnSameLine(const Value& root); - bool hasCommentForValue(const Value& value); - static std::string normalizeEOL(const std::string& text); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::string document_; - std::string indentString_; - int rightMargin_; - int indentSize_; - bool addChildValues_; -}; - -/** \brief Writes a Value in JSON format in a - human friendly way, - to a stream rather than to a string. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per - line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value - types, - * and all the values fit on one lines, then print the array on a single - line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their - #CommentPlacement. - * - * \param indentation Each level will be indented by this amount extra. - * \sa Reader, Value, Value::setComment() - * \deprecated Use StreamWriterBuilder. - */ -class JSON_API StyledStreamWriter { -public: - StyledStreamWriter(std::string indentation = "\t"); - ~StyledStreamWriter() {} - -public: - /** \brief Serialize a Value in JSON format. - * \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not - * return a value. - */ - void write(std::ostream& out, const Value& root); - -private: - void writeValue(const Value& value); - void writeArrayValue(const Value& value); - bool isMultineArray(const Value& value); - void pushValue(const std::string& value); - void writeIndent(); - void writeWithIndent(const std::string& value); - void indent(); - void unindent(); - void writeCommentBeforeValue(const Value& root); - void writeCommentAfterValueOnSameLine(const Value& root); - bool hasCommentForValue(const Value& value); - static std::string normalizeEOL(const std::string& text); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::ostream* document_; - std::string indentString_; - int rightMargin_; - std::string indentation_; - bool addChildValues_ : 1; - bool indented_ : 1; -}; - -#if defined(JSON_HAS_INT64) -std::string JSON_API valueToString(Int value); -std::string JSON_API valueToString(UInt value); -#endif // if defined(JSON_HAS_INT64) -std::string JSON_API valueToString(LargestInt value); -std::string JSON_API valueToString(LargestUInt value); -std::string JSON_API valueToString(double value); -std::string JSON_API valueToString(bool value); -std::string JSON_API valueToQuotedString(const char* value); - -/// \brief Output using the StyledStreamWriter. -/// \see Json::operator>>() -JSON_API std::ostream& operator<<(std::ostream&, const Value& root); - -} // namespace Json - -#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma warning(pop) -#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) - -#endif // JSON_WRITER_H_INCLUDED diff --git a/3rdpart/media-server b/3rdpart/media-server index 8dc2fbf2..357ef885 160000 --- a/3rdpart/media-server +++ b/3rdpart/media-server @@ -1 +1 @@ -Subproject commit 8dc2fbf275628ab306a0976ac54533dd0e181f65 +Subproject commit 357ef885afa5112c8cb873eab21d94d26744c0ab diff --git a/Android/app/build.gradle b/Android/app/build.gradle index 4764f8ce..4fb582df 100644 --- a/Android/app/build.gradle +++ b/Android/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 28 - ndkVersion "25.0.8775105" + //ndkVersion "25.1.xxx" defaultConfig { applicationId "com.zlmediakit.demo" minSdkVersion 15 diff --git a/Android/app/src/main/cpp/native-lib.cpp b/Android/app/src/main/cpp/native-lib.cpp index 5713b11e..9eb0ca17 100644 --- a/Android/app/src/main/cpp/native-lib.cpp +++ b/Android/app/src/main/cpp/native-lib.cpp @@ -177,11 +177,11 @@ JNI_API(jboolean, startDemo, jstring ini_dir){ s_tread_id = pthread_self(); try { //http根目录修改默认路径 - mINI::Instance()[Http::kRootPath] = mINI::Instance()[Hls::kFilePath] = sd_path + "/httpRoot"; + mINI::Instance()[Http::kRootPath] = sd_path + "/httpRoot"; //mp4录制点播根目录修改默认路径 - mINI::Instance()[Record::kFilePath] = mINI::Instance()[Hls::kFilePath] = sd_path + "/httpRoot"; + mINI::Instance()[Protocol::kMP4SavePath] = sd_path + "/httpRoot"; //hls根目录修改默认路径 - mINI::Instance()[Hls::kFilePath] = mINI::Instance()[Hls::kFilePath] = sd_path + "/httpRoot"; + mINI::Instance()[Protocol::kHlsSavePath] = sd_path + "/httpRoot"; //替换默认端口号(在配置文件未生成时有效) mINI::Instance()["http.port"] = 8080; mINI::Instance()["http.sslport"] = 8443; @@ -232,18 +232,18 @@ JNI_API(jlong, createMediaPlayer, jstring url, jobject callback){ auto viedoTrack = strongPlayer->getTrack(TrackVideo); if (viedoTrack) { - viedoTrack->addDelegate(std::make_shared([globalWeakRef](const Frame::Ptr &frame) { + viedoTrack->addDelegate([globalWeakRef](const Frame::Ptr &frame) { emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame)); return true; - })); + }); } auto audioTrack = strongPlayer->getTrack(TrackAudio); if (audioTrack) { - audioTrack->addDelegate(std::make_shared([globalWeakRef](const Frame::Ptr &frame) { + audioTrack->addDelegate([globalWeakRef](const Frame::Ptr &frame) { emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame)); return true; - })); + }); } }); diff --git a/CMakeLists.txt b/CMakeLists.txt index b2fd2e04..adc5f768 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ option(ENABLE_SRT "Enable SRT" ON) option(ENABLE_TESTS "Enable Tests" ON) option(ENABLE_WEBRTC "Enable WebRTC" ON) option(ENABLE_X264 "Enable x264" OFF) -option(ENABLE_WEPOLL "Enable wepoll" OFF) +option(ENABLE_WEPOLL "Enable wepoll" ON) option(USE_SOLUTION_FOLDERS "Enable solution dir supported" ON) @@ -97,11 +97,12 @@ set(LIBRARY_OUTPUT_PATH ${OUTPUT_DIR}) set(EXECUTABLE_OUTPUT_PATH ${OUTPUT_DIR}) # 添加 git 版本信息 -set(COMMIT_HASH "Git_NotFound_Unkown_commit") -set(BRANCH_NAME "Git_NotFound_Unkown_branch") +set(COMMIT_HASH "Git_Unkown_commit") +set(COMMIT_TIME "Git_Unkown_time") +set(BRANCH_NAME "Git_Unkown_branch") set(BUILD_TIME "") -string(TIMESTAMP BUILD_TIME "%Y-%m-%d %H:%M:%S") +string(TIMESTAMP BUILD_TIME "%Y-%m-%dT%H:%M:%S") find_package(Git QUIET) if(GIT_FOUND) @@ -117,6 +118,13 @@ if(GIT_FOUND) OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + + execute_process( + COMMAND ${GIT_EXECUTABLE} log --format=format:%aI -1 + OUTPUT_VARIABLE COMMIT_TIME + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif() configure_file( @@ -124,7 +132,7 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) -message(STATUS "Git version is ${BRANCH_NAME}:${COMMIT_HASH}:${BUILD_TIME}") +message(STATUS "Git version is ${BRANCH_NAME} ${COMMIT_HASH}/${COMMIT_TIME} ${BUILD_TIME}") ############################################################################## @@ -172,12 +180,12 @@ if(UNIX) "-Wno-unused-function;-Wno-unused-parameter;-Wno-unused-variable" "-Wno-error=extra;-Wno-error=missing-field-initializers;-Wno-error=type-limits") elseif(WIN32) - set(COMPILE_OPTIONS_DEFAULT - # TODO: /wd4819 应该是不会生效 - "/wd4566;/wd4819" - # warning C4530: C++ exception handler used, but unwind semantics are not enabled. - "/EHsc") if (MSVC) + set(COMPILE_OPTIONS_DEFAULT + # TODO: /wd4819 应该是不会生效 + "/wd4566;/wd4819" + # warning C4530: C++ exception handler used, but unwind semantics are not enabled. + "/EHsc") # disable Windows logo list(APPEND COMPILE_OPTIONS_DEFAULT "/nologo") list(APPEND CMAKE_STATIC_LINKER_FLAGS "/nologo") @@ -299,7 +307,7 @@ if(ENABLE_FFMPEG) endif() if(ENABLE_MEM_DEBUG) - list(APPEND COMPILE_OPTIONS_DEFAULT + update_cached_list(MK_LINK_LIBRARIES "-Wl,-wrap,free;-Wl,-wrap,malloc;-Wl,-wrap,realloc;-Wl,-wrap,calloc") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_MEM_DEBUG) message(STATUS "已启用内存调试功能") @@ -308,6 +316,10 @@ endif() if(ENABLE_ASAN) list(APPEND COMPILE_OPTIONS_DEFAULT "-fsanitize=address;-fno-omit-frame-pointer") + # https://github.com/google/sanitizers/wiki/AddressSanitizer#using-addresssanitizer + # > In order to use AddressSanitizer you will need to + # > compile and link your program using clang with the -fsanitize=address switch. + update_cached_list(MK_LINK_LIBRARIES "-fsanitize=address") message(STATUS "已启用 Address Sanitize") endif() @@ -413,6 +425,9 @@ endif() # for version.h include_directories(${CMAKE_CURRENT_BINARY_DIR}) +# for assert.h +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdpart) + add_subdirectory(3rdpart) add_subdirectory(src) diff --git a/README.md b/README.md index 64a74930..dad412ac 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ ## 功能清单 ### 功能一览 -功能一览 +功能一览 - RTSP[S] - RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 @@ -92,10 +92,11 @@ - 支持http文件访问鉴权 - GB28181与RTP推流 - - 支持UDP/TCP国标RTP(PS或TS)推流服务器,可以转换成RTSP/RTMP/HLS等协议 - - 支持RTSP/RTMP/HLS转国标推流客户端,支持TCP/UDP模式,提供相应restful api + - 支持UDP/TCP RTP(PS/TS/ES)推流服务器,可以转换成RTSP/RTMP/HLS等协议 + - 支持RTSP/RTMP/HLS等协议转rtp推流客户端,支持TCP/UDP模式,提供相应restful api,支持主动被动方式。 - 支持H264/H265/AAC/G711/OPUS编码 - 支持海康ehome推流 + - 支持GB28181主动拉流模式 - MP4点播与录制 - 支持录制为FLV/HLS/MP4 @@ -114,6 +115,7 @@ - 支持rtp扩展解析 - 支持GOP缓冲,webrtc播放秒开 - 支持datachannel + - 支持webrtc over tcp模式 - [SRT支持](./srt/srt.md) - 其他 - 支持丰富的restful api以及web hook事件 diff --git a/README_en.md b/README_en.md index f9c56da9..dd031c10 100644 --- a/README_en.md +++ b/README_en.md @@ -213,9 +213,9 @@ git submodule update --init WarnL << "none video Track!"; return; } - viedoTrack->addDelegate(std::make_shared([](const Frame::Ptr &frame) { + viedoTrack->addDelegate([](const Frame::Ptr &frame) { //please decode video here - })); + }); }); player->setOnShutdown([](const SockException &ex) { diff --git a/_config.yml b/_config.yml deleted file mode 100644 index c4192631..00000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-cayman \ No newline at end of file diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt index 6f46635d..42061925 100644 --- a/api/CMakeLists.txt +++ b/api/CMakeLists.txt @@ -28,7 +28,7 @@ file(GLOB API_SRC_LIST source/*.cpp source/*.h) -set(LINK_LIBRARIES ${MK_LINK_LIBRARIES} jsoncpp) +set(LINK_LIBRARIES ${MK_LINK_LIBRARIES}) if(IOS) add_library(mk_api STATIC ${API_SRC_LIST}) @@ -38,7 +38,10 @@ if(IOS) endif () set(COMPILE_DEFINITIONS ${MK_COMPILE_DEFINITIONS}) -list(APPEND COMPILE_DEFINITIONS GENERATE_EXPORT) + +if (MSVC) + list(APPEND COMPILE_DEFINITIONS GENERATE_EXPORT) +endif () if(ENABLE_API_STATIC_LIB) add_library(mk_api STATIC ${API_SRC_LIST}) @@ -62,7 +65,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux") elseif(CMAKE_SYSTEM_NAME MATCHES "Android") target_link_libraries(mk_api log -Wl,--start-group ${LINK_LIBRARIES} -Wl,--end-group) else() - target_link_libraries(mk_api jsoncpp ${LINK_LIBRARIES}) + target_link_libraries(mk_api ${LINK_LIBRARIES}) endif() generate_export_header(mk_api diff --git a/api/include/mk_common.h b/api/include/mk_common.h index 7105f0d3..0ee50b66 100755 --- a/api/include/mk_common.h +++ b/api/include/mk_common.h @@ -162,11 +162,32 @@ API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port); /** * 创建rtc服务器 - * @param port rtp监听端口 + * @param port rtc监听端口 * @return 0:失败,非0:端口号 */ API_EXPORT uint16_t API_CALL mk_rtc_server_start(uint16_t port); +//获取webrtc answer sdp回调函数 +typedef void(API_CALL *on_mk_webrtc_get_answer_sdp)(void *user_data, const char *answer, const char *err); + +/** + * webrtc交换sdp,根据offer sdp生成answer sdp + * @param user_data 回调用户指针 + * @param cb 回调函数 + * @param type webrtc插件类型,支持echo,play,push + * @param offer webrtc offer sdp + * @param url rtc url, 例如 rtc://__defaultVhost/app/stream?key1=val1&key2=val2 + */ +API_EXPORT void API_CALL mk_webrtc_get_answer_sdp(void *user_data, on_mk_webrtc_get_answer_sdp cb, const char *type, + const char *offer, const char *url); + +/** + * 创建srt服务器 + * @param port srt监听端口 + * @return 0:失败,非0:端口号 + */ +API_EXPORT uint16_t API_CALL mk_srt_server_start(uint16_t port); + /** * 创建shell服务器 diff --git a/api/include/mk_events.h b/api/include/mk_events.h index 2692ccd2..c628a0ec 100644 --- a/api/include/mk_events.h +++ b/api/include/mk_events.h @@ -53,8 +53,10 @@ typedef struct { * 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 * @param url_info 播放url相关信息 * @param sender 播放客户端相关信息 + * @return 1 直接关闭 + * 0 等待流注册 */ - void (API_CALL *on_mk_media_not_found)(const mk_media_info url_info, + int (API_CALL *on_mk_media_not_found)(const mk_media_info url_info, const mk_sock_info sender); /** @@ -152,7 +154,7 @@ typedef struct { size_t total_seconds, int is_player, const mk_sock_info sender); - + /** * 日志输出广播 @@ -178,4 +180,3 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events); } #endif #endif //MK_EVENTS_H - diff --git a/api/include/mk_events_objects.h b/api/include/mk_events_objects.h index 5913c080..d2fc2a57 100644 --- a/api/include/mk_events_objects.h +++ b/api/include/mk_events_objects.h @@ -177,14 +177,6 @@ API_EXPORT void API_CALL mk_http_response_invoker_do(const mk_http_response_invo int response_code, const char **response_header, const mk_http_body response_body); -/** - * HttpSession::HttpResponseInvoker(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body); - Parser(); - SockInfo(); - */ -API_EXPORT void API_CALL mk_webrtc_http_response_invoker_do( const mk_http_response_invoker invoker, - const mk_parser parser, - const mk_sock_info sender); /** * HttpSession::HttpResponseInvoker(const string &codeOut, const StrCaseMap &headerOut, const string &body); diff --git a/api/include/mk_rtp_server.h b/api/include/mk_rtp_server.h index bd7f594c..20a0c5ef 100644 --- a/api/include/mk_rtp_server.h +++ b/api/include/mk_rtp_server.h @@ -19,11 +19,27 @@ typedef void* mk_rtp_server; /** * 创建GB28181 RTP 服务器 * @param port 监听端口,0则为随机 - * @param enable_tcp 创建udp端口时是否同时监听tcp端口 + * @param tcp_mode tcp模式(0: 不监听端口 1: 监听端口 2: 主动连接到服务端) * @param stream_id 该端口绑定的流id * @return */ -API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable_tcp, const char *stream_id); +API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int tcp_mode, const char *stream_id); + +/** + * TCP 主动模式时连接到服务器是否成功的回调 + */ +typedef void(API_CALL *on_mk_rtp_server_connected)(void *user_data, int err, const char *what, int sys_err); + +/** + * TCP 主动模式时连接到服务器 + * @param @param ctx 服务器对象 + * @param dst_url 服务端地址 + * @param dst_port 服务端端口 + * @param cb 连接到服务器是否成功的回调 + * @param user_data 用户数据指针 + * @return + */ +API_EXPORT void API_CALL mk_rtp_server_connect(mk_rtp_server ctx, const char *dst_url, uint16_t dst_port, on_mk_rtp_server_connected cb, void *user_data); /** * 销毁GB28181 RTP 服务器 diff --git a/api/source/mk_common.cpp b/api/source/mk_common.cpp index 09a0d4fa..59bbfde7 100644 --- a/api/source/mk_common.cpp +++ b/api/source/mk_common.cpp @@ -37,7 +37,13 @@ static std::shared_ptr rtpServer; #ifdef ENABLE_WEBRTC #include "../webrtc/WebRtcSession.h" -static std::shared_ptr rtcServer; +static std::shared_ptr rtcServer_udp; +static std::shared_ptr rtcServer_tcp; +#endif + +#if defined(ENABLE_SRT) +#include "../srt/SrtSession.hpp" +static std::shared_ptr srtServer; #endif //////////////////////////environment init/////////////////////////// @@ -62,8 +68,16 @@ API_EXPORT void API_CALL mk_stop_all_server(){ CLEAR_ARR(rtsp_server); CLEAR_ARR(rtmp_server); CLEAR_ARR(http_server); + shell_server = nullptr; #ifdef ENABLE_RTPPROXY rtpServer = nullptr; +#endif +#ifdef ENABLE_WEBRTC + rtcServer_udp = nullptr; + rtcServer_tcp = nullptr; +#endif +#ifdef ENABLE_SRT + srtServer = nullptr; #endif stopAllTcpServer(); } @@ -160,13 +174,13 @@ API_EXPORT uint16_t API_CALL mk_http_server_start(uint16_t port, int ssl) { try { http_server[ssl] = std::make_shared(); if(ssl){ - http_server[ssl]->start >(port); + http_server[ssl]->start >(port); } else{ http_server[ssl]->start(port); } return http_server[ssl]->getPort(); } catch (std::exception &ex) { - http_server[ssl].reset(); + http_server[ssl] = nullptr;; WarnL << ex.what(); return 0; } @@ -177,13 +191,13 @@ API_EXPORT uint16_t API_CALL mk_rtsp_server_start(uint16_t port, int ssl) { try { rtsp_server[ssl] = std::make_shared(); if(ssl){ - rtsp_server[ssl]->start >(port); + rtsp_server[ssl]->start >(port); }else{ rtsp_server[ssl]->start(port); } return rtsp_server[ssl]->getPort(); } catch (std::exception &ex) { - rtsp_server[ssl].reset(); + rtsp_server[ssl] = nullptr;; WarnL << ex.what(); return 0; } @@ -194,13 +208,13 @@ API_EXPORT uint16_t API_CALL mk_rtmp_server_start(uint16_t port, int ssl) { try { rtmp_server[ssl] = std::make_shared(); if(ssl){ - rtmp_server[ssl]->start >(port); + rtmp_server[ssl]->start >(port); }else{ rtmp_server[ssl]->start(port); } return rtmp_server[ssl]->getPort(); } catch (std::exception &ex) { - rtmp_server[ssl].reset(); + rtmp_server[ssl] = nullptr;; WarnL << ex.what(); return 0; } @@ -214,7 +228,7 @@ API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port){ rtpServer->start(port); return rtpServer->getPort(); } catch (std::exception &ex) { - rtpServer.reset(); + rtpServer = nullptr;; WarnL << ex.what(); return 0; } @@ -227,9 +241,9 @@ API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port){ API_EXPORT uint16_t API_CALL mk_rtc_server_start(uint16_t port) { #ifdef ENABLE_WEBRTC try { - //创建rtc服务器 - rtcServer = std::make_shared(); - rtcServer->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) { + //创建rtc udp服务器 + rtcServer_udp = std::make_shared(); + rtcServer_udp->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) { if (!buf) { return Socket::createSocket(poller, false); } @@ -240,11 +254,82 @@ API_EXPORT uint16_t API_CALL mk_rtc_server_start(uint16_t port) { } return Socket::createSocket(new_poller, false); }); - rtcServer->start(port); - return rtcServer->getPort(); + rtcServer_udp->start(port); + //创建rtc tcp服务器 + rtcServer_tcp = std::make_shared(); + rtcServer_tcp->start(rtcServer_udp->getPort()); + return rtcServer_udp->getPort(); } catch (std::exception &ex) { - rtcServer.reset(); + rtcServer_udp = nullptr; + rtcServer_tcp = nullptr; + WarnL << ex.what(); + return 0; + } +#else + WarnL << "未启用webrtc功能, 编译时请开启ENABLE_WEBRTC"; + return 0; +#endif +} + +#ifdef ENABLE_WEBRTC +class WebRtcArgsUrl : public mediakit::WebRtcArgs { +public: + WebRtcArgsUrl(std::string url) { _url = std::move(url); } + ~WebRtcArgsUrl() = default; + + toolkit::variant operator[](const std::string &key) const override { + if (key == "url") { + return _url; + } + return ""; + } + +private: + std::string _url; +}; +#endif + +API_EXPORT void API_CALL mk_webrtc_get_answer_sdp(void *user_data, on_mk_webrtc_get_answer_sdp cb, const char *type, + const char *offer, const char *url) { +#ifdef ENABLE_WEBRTC + assert(type && offer && url && cb); + auto session = std::make_shared(Socket::createSocket()); + std::string offer_str = offer; + WebRtcPluginManager::Instance().getAnswerSdp(*session, type, WebRtcArgsUrl(url), + [offer_str, session, user_data, cb](const WebRtcInterface &exchanger) mutable { + try { + auto sdp_answer = const_cast(exchanger).getAnswerSdp(offer_str); + cb(user_data, sdp_answer.data(), nullptr); + } catch (std::exception &ex) { + cb(user_data, nullptr, ex.what()); + } + }); +#else + WarnL << "未启用webrtc功能, 编译时请开启ENABLE_WEBRTC"; +#endif +} + +API_EXPORT uint16_t API_CALL mk_srt_server_start(uint16_t port) { +#ifdef ENABLE_SRT + try { + srtServer = std::make_shared(); + srtServer->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) { + if (!buf) { + return Socket::createSocket(poller, false); + } + auto new_poller = SRT::SrtSession::queryPoller(buf); + if (!new_poller) { + //握手第一阶段 + return Socket::createSocket(poller, false); + } + return Socket::createSocket(new_poller, false); + }); + srtServer->start(port); + return srtServer->getPort(); + + } catch (std::exception &ex) { + srtServer = nullptr;; WarnL << ex.what(); return 0; } @@ -260,7 +345,7 @@ API_EXPORT uint16_t API_CALL mk_shell_server_start(uint16_t port){ shell_server->start(port); return shell_server->getPort(); } catch (std::exception &ex) { - shell_server.reset(); + shell_server = nullptr;; WarnL << ex.what(); return 0; } diff --git a/api/source/mk_events.cpp b/api/source/mk_events.cpp index cd68419d..43e3362c 100644 --- a/api/source/mk_events.cpp +++ b/api/source/mk_events.cpp @@ -141,8 +141,10 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){ NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){ if (s_events.on_mk_media_not_found) { - s_events.on_mk_media_not_found((mk_media_info) &args, - (mk_sock_info) &sender); + if (s_events.on_mk_media_not_found((mk_media_info) &args, + (mk_sock_info) &sender)) { + closePlayer(); + } } }); diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp index 2523b86c..768b2aa9 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -18,11 +18,6 @@ #include "Http/HttpClient.h" #include "Rtsp/RtspSession.h" -#ifdef ENABLE_WEBRTC -#include "jsoncpp/json.h" -#include "mk_webrtc_private.h" -#endif - using namespace toolkit; using namespace mediakit; @@ -330,53 +325,6 @@ API_EXPORT void API_CALL mk_http_response_invoker_do(const mk_http_response_invo (*invoker)(response_code,header,*body); } -API_EXPORT void API_CALL mk_webrtc_http_response_invoker_do(const mk_http_response_invoker ctx_invoker, - const mk_parser ctx_parser, - const mk_sock_info ctx_sock ) { - assert(ctx_parser && ctx_invoker && ctx_sock); -#ifdef ENABLE_WEBRTC - static auto webrtc_cb = [](API_ARGS_STRING_ASYNC){ - CHECK_ARGS("type"); - auto type = allArgs["type"]; - auto offer = allArgs.getArgs(); - CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty"); - - WebRtcPluginManager::Instance().getAnswerSdp( - *(static_cast(&sender)), type, offer, WebRtcArgsImp(allArgs, sender.getIdentifier()), - [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable { - //设置返回类型 - headerOut["Content-Type"] = HttpFileManager::getContentType(".json"); - //设置跨域 - headerOut["Access-Control-Allow-Origin"] = "*"; - - try { - val["sdp"] = const_cast(exchanger).getAnswerSdp(offer); - val["id"] = exchanger.getIdentifier(); - val["type"] = "answer"; - invoker(200, headerOut, val.toStyledString()); - } catch (std::exception &ex) { - val["code"] = API::Exception; - val["msg"] = ex.what(); - invoker(200, headerOut, val.toStyledString()); - } - }); - }; - - Parser *parser = (Parser *)ctx_parser; - HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx_invoker; - SockInfo* sender = (SockInfo*)ctx_sock; - - GET_CONFIG(std::string, charSet, Http::kCharSet); - HttpSession::KeyValue headerOut; - headerOut["Content-Type"] = std::string("application/json; charset=") + charSet; - - Json::Value val; - val["code"] = API::Success; - - webrtc_cb(*sender, headerOut, HttpAllArgs(*parser, (std::string &)parser->Content()), val, *invoker); -#endif -}; - API_EXPORT mk_http_response_invoker API_CALL mk_http_response_invoker_clone(const mk_http_response_invoker ctx){ assert(ctx); HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx; diff --git a/api/source/mk_frame.cpp b/api/source/mk_frame.cpp index 367e8cc3..ff45da07 100644 --- a/api/source/mk_frame.cpp +++ b/api/source/mk_frame.cpp @@ -161,16 +161,16 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) { auto &ref = *((Frame::Ptr *) frame); uint32_t ret = 0; if (ref->keyFrame()) { - ret &= MK_FRAME_FLAG_IS_KEY; + ret |= MK_FRAME_FLAG_IS_KEY; } if (ref->configFrame()) { - ret &= MK_FRAME_FLAG_IS_CONFIG; + ret |= MK_FRAME_FLAG_IS_CONFIG; } if (ref->dropAble()) { - ret &= MK_FRAME_FLAG_DROP_ABLE; + ret |= MK_FRAME_FLAG_DROP_ABLE; } if (!ref->decodeAble()) { - ret &= MK_FRAME_FLAG_NOT_DECODE_ABLE; + ret |= MK_FRAME_FLAG_NOT_DECODE_ABLE; } return ret; -} \ No newline at end of file +} diff --git a/api/source/mk_media.cpp b/api/source/mk_media.cpp index 93a0929b..c6c97e4c 100755 --- a/api/source/mk_media.cpp +++ b/api/source/mk_media.cpp @@ -60,19 +60,15 @@ public: protected: // 通知其停止推流 - bool close(MediaSource &sender,bool force) override{ - if(!force && _channel->totalReaderCount()){ - //非强制关闭且正有人在观看该视频 - return false; - } - if(!_on_close){ + bool close(MediaSource &sender) override { + if (!_on_close) { //未设置回调,没法关闭 WarnL << "请使用mk_media_set_on_close函数设置回调函数!"; return false; } //请在回调中调用mk_media_release函数释放资源,否则MediaSource::close()操作不会生效 _on_close(_on_close_data); - WarnL << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + WarnL << "close media: " << sender.getUrl(); return true; } @@ -262,7 +258,7 @@ API_EXPORT void API_CALL mk_media_start_send_rtp(mk_media ctx, const char *dst_u // sender参数无用 auto ref = *obj; - (*obj)->getOwnerPoller(MediaSource::NullMediaSource())->async([args, ref, cb, user_data]() { + (*obj)->getChannel()->getOwnerPoller(MediaSource::NullMediaSource())->async([args, ref, cb, user_data]() { ref->getChannel()->startSendRtp(MediaSource::NullMediaSource(), args, [cb, user_data](uint16_t local_port, const SockException &ex) { if (cb) { cb(user_data, local_port, ex.getErrCode(), ex.what()); @@ -277,12 +273,12 @@ API_EXPORT void API_CALL mk_media_stop_send_rtp(mk_media ctx, const char *ssrc){ // sender参数无用 auto ref = *obj; string ssrc_str = ssrc ? ssrc : ""; - (*obj)->getOwnerPoller(MediaSource::NullMediaSource())->async([ref, ssrc_str]() { + (*obj)->getChannel()->getOwnerPoller(MediaSource::NullMediaSource())->async([ref, ssrc_str]() { ref->getChannel()->stopSendRtp(MediaSource::NullMediaSource(), ssrc_str); }); } API_EXPORT mk_thread API_CALL mk_media_get_owner_thread(mk_media ctx) { MediaHelper::Ptr *obj = (MediaHelper::Ptr *)ctx; - return (*obj)->getOwnerPoller(MediaSource::NullMediaSource()).get(); + return (*obj)->getChannel()->getOwnerPoller(MediaSource::NullMediaSource()).get(); } \ No newline at end of file diff --git a/api/source/mk_rtp_server.cpp b/api/source/mk_rtp_server.cpp index 16dfa74d..617418c6 100644 --- a/api/source/mk_rtp_server.cpp +++ b/api/source/mk_rtp_server.cpp @@ -16,23 +16,34 @@ using namespace toolkit; #include "Rtp/RtpServer.h" using namespace mediakit; -API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable_tcp, const char *stream_id){ +API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int tcp_mode, const char *stream_id) { RtpServer::Ptr *server = new RtpServer::Ptr(new RtpServer); - (*server)->start(port, stream_id, enable_tcp); + (*server)->start(port, stream_id, (RtpServer::TcpMode)tcp_mode); return server; } -API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx){ +API_EXPORT void API_CALL mk_rtp_server_connect(mk_rtp_server ctx, const char *dst_url, uint16_t dst_port, on_mk_rtp_server_connected cb, void *user_data) { + RtpServer::Ptr *server = (RtpServer::Ptr *)ctx; + if (server) { + (*server)->connectToServer(dst_url, dst_port, [cb, user_data](const SockException &ex) { + if (cb) { + cb(user_data, ex.getErrCode(), ex.what(), ex.getCustomCode()); + } + }); + } +} + +API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx) { RtpServer::Ptr *server = (RtpServer::Ptr *)ctx; delete server; } -API_EXPORT uint16_t API_CALL mk_rtp_server_port(mk_rtp_server ctx){ +API_EXPORT uint16_t API_CALL mk_rtp_server_port(mk_rtp_server ctx) { RtpServer::Ptr *server = (RtpServer::Ptr *)ctx; return (*server)->getPort(); } -API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rtp_server_detach cb, void *user_data){ +API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rtp_server_detach cb, void *user_data) { RtpServer::Ptr *server = (RtpServer::Ptr *) ctx; if (cb) { (*server)->setOnDetach([cb, user_data]() { @@ -45,21 +56,21 @@ API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rt #else -API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable_tcp, const char *stream_id){ +API_EXPORT mk_rtp_server API_CALL mk_rtp_server_create(uint16_t port, int enable_tcp, const char *stream_id) { WarnL << "请打开ENABLE_RTPPROXY后再编译"; return nullptr; } -API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx){ +API_EXPORT void API_CALL mk_rtp_server_release(mk_rtp_server ctx) { WarnL << "请打开ENABLE_RTPPROXY后再编译"; } -API_EXPORT uint16_t API_CALL mk_rtp_server_port(mk_rtp_server ctx){ +API_EXPORT uint16_t API_CALL mk_rtp_server_port(mk_rtp_server ctx) { WarnL << "请打开ENABLE_RTPPROXY后再编译"; return 0; } -API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rtp_server_detach cb, void *user_data){ +API_EXPORT void API_CALL mk_rtp_server_set_on_detach(mk_rtp_server ctx, on_mk_rtp_server_detach cb, void *user_data) { WarnL << "请打开ENABLE_RTPPROXY后再编译"; } diff --git a/api/source/mk_tcp.cpp b/api/source/mk_tcp.cpp index ea15fdb5..2e2dc6e1 100644 --- a/api/source/mk_tcp.cpp +++ b/api/source/mk_tcp.cpp @@ -109,19 +109,19 @@ API_EXPORT uint16_t API_CALL mk_sock_info_local_port(const mk_sock_info ctx){ //////////////////////////////////////////////////////////////////////////////////////// API_EXPORT mk_sock_info API_CALL mk_tcp_session_get_sock_info(const mk_tcp_session ctx){ assert(ctx); - TcpSessionForC *session = (TcpSessionForC *)ctx; + SessionForC *session = (SessionForC *)ctx; return (SockInfo *)session; } API_EXPORT void API_CALL mk_tcp_session_shutdown(const mk_tcp_session ctx,int err,const char *err_msg){ assert(ctx); - TcpSessionForC *session = (TcpSessionForC *)ctx; + SessionForC *session = (SessionForC *)ctx; session->safeShutdown(SockException((ErrCode)err,err_msg)); } API_EXPORT void API_CALL mk_tcp_session_send_buffer(const mk_tcp_session ctx, mk_buffer buffer) { assert(ctx && buffer); - TcpSessionForC *session = (TcpSessionForC *) ctx; + SessionForC *session = (SessionForC *) ctx; session->send(*((Buffer::Ptr *) buffer)); } @@ -134,9 +134,9 @@ API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx, const cha API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ctx, mk_buffer buffer) { assert(ctx && buffer); try { - std::weak_ptr weak_session = ((TcpSessionForC *) ctx)->shared_from_this(); + std::weak_ptr weak_session = ((SessionForC *) ctx)->shared_from_this(); auto ref = mk_buffer_ref(buffer); - ((TcpSessionForC *) ctx)->async([weak_session, ref]() { + ((SessionForC *) ctx)->async([weak_session, ref]() { auto session_session = weak_session.lock(); if (session_session) { session_session->send(*((Buffer::Ptr *) ref)); @@ -149,16 +149,16 @@ API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ct } API_EXPORT mk_tcp_session_ref API_CALL mk_tcp_session_ref_from(const mk_tcp_session ctx) { - auto ref = ((TcpSessionForC *) ctx)->shared_from_this(); - return new std::shared_ptr(std::dynamic_pointer_cast(ref)); + auto ref = ((SessionForC *) ctx)->shared_from_this(); + return new std::shared_ptr(std::dynamic_pointer_cast(ref)); } API_EXPORT void mk_tcp_session_ref_release(const mk_tcp_session_ref ref) { - delete (std::shared_ptr *) ref; + delete (std::shared_ptr *) ref; } API_EXPORT mk_tcp_session mk_tcp_session_from_ref(const mk_tcp_session_ref ref) { - return ((std::shared_ptr *) ref)->get(); + return ((std::shared_ptr *) ref)->get(); } API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx, const char *data, size_t len) { @@ -167,30 +167,30 @@ API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx, cons mk_buffer_unref(buffer); } -////////////////////////////////////////TcpSessionForC//////////////////////////////////////////////// +////////////////////////////////////////SessionForC//////////////////////////////////////////////// static TcpServer::Ptr s_tcp_server[4]; static mk_tcp_session_events s_events_server = {0}; -TcpSessionForC::TcpSessionForC(const Socket::Ptr &pSock) : TcpSession(pSock) { +SessionForC::SessionForC(const Socket::Ptr &pSock) : Session(pSock) { _local_port = get_local_port(); if (s_events_server.on_mk_tcp_session_create) { s_events_server.on_mk_tcp_session_create(_local_port,this); } } -void TcpSessionForC::onRecv(const Buffer::Ptr &buffer) { +void SessionForC::onRecv(const Buffer::Ptr &buffer) { if (s_events_server.on_mk_tcp_session_data) { s_events_server.on_mk_tcp_session_data(_local_port, this, (mk_buffer)&buffer); } } -void TcpSessionForC::onError(const SockException &err) { +void SessionForC::onError(const SockException &err) { if (s_events_server.on_mk_tcp_session_disconnect) { s_events_server.on_mk_tcp_session_disconnect(_local_port,this, err.getErrCode(), err.what()); } } -void TcpSessionForC::onManager() { +void SessionForC::onManager() { if (s_events_server.on_mk_tcp_session_manager) { s_events_server.on_mk_tcp_session_manager(_local_port,this); } @@ -202,13 +202,13 @@ void stopAllTcpServer(){ API_EXPORT void API_CALL mk_tcp_session_set_user_data(mk_tcp_session session,void *user_data){ assert(session); - TcpSessionForC *obj = (TcpSessionForC *)session; + SessionForC *obj = (SessionForC *)session; obj->_user_data = user_data; } API_EXPORT void* API_CALL mk_tcp_session_get_user_data(mk_tcp_session session){ assert(session); - TcpSessionForC *obj = (TcpSessionForC *)session; + SessionForC *obj = (SessionForC *)session; return obj->_user_data; } @@ -226,18 +226,18 @@ API_EXPORT uint16_t API_CALL mk_tcp_server_start(uint16_t port, mk_tcp_type type s_tcp_server[type] = std::make_shared(); switch (type) { case mk_type_tcp: - s_tcp_server[type]->start(port); + s_tcp_server[type]->start(port); break; case mk_type_ssl: - s_tcp_server[type]->start >(port); + s_tcp_server[type]->start >(port); break; case mk_type_ws: //此处你也可以修改WebSocketHeader::BINARY - s_tcp_server[type]->start >(port); + s_tcp_server[type]->start >(port); break; case mk_type_wss: //此处你也可以修改WebSocketHeader::BINARY - s_tcp_server[type]->start >(port); + s_tcp_server[type]->start >(port); break; default: return 0; @@ -295,7 +295,7 @@ TcpClientForC::Ptr *mk_tcp_client_create_l(mk_tcp_client_events *events, mk_tcp_ case mk_type_tcp: return new TcpClientForC::Ptr(new TcpClientForC(events)); case mk_type_ssl: - return (TcpClientForC::Ptr *)new std::shared_ptr >(new TcpSessionWithSSL(events)); + return (TcpClientForC::Ptr *)new std::shared_ptr >(new SessionWithSSL(events)); case mk_type_ws: //此处你也可以修改WebSocketHeader::BINARY return (TcpClientForC::Ptr *)new std::shared_ptr >(new WebSocketClient(events)); diff --git a/api/source/mk_tcp_private.h b/api/source/mk_tcp_private.h index 46cdc6f3..053602dc 100644 --- a/api/source/mk_tcp_private.h +++ b/api/source/mk_tcp_private.h @@ -13,7 +13,7 @@ #include "mk_tcp.h" #include "Network/TcpClient.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" class TcpClientForC : public toolkit::TcpClient { public: @@ -31,10 +31,10 @@ private: mk_tcp_client _client; }; -class TcpSessionForC : public toolkit::TcpSession { +class SessionForC : public toolkit::Session { public: - TcpSessionForC(const toolkit::Socket::Ptr &pSock) ; - ~TcpSessionForC() override = default; + SessionForC(const toolkit::Socket::Ptr &pSock) ; + ~SessionForC() override = default; void onRecv(const toolkit::Buffer::Ptr &buffer) override ; void onError(const toolkit::SockException &err) override; void onManager() override; diff --git a/api/source/mk_thread.cpp b/api/source/mk_thread.cpp index 42ed5fef..f3c06ff4 100644 --- a/api/source/mk_thread.cpp +++ b/api/source/mk_thread.cpp @@ -18,7 +18,7 @@ using namespace toolkit; API_EXPORT mk_thread API_CALL mk_thread_from_tcp_session(mk_tcp_session ctx){ assert(ctx); - TcpSessionForC *obj = (TcpSessionForC *)ctx; + SessionForC *obj = (SessionForC *)ctx; return obj->getPoller().get(); } diff --git a/api/source/mk_track.cpp b/api/source/mk_track.cpp index b4df453d..3190b936 100644 --- a/api/source/mk_track.cpp +++ b/api/source/mk_track.cpp @@ -115,12 +115,10 @@ API_EXPORT int API_CALL mk_track_bit_rate(mk_track track) { API_EXPORT void *API_CALL mk_track_add_delegate(mk_track track, on_mk_frame_out cb, void *user_data) { assert(track && cb); - auto delegate = std::make_shared([cb, user_data](const Frame::Ptr &frame) { + return (*((Track::Ptr *) track))->addDelegate([cb, user_data](const Frame::Ptr &frame) { cb(user_data, (mk_frame) &frame); return true; }); - (*((Track::Ptr *) track))->addDelegate(delegate); - return delegate.get(); } API_EXPORT void API_CALL mk_track_del_delegate(mk_track track, void *tag) { diff --git a/api/source/mk_webrtc_private.h b/api/source/mk_webrtc_private.h deleted file mode 100644 index 80ec8332..00000000 --- a/api/source/mk_webrtc_private.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifdef ENABLE_WEBRTC -#ifndef MK_WEBRTC_API_H -#define MK_WEBRTC_API_H - -#include "Common/Parser.h" -#include "Http/HttpSession.h" -#include "Network/Socket.h" -#include "jsoncpp/json.h" -#include -#include - -#include "../webrtc/WebRtcEchoTest.h" -#include "../webrtc/WebRtcPlayer.h" -#include "../webrtc/WebRtcPusher.h" -#include "../webrtc/WebRtcTransport.h" - -namespace API { -typedef enum { - NotFound = -500, //未找到 - Exception = -400, //代码抛异常 - InvalidArgs = -300, //参数不合法 - SqlFailed = -200, // sql执行失败 - AuthFailed = -100, //鉴权失败 - OtherFailed = -1, //业务代码执行失败, - Success = 0 //执行成功 -} ApiErr; -} // namespace API - -class ApiRetException : public std::runtime_error { -public: - ApiRetException(const char *str = "success", int code = API::Success) - : runtime_error(str) { - _code = code; - } - ~ApiRetException() = default; - int code() { return _code; } - -private: - int _code; -}; - -class AuthException : public ApiRetException { -public: - AuthException(const char *str) - : ApiRetException(str, API::AuthFailed) {} - ~AuthException() = default; -}; - -class InvalidArgsException : public ApiRetException { -public: - InvalidArgsException(const char *str) - : ApiRetException(str, API::InvalidArgs) {} - ~InvalidArgsException() = default; -}; - -class SuccessException : public ApiRetException { -public: - SuccessException() - : ApiRetException("success", API::Success) {} - ~SuccessException() = default; -}; - -using ApiArgsType = std::map; -template -std::string getValue(Args &args, const First &first) { - return args[first]; -} - -template -std::string getValue(Json::Value &args, const First &first) { - return args[first].asString(); -} - -template -std::string getValue(std::string &args, const First &first) { - return ""; -} - -template -std::string getValue(const mediakit::Parser &parser, const First &first) { - auto ret = parser.getUrlArgs()[first]; - if (!ret.empty()) { - return ret; - } - return parser.getHeader()[first]; -} - -template -std::string getValue(mediakit::Parser &parser, const First &first) { - return getValue((const mediakit::Parser &)parser, first); -} - -template -std::string getValue(const mediakit::Parser &parser, Args &args, const First &first) { - auto ret = getValue(args, first); - if (!ret.empty()) { - return ret; - } - return getValue(parser, first); -} - -template -class HttpAllArgs { -public: - HttpAllArgs(const mediakit::Parser &parser, Args &args) { - _get_args = [&args]() { return (void *)&args; }; - _get_parser = [&parser]() -> const mediakit::Parser & { return parser; }; - _get_value - = [](HttpAllArgs &that, const std::string &key) { return getValue(that.getParser(), that.getArgs(), key); }; - _clone = [&](HttpAllArgs &that) { - that._get_args = [args]() { return (void *)&args; }; - that._get_parser = [parser]() -> const mediakit::Parser & { return parser; }; - that._get_value = [](HttpAllArgs &that, const std::string &key) { - return getValue(that.getParser(), that.getArgs(), key); - }; - that._cache_able = true; - }; - } - - HttpAllArgs(const HttpAllArgs &that) { - if (that._cache_able) { - _get_args = that._get_args; - _get_parser = that._get_parser; - _get_value = that._get_value; - _cache_able = true; - } else { - that._clone(*this); - } - } - - ~HttpAllArgs() = default; - - template - toolkit::variant operator[](const Key &key) const { - return (toolkit::variant)_get_value(*(HttpAllArgs *)this, key); - } - - const mediakit::Parser &getParser() const { return _get_parser(); } - - Args &getArgs() { return *((Args *)_get_args()); } - - const Args &getArgs() const { return *((Args *)_get_args()); } - -private: - bool _cache_able = false; - std::function _get_args; - std::function _get_parser; - std::function _get_value; - std::function _clone; -}; - -#define API_ARGS_MAP \ - toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const HttpAllArgs &allArgs, \ - Json::Value &val -#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker -#define API_ARGS_JSON \ - toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const HttpAllArgs &allArgs, \ - Json::Value &val -#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker -#define API_ARGS_STRING \ - toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const HttpAllArgs &allArgs, \ - Json::Value &val -#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker -#define API_ARGS_VALUE sender, headerOut, allArgs, val - -//注册http请求参数是http原始请求信息的异步回复的http api -void api_regist(const std::string &api_path, const std::function &func); - -template -bool checkArgs(Args &args, const First &first) { - return !args[first].empty(); -} - -template -bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) { - return checkArgs(args, first) && checkArgs(args, keys...); -} - -//检查http url中或body中或http header参数是否为空的宏 -#define CHECK_ARGS(...) \ - if (!checkArgs(allArgs, ##__VA_ARGS__)) { \ - throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \ - } - -#ifdef ENABLE_WEBRTC -class WebRtcArgsImp : public WebRtcArgs { -public: - WebRtcArgsImp(const HttpAllArgs &args, std::string session_id) - : _args(args) - , _session_id(std::move(session_id)) {} - ~WebRtcArgsImp() override = default; - - variant operator[](const std::string &key) const override { - if (key == "url") { - return getUrl(); - } - return _args[key]; - } - -private: - std::string getUrl() const { - auto &allArgs = _args; - CHECK_ARGS("app", "stream"); - - return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/" << _args["stream"] - << "?" << _args.getParser().Params() + "&session=" + _session_id; - } - -private: - HttpAllArgs _args; - std::string _session_id; -}; -#endif - -#endif // MK_WEBRTC_API_H -#endif // ENABLE_WEBRTC \ No newline at end of file diff --git a/api/tests/server.c b/api/tests/server.c index cd400fd4..8b283b6b 100644 --- a/api/tests/server.c +++ b/api/tests/server.c @@ -88,8 +88,10 @@ void API_CALL on_mk_media_play(const mk_media_info url_info, * 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 * @param url_info 播放url相关信息 * @param sender 播放客户端相关信息 + * @return 1 直接关闭 + * 0 等待流注册 */ -void API_CALL on_mk_media_not_found(const mk_media_info url_info, +int API_CALL on_mk_media_not_found(const mk_media_info url_info, const mk_sock_info sender) { char ip[64]; log_printf(LOG_LEV, @@ -104,6 +106,7 @@ void API_CALL on_mk_media_not_found(const mk_media_info url_info, mk_media_info_get_app(url_info), mk_media_info_get_stream(url_info), mk_media_info_get_params(url_info)); + return 0; } /** @@ -119,6 +122,72 @@ void API_CALL on_mk_media_no_reader(const mk_media_source sender) { mk_media_source_get_stream(sender)); } +//按照json转义规则转义webrtc answer sdp +static char *escape_string(const char *ptr){ + char *escaped = malloc(2 * strlen(ptr)); + char *ptr_escaped = escaped; + while (1) { + switch (*ptr) { + case '\r': { + *(ptr_escaped++) = '\\'; + *(ptr_escaped++) = 'r'; + break; + } + case '\n': { + *(ptr_escaped++) = '\\'; + *(ptr_escaped++) = 'n'; + break; + } + case '\t': { + *(ptr_escaped++) = '\\'; + *(ptr_escaped++) = 't'; + break; + } + + default: { + *(ptr_escaped++) = *ptr; + if (!*ptr) { + return escaped; + } + break; + } + } + ++ptr; + } +} + +static void on_mk_webrtc_get_answer_sdp_func(void *user_data, const char *answer, const char *err) { + const char *response_header[] = { "Content-Type", "application/json", "Access-Control-Allow-Origin", "*" , NULL}; + if (answer) { + answer = escape_string(answer); + } + size_t len = answer ? 2 * strlen(answer) : 1024; + char *response_content = (char *)malloc(len); + + if (answer) { + snprintf(response_content, len, + "{" + "\"sdp\":\"%s\"," + "\"type\":\"answer\"," + "\"code\":0" + "}", + answer); + } else { + snprintf(response_content, len, + "{" + "\"msg\":\"%s\"," + "\"code\":-1" + "}", + err); + } + + mk_http_response_invoker_do_string(user_data, 200, response_header, response_content); + mk_http_response_invoker_clone_release(user_data); + free(response_content); + if (answer) { + free((void *)answer); + } +} /** * 收到http api请求广播(包括GET/POST) * @param parser http请求内容对象 @@ -153,7 +222,7 @@ void API_CALL on_mk_http_request(const mk_parser parser, *consumed = 1; //拦截api: /api/test - if(strcmp(url,"/api/test") == 0) { + if (strcmp(url, "/api/test") == 0) { const char *response_header[] = { "Content-Type", "text/html", NULL }; const char *content = "" "" @@ -168,12 +237,16 @@ void API_CALL on_mk_http_request(const mk_parser parser, mk_http_body body = mk_http_body_from_string(content, 0); mk_http_response_invoker_do(invoker, 200, response_header, body); mk_http_body_release(body); - } - //拦截api: /index/api/webrtc - else if(strcmp(url,"/index/api/webrtc") == 0){ - mk_webrtc_http_response_invoker_do(invoker,parser,sender); - } - else{ + } else if (strcmp(url, "/index/api/webrtc") == 0) { + //拦截api: /index/api/webrtc + char rtc_url[1024]; + snprintf(rtc_url, sizeof(rtc_url), "rtc://%s/%s/%s?%s", mk_parser_get_header(parser, "Host"), + mk_parser_get_url_param(parser, "app"), mk_parser_get_url_param(parser, "stream"), + mk_parser_get_url_params(parser)); + + mk_webrtc_get_answer_sdp(mk_http_response_invoker_clone(invoker), on_mk_webrtc_get_answer_sdp_func, + mk_parser_get_url_param(parser, "type"), mk_parser_get_content(parser, NULL), rtc_url); + } else { *consumed = 0; return; } @@ -413,6 +486,7 @@ int main(int argc, char *argv[]) { mk_shell_server_start(9000); mk_rtp_server_start(10000); mk_rtc_server_start(8000); + mk_srt_server_start(9000); mk_events events = { .on_mk_media_changed = on_mk_media_changed, diff --git a/build_docker_images.sh b/build_docker_images.sh index bc110256..7c8b855c 100644 --- a/build_docker_images.sh +++ b/build_docker_images.sh @@ -1,31 +1,36 @@ #!/bin/bash set -e -while getopts c:t:m:v: opt +while getopts c:t:p:m:v: opt do - case $opt in - t) - type=$OPTARG - ;; - v) + case $opt in + t) + type=$OPTARG + ;; + v) version=$OPTARG ;; + p) + platform=$OPTARG + ;; m) model=$OPTARG ;; - ?) - echo "unkonwn" - exit - ;; + ?) + echo "unkonwn" + exit + ;; esac done +help_string=".sh [-t build|push] [-p (amd64|arm64|...,default is `arch`) ] [-m Debug|Release] [-v [version]]" + if [[ ! -n $type ]];then - echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" + echo $help_string exit fi if [[ ! -n $model ]];then - echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" + echo $help_string exit fi @@ -34,16 +39,36 @@ if [[ ! -n $version ]];then version="latest" fi +if [[ ! -n $platform ]];then + platform=`arch` + echo "auto select arch:${platform}" +fi + +case $platform in +"arm64") + #eg:osx + platform="linux/arm64" + ;; +"x86_64"|"amd64") + platform="linux/amd64" + ;; +*) + echo "unknown cpu-arch ${platform}" + echo "Use 'docker buildx ls' to get supported ARCH" + exit + ;; +esac + case $model in 'Debug') ;; 'Release') ;; *) - echo "unkonwn model" - echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" - exit - ;; + echo "unkonwn model" + echo $help_string + exit + ;; esac namespace="zlmediakit" @@ -53,7 +78,8 @@ case $type in 'build') rm -rf ./build/CMakeCache.txt # 以腾讯云账号为例 - docker build --network=host --build-arg MODEL=$model -t $namespace/$packagename:$model.$version . + docker buildx build --platform=$platform --network=host --build-arg MODEL=$model -t $namespace/$packagename:$model.$version . + #docker build --network=host --build-arg MODEL=$model -t $namespace/$packagename:$model.$version . ;; 'push') echo "push to dst registry" @@ -62,8 +88,8 @@ case $type in docker push $namespace/$packagename:$model.$version ;; *) - echo "unkonwn type" - echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" - exit - ;; + echo "unkonwn type" + echo $help_string + exit + ;; esac diff --git a/conf/config.ini b/conf/config.ini index a60fa4cc..ae0ce077 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -29,6 +29,56 @@ log=./ffmpeg/ffmpeg.log # 自动重启的时间(秒), 默认为0, 也就是不自动重启. 主要是为了避免长时间ffmpeg拉流导致的不同步现象 restart_sec=0 +#转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项 +[protocol] +#转协议时,是否开启帧级时间戳覆盖 +modify_stamp=0 +#转协议是否开启音频 +enable_audio=1 +#添加acc静音音频,在关闭音频时,此开关无效 +add_mute_audio=1 +#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 +#置0关闭此特性(推流断开会导致立即断开播放器) +#此参数不应大于播放器超时时间;单位毫秒 +continue_push_ms=15000 + +#是否开启转换为hls +enable_hls=1 +#是否开启MP4录制 +enable_mp4=0 +#是否开启转换为rtsp/webrtc +enable_rtsp=1 +#是否开启转换为rtmp/flv +enable_rtmp=1 +#是否开启转换为http-ts/ws-ts +enable_ts=1 +#是否开启转换为http-fmp4/ws-fmp4 +enable_fmp4=1 + +#是否将mp4录制当做观看者 +mp4_as_player=0 +#mp4切片大小,单位秒 +mp4_max_second=3600 +#mp4录制保存路径 +mp4_save_path=./www + +#hls录制保存路径 +hls_save_path=./www + +###### 以下是按需转协议的开关,在测试ZLMediaKit的接收推流性能时,请把下面开关置1 +###### 如果某种协议你用不到,你可以把以下开关置1以便节省资源(但是还是可以播放,只是第一个播放者体验稍微差点), +###### 如果某种协议你想获取最好的用户体验,请置0(第一个播放者可以秒开,且不花屏) +#hls协议是否按需生成,如果hls.segNum配置为0(意味着hls录制),那么hls将一直生成(不管此开关) +hls_demand=0 +#rtsp[s]协议是否按需生成 +rtsp_demand=0 +#rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成 +rtmp_demand=0 +#http[s]-ts协议是否按需生成 +ts_demand=0 +#http[s]-fmp4、ws[s]-fmp4协议是否按需生成 +fmp4_demand=0 + [general] #是否启用虚拟主机 enableVhost=0 @@ -44,43 +94,14 @@ maxStreamWaitMS=15000 #某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒 #在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流 streamNoneReaderDelayMS=20000 -#是否全局添加静音aac音频,转协议时有效 -#有些播放器在打开单视频流时不能秒开,添加静音音频可以加快秒开速度 -addMuteAudio=1 #拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, #如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) resetWhenRePlay=1 -#是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置 -publishToHls=1 -#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置 -publishToMP4=0 #合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时 #开启后会同时关闭TCP_NODELAY并开启MSG_MORE mergeWriteMS=0 -#全局的时间戳覆盖开关,在转协议时,对frame进行时间戳覆盖 -#该开关对rtsp/rtmp/rtp推流、rtsp/rtmp/hls拉流代理转协议时生效 -#会直接影响rtsp/rtmp/hls/mp4/flv等协议的时间戳 -#同协议情况下不影响(例如rtsp/rtmp推流,那么播放rtsp/rtmp时不会影响时间戳) -modifyStamp=0 #服务器唯一id,用于触发hook时区别是哪台服务器 mediaServerId=your_server_id -#转协议是否全局开启或关闭音频 -enable_audio=1 - -###### 以下是按需转协议的开关,在测试ZLMediaKit的接收推流性能时,请把下面开关置1 -###### 如果某种协议你用不到,你可以把以下开关置1以便节省资源(但是还是可以播放,只是第一个播放者体验稍微差点), -###### 如果某种协议你想获取最好的用户体验,请置0(第一个播放者可以秒开,且不花屏) - -#hls协议是否按需生成,如果hls.segNum配置为0(意味着hls录制),那么hls将一直生成(不管此开关) -hls_demand=0 -#rtsp[s]协议是否按需生成 -rtsp_demand=0 -#rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成 -rtmp_demand=0 -#http[s]-ts协议是否按需生成 -ts_demand=0 -#http[s]-fmp4、ws[s]-fmp4协议是否按需生成 -fmp4_demand=0 #最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track wait_track_ready_ms=10000 @@ -89,17 +110,10 @@ wait_track_ready_ms=10000 wait_add_track_ms=3000 #如果track未就绪,我们先缓存帧数据,但是有最大个数限制,防止内存溢出 unready_frame_cache=100 -#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 -#置0关闭此特性(推流断开会导致立即断开播放器) -#此参数不应大于播放器超时时间 -continue_push_ms=15000 [hls] #hls写文件的buf大小,调整参数可以提高文件io性能 fileBufSize=65536 -#hls保存文件路径 -#可以为相对(相对于本可执行程序目录)或绝对路径 -filePath=./www #hls最大切片时间 segDur=2 #m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个) @@ -110,7 +124,7 @@ segRetain=5 #是否广播 ts 切片完成通知 broadcastRecordTs=0 #直播hls文件删除延时,单位秒,issue: #913 -deleteDelaySec=0 +deleteDelaySec=10 #是否保留hls文件,此功能部分等效于segNum=0的情况 #不同的是这个保留不会在m3u8文件中体现 #0为不保留,不起作用 @@ -155,6 +169,9 @@ on_server_started=https://127.0.0.1/index/hook/on_server_started on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive #发送rtp(startSendRtp)被动关闭时回调 on_send_rtp_stopped=https://127.0.0.1/index/hook/on_send_rtp_stopped +#rtp server 超时未收到数据 +on_rtp_server_timeout=https://127.0.0.1/index/hook/on_rtp_server_timeout + #hook api最大等待回复时间,单位秒 timeoutSec=10 #keepalive hook触发间隔,单位秒,float类型 @@ -228,11 +245,6 @@ udpTTL=64 appName=record #mp4录制写文件缓存,单位BYTE,调整参数可以提高文件io性能 fileBufSize=65536 -#mp4录制保存、mp4点播根路径 -#可以为相对(相对于本可执行程序目录)或绝对路径 -filePath=./www -#mp4录制切片时间,单位秒 -fileSecond=3600 #mp4点播每次流化数据量,单位毫秒, #减少该值可以让点播数据发送量更平滑,增大该值则更节省cpu资源 sampleMS=500 @@ -240,8 +252,6 @@ sampleMS=500 fastStart=0 #MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件 fileRepeat=0 -#MP4录制是否当做播放器参与播放人数统计 -mp4_as_player=0 [rtmp] #rtmp必须在此时间内完成握手,否则服务器会断开链接,单位秒 @@ -264,6 +274,8 @@ audioMtuSize=600 videoMtuSize=1400 #rtp包最大长度限制,单位KB,主要用于识别TCP上下文破坏时,获取到错误的rtp rtpMaxSize=10 +# rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏 +lowLatency=0 [rtp_proxy] #导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出 @@ -275,25 +287,18 @@ timeoutSec=15 #随机端口范围,最少确保36个端口 #该范围同时限制rtsp服务器udp端口范围 port_range=30000-35000 - #rtp h264 负载的pt h264_pt=98 - #rtp h265 负载的pt h265_pt=99 - #rtp ps 负载的pt ps_pt=96 - #rtp ts 负载的pt ts_pt=33 - #rtp opus 负载的pt opus_pt=100 - #rtp g711u 负载的pt g711u_pt=0 - #rtp g711a 负载的pt g711a_pt=8 @@ -308,6 +313,10 @@ externIP= #该端口是多线程的,同时支持客户端网络切换导致的连接迁移 #需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致 port=8000 +#rtc tcp服务器监听端口号,在udp 不通的情况下,会使用tcp传输数据 +#该端口是多线程的,同时支持客户端网络切换导致的连接迁移 +#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致 +tcpPort = 8000 #设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质 #目前已经实现twcc自动调整码率,关闭remb根据真实网络状况调整码率 rembBitRate=0 @@ -316,7 +325,7 @@ rembBitRate=0 preferredCodecA=PCMU,PCMA,opus,mpeg4-generic #rtc支持的视频codec类型,在前面的优先级更高 #以下范例为所有支持的视频codec -preferredCodecV=H264,H265,AV1X,VP9,VP8 +preferredCodecV=H264,H265,AV1,VP9,VP8 [srt] #srt播放推流、播放超时时间,单位秒 @@ -324,10 +333,8 @@ timeoutSec=5 #srt udp服务器监听端口号,所有srt客户端将通过该端口传输srt数据, #该端口是多线程的,同时支持客户端网络切换导致的连接迁移 port=9000 - #srt 协议中延迟缓存的估算参数,在握手阶段估算rtt ,然后latencyMul*rtt 为最大缓存时长,此参数越大,表示等待重传的时长就越大 latencyMul=4 - #包缓存的大小 pktBufSize=8192 @@ -352,7 +359,8 @@ keepAliveSecond=15 port=554 #rtsps服务器监听地址 sslport=0 - +#rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟 +lowLatency=0 [shell] #调试telnet服务器接受最大bufffer大小 maxReqSize=1024 diff --git a/default.pem b/default.pem index 97460132..9bfab500 100644 --- a/default.pem +++ b/default.pem @@ -86,4 +86,4 @@ M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV 4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg== ------END CERTIFICATE----- \ No newline at end of file +-----END CERTIFICATE----- diff --git a/player/test_player.cpp b/player/test_player.cpp index 40d3fcbb..5bbedf23 100644 --- a/player/test_player.cpp +++ b/player/test_player.cpp @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) { Logger::Instance().add(std::make_shared()); Logger::Instance().setWriter(std::make_shared()); - if (argc != 3) { + if (argc < 3) { ErrorL << "\r\n测试方法:./test_player rtxp_url rtp_type\r\n" << "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n" << endl; @@ -83,10 +83,9 @@ int main(int argc, char *argv[]) { return true; }); }); - auto delegate = std::make_shared([decoder](const Frame::Ptr &frame) { + videoTrack->addDelegate([decoder](const Frame::Ptr &frame) { return decoder->inputFrame(frame, false, true); }); - videoTrack->addDelegate(delegate); } if (audioTrack) { @@ -105,10 +104,9 @@ int main(int argc, char *argv[]) { auto len = pcm->get()->nb_samples * pcm->get()->channels * av_get_bytes_per_sample((enum AVSampleFormat)pcm->get()->format); audio_player->playPCM((const char *) (pcm->get()->data[0]), MIN(len, frame->get()->linesize[0])); }); - auto audio_delegate = std::make_shared( [decoder](const Frame::Ptr &frame) { + audioTrack->addDelegate([decoder](const Frame::Ptr &frame) { return decoder->inputFrame(frame, false, true); }); - audioTrack->addDelegate(audio_delegate); } }); @@ -119,6 +117,9 @@ int main(int argc, char *argv[]) { (*player)[Client::kRtpType] = atoi(argv[2]); //不等待track ready再回调播放成功事件,这样可以加快秒开速度 (*player)[Client::kWaitTrackReady] = false; + if (argc > 3) { + (*player)[Client::kPlayTrack] = atoi(argv[3]); + } player->play(argv[1]); SDLDisplayerHelper::Instance().runLoop(); return 0; diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index e456ed08..a67aa423 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -348,7 +348,7 @@ "response": [] }, { - "name": "获取TcpSession列表(getAllSession)", + "name": "获取Session列表(getAllSession)", "request": { "method": "GET", "header": [], @@ -1384,9 +1384,9 @@ "description": "绑定的端口,0时为随机端口" }, { - "key": "enable_tcp", + "key": "tcp_mode", "value": "1", - "description": "创建 udp端口时是否同时监听tcp端口" + "description": "tcp模式,0时为不启用tcp监听,1时为启用tcp监听,2时为tcp主动连接模式" }, { "key": "stream_id", @@ -1410,6 +1410,47 @@ }, "response": [] }, + { + "name": "连接RTP服务器(connectRtpServer)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/connectRtpServer?secret={{ZLMediaKit_secret}}&dst_url=127.0.0.1&dst_port=10000&stream_id=test", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "connectRtpServer" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}", + "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + }, + { + "key": "dst_url", + "value": "0", + "description": "tcp主动模式时服务端地址" + }, + { + "key": "dst_port", + "value": "1", + "description": "tcp主动模式时服务端端口" + }, + { + "key": "stream_id", + "value": "test", + "description": "OpenRtpServer时绑定的流id\n" + } + ] + } + }, + "response": [] + }, { "name": "关闭RTP服务器(closeRtpServer)", "request": { diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index abb950bc..1392d9dc 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -273,14 +273,14 @@ void FFmpegSource::setOnClose(const function &cb){ _onClose = cb; } -bool FFmpegSource::close(MediaSource &sender, bool force) { +bool FFmpegSource::close(MediaSource &sender) { auto listener = getDelegate(); - if(listener && !listener->close(sender,force)){ + if (listener && !listener->close(sender)) { //关闭失败 return false; } //该流无人观看,我们停止吧 - if(_onClose){ + if (_onClose) { _onClose(); } return true; diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index 3bf5fe19..3f28f228 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -75,7 +75,7 @@ private: ///////MediaSourceEvent override/////// // 关闭 - bool close(mediakit::MediaSource &sender,bool force) override; + bool close(mediakit::MediaSource &sender) override; // 获取媒体源类型 mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override; //获取媒体源url或者文件路径 diff --git a/server/Process.cpp b/server/Process.cpp index e8f48b26..7ddebb08 100644 --- a/server/Process.cpp +++ b/server/Process.cpp @@ -58,6 +58,12 @@ static int runChildProcess(string cmd, string log_file) { log_file = StrPrinter << log_file << "." << getpid(); } + if (isatty(STDIN_FILENO)) { + /* bb_error_msg("ignoring input"); */ + close(STDIN_FILENO); + open("/dev/null", O_RDONLY, 0666); /* will be fd 0 (STDIN_FILENO) */ + } + //重定向shell日志至文件 auto fp = File::create_file(log_file.data(), "ab"); if (!fp) { diff --git a/server/Process.h b/server/Process.h index db752232..d514edb5 100644 --- a/server/Process.h +++ b/server/Process.h @@ -11,8 +11,10 @@ #ifndef ZLMEDIAKIT_PROCESS_H #define ZLMEDIAKIT_PROCESS_H -#ifdef _WIN32 +#if defined(_WIN32) +#if !defined(__MINGW32__) typedef int pid_t; +#endif #else #include #endif // _WIN32 diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 5d65a593..e7d1fa79 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -312,6 +312,14 @@ static inline string getPusherKey(const string &schema, const string &vhost, con return schema + "/" + vhost + "/" + app + "/" + stream + "/" + MD5(dst_url).hexdigest(); } +static void fillSockInfo(Value& val, SockInfo* info) { + val["peer_ip"] = info->get_peer_ip(); + val["peer_port"] = info->get_peer_port(); + val["local_port"] = info->get_local_port(); + val["local_ip"] = info->get_local_ip(); + val["identifier"] = info->getIdentifier(); +} + Value makeMediaSourceJson(MediaSource &media){ Value item; item["schema"] = media.getSchema(); @@ -330,11 +338,7 @@ Value makeMediaSourceJson(MediaSource &media){ item["isRecordingHLS"] = media.isRecording(Recorder::type_hls); auto originSock = media.getOriginSock(); if (originSock) { - item["originSock"]["local_ip"] = originSock->get_local_ip(); - item["originSock"]["local_port"] = originSock->get_local_port(); - item["originSock"]["peer_ip"] = originSock->get_peer_ip(); - item["originSock"]["peer_port"] = originSock->get_peer_port(); - item["originSock"]["identifier"] = originSock->getIdentifier(); + fillSockInfo(item["originSock"], originSock.get()); } else { item["originSock"] = Json::nullValue; } @@ -383,7 +387,7 @@ Value makeMediaSourceJson(MediaSource &media){ } #if defined(ENABLE_RTPPROXY) -uint16_t openRtpServer(uint16_t local_port, const string &stream_id, bool enable_tcp, const string &local_ip, bool re_use_port, uint32_t ssrc) { +uint16_t openRtpServer(uint16_t local_port, const string &stream_id, int tcp_mode, const string &local_ip, bool re_use_port, uint32_t ssrc) { lock_guard lck(s_rtpServerMapMtx); if (s_rtpServerMap.find(stream_id) != s_rtpServerMap.end()) { //为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的stream_id @@ -391,12 +395,12 @@ uint16_t openRtpServer(uint16_t local_port, const string &stream_id, bool enable } RtpServer::Ptr server = std::make_shared(); - server->start(local_port, stream_id, enable_tcp, local_ip.c_str(), re_use_port, ssrc); + server->start(local_port, stream_id, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc); server->setOnDetach([stream_id]() { //设置rtp超时移除事件 lock_guard lck(s_rtpServerMapMtx); s_rtpServerMap.erase(stream_id); - }); + }); //保存对象 s_rtpServerMap.emplace(stream_id, server); @@ -404,6 +408,16 @@ uint16_t openRtpServer(uint16_t local_port, const string &stream_id, bool enable return server->getPort(); } +void connectRtpServer(const string &stream_id, const string &dst_url, uint16_t dst_port, const function &cb) { + lock_guard lck(s_rtpServerMapMtx); + auto it = s_rtpServerMap.find(stream_id); + if (it == s_rtpServerMap.end()) { + cb(SockException(Err_other, "未找到rtp服务")); + return; + } + it->second->connectToServer(dst_url, dst_port, cb); +} + bool closeRtpServer(const string &stream_id) { lock_guard lck(s_rtpServerMapMtx); auto it = s_rtpServerMap.find(stream_id); @@ -772,9 +786,7 @@ void installWebApi() { [](std::shared_ptr &&info) -> std::shared_ptr { auto obj = std::make_shared(); auto session = static_pointer_cast(info); - (*obj)["peer_ip"] = session->get_peer_ip(); - (*obj)["peer_port"] = session->get_peer_port(); - (*obj)["id"] = session->getIdentifier(); + fillSockInfo(*obj, session.get()); (*obj)["typeid"] = toolkit::demangle(typeid(*session).name()); return obj; }); @@ -842,7 +854,7 @@ void installWebApi() { val["count_closed"] = count_closed; }); - //获取所有TcpSession列表信息 + //获取所有Session列表信息 //可以根据本地端口和远端ip来筛选 //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935 api_regist("/index/api/getAllSession",[](API_ARGS_MAP){ @@ -858,10 +870,7 @@ void installWebApi() { if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){ return; } - jsession["peer_ip"] = session->get_peer_ip(); - jsession["peer_port"] = session->get_peer_port(); - jsession["local_ip"] = session->get_local_ip(); - jsession["local_port"] = session->get_local_port(); + fillSockInfo(jsession, session.get()); jsession["id"] = id; jsession["typeid"] = toolkit::demangle(typeid(*session).name()); val["data"].append(jsession); @@ -1002,20 +1011,7 @@ void installWebApi() { CHECK_SECRET(); CHECK_ARGS("vhost","app","stream","url"); - ProtocolOption option; - getArgsValue(allArgs, "enable_hls", option.enable_hls); - getArgsValue(allArgs, "enable_mp4", option.enable_mp4); - getArgsValue(allArgs, "mp4_as_player", option.mp4_as_player); - getArgsValue(allArgs, "enable_rtsp", option.enable_rtsp); - getArgsValue(allArgs, "enable_rtmp", option.enable_rtmp); - getArgsValue(allArgs, "enable_ts", option.enable_ts); - getArgsValue(allArgs, "enable_fmp4", option.enable_fmp4); - getArgsValue(allArgs, "enable_audio", option.enable_audio); - getArgsValue(allArgs, "add_mute_audio", option.add_mute_audio); - getArgsValue(allArgs, "mp4_save_path", option.mp4_save_path); - getArgsValue(allArgs, "mp4_max_second", option.mp4_max_second); - getArgsValue(allArgs, "hls_save_path", option.hls_save_path); - getArgsValue(allArgs, "modify_stamp", option.modify_stamp); + ProtocolOption option(allArgs); addStreamProxy(allArgs["vhost"], allArgs["app"], @@ -1127,25 +1123,41 @@ void installWebApi() { return; } val["exist"] = true; - val["peer_ip"] = process->get_peer_ip(); - val["peer_port"] = process->get_peer_port(); - val["local_port"] = process->get_local_port(); - val["local_ip"] = process->get_local_ip(); + fillSockInfo(val, process.get()); }); api_regist("/index/api/openRtpServer",[](API_ARGS_MAP){ CHECK_SECRET(); - CHECK_ARGS("port", "enable_tcp", "stream_id"); + CHECK_ARGS("port", "stream_id"); auto stream_id = allArgs["stream_id"]; - auto port = openRtpServer(allArgs["port"], stream_id, allArgs["enable_tcp"].as(), "::", - allArgs["re_use_port"].as(), allArgs["ssrc"].as()); - if(port == 0) { + auto tcp_mode = allArgs["tcp_mode"].as(); + if (allArgs["enable_tcp"].as() && !tcp_mode) { + //兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数 + tcp_mode = 1; + } + auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, "::", allArgs["re_use_port"].as(), + allArgs["ssrc"].as()); + if (port == 0) { throw InvalidArgsException("该stream_id已存在"); } //回复json val["port"] = port; }); + api_regist("/index/api/connectRtpServer", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); + CHECK_ARGS("stream_id", "dst_url", "dst_port"); + connectRtpServer( + allArgs["stream_id"], allArgs["dst_url"], allArgs["dst_port"], + [val, headerOut, invoker](const SockException &ex) mutable { + if (ex) { + val["code"] = API::OtherFailed; + val["msg"] = ex.what(); + } + invoker(200, headerOut, val.toStyledString()); + }); + }); + api_regist("/index/api/closeRtpServer",[](API_ARGS_MAP){ CHECK_SECRET(); CHECK_ARGS("stream_id"); @@ -1567,9 +1579,9 @@ void installWebApi() { auto offer = allArgs.getArgs(); CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty"); - WebRtcPluginManager::Instance().getAnswerSdp( - *(static_cast(&sender)), type, offer, WebRtcArgsImp(allArgs, sender.getIdentifier()), - [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable { + WebRtcPluginManager::Instance().getAnswerSdp(*(static_cast(&sender)), type, + WebRtcArgsImp(allArgs, sender.getIdentifier()), + [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable { //设置返回类型 headerOut["Content-Type"] = HttpFileManager::getContentType(".json"); //设置跨域 @@ -1605,9 +1617,9 @@ void installWebApi() { api_regist("/index/hook/on_publish",[](API_ARGS_JSON){ //开始推流事件 //转换hls - val["enableHls"] = true; + val["enable_hls"] = true; //不录制mp4 - val["enableMP4"] = false; + val["enable_mp4"] = false; }); api_regist("/index/hook/on_play",[](API_ARGS_JSON){ @@ -1750,6 +1762,11 @@ void installWebApi() { api_regist("/index/hook/on_server_keepalive",[](API_ARGS_JSON){ //心跳hook }); + + api_regist("/index/hook/on_rtp_server_timeout",[](API_ARGS_JSON){ + //rtp server 超时 + TraceL < #include -#include "jsoncpp/json.h" +#include "json/json.h" #include "Common/Parser.h" #include "Network/Socket.h" #include "Http/HttpSession.h" @@ -231,7 +231,8 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) { void installWebApi(); void unInstallWebApi(); -uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, bool enable_tcp, const std::string &local_ip, bool re_use_port, uint32_t ssrc); +uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc); +void connectRtpServer(const std::string &stream_id, const std::string &dst_url, uint16_t dst_port, const std::function &cb); bool closeRtpServer(const std::string &stream_id); Json::Value makeMediaSourceJson(mediakit::MediaSource &media); void getStatisticJson(const std::function &cb); diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 0bfd4fda..b66707f5 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -15,7 +15,7 @@ #include "Common/config.h" #include "Common/MediaSource.h" #include "Http/HttpRequester.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" #include "Rtsp/RtspSession.h" #include "Http/HttpSession.h" #include "WebHook.h" @@ -46,6 +46,7 @@ const string kOnHttpAccess = HOOK_FIELD"on_http_access"; const string kOnServerStarted = HOOK_FIELD"on_server_started"; const string kOnServerKeepalive = HOOK_FIELD"on_server_keepalive"; const string kOnSendRtpStopped = HOOK_FIELD"on_send_rtp_stopped"; +const string kOnRtpServerTimeout = HOOK_FIELD"on_rtp_server_timeout"; const string kAdminParams = HOOK_FIELD"admin_params"; const string kAliveInterval = HOOK_FIELD"alive_interval"; const string kRetry = HOOK_FIELD"retry"; @@ -70,6 +71,7 @@ onceToken token([](){ mINI::Instance()[kOnServerStarted] = ""; mINI::Instance()[kOnServerKeepalive] = ""; mINI::Instance()[kOnSendRtpStopped] = ""; + mINI::Instance()[kOnRtpServerTimeout] = ""; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kRetry] = 1; @@ -292,6 +294,21 @@ static void pullStreamFromOrigin(const vector& urls, size_t index, size_ static void *web_hook_tag = nullptr; +static mINI jsonToMini(const Value &obj) { + mINI ret; + if (obj.isObject()) { + for (auto it = obj.begin(); it != obj.end(); ++it) { + try { + auto str = (*it).asString(); + ret[it.name()] = std::move(str); + } catch (std::exception &) { + WarnL << "Json is not convertible to string, key: " << it.name() << ", value: " << (*it); + } + } + } + return ret; +} + void installWebHook(){ GET_CONFIG(bool,hook_enable,Hook::kEnable); GET_CONFIG(string,hook_adminparams,Hook::kAdminParams); @@ -311,55 +328,12 @@ void installWebHook(){ body["originTypeStr"] = getOriginTypeString(type); //执行hook do_http_hook(hook_publish, body, [invoker](const Value &obj, const string &err) mutable { - ProtocolOption option; if (err.empty()) { //推流鉴权成功 - if (obj.isMember("enable_hls")) { - option.enable_hls = obj["enable_hls"].asBool(); - } - if (obj.isMember("enable_mp4")) { - option.enable_mp4 = obj["enable_mp4"].asBool(); - } - if (obj.isMember("enable_audio")) { - option.enable_audio = obj["enable_audio"].asBool(); - } - if (obj.isMember("add_mute_audio")) { - option.add_mute_audio = obj["add_mute_audio"].asBool(); - } - if (obj.isMember("mp4_save_path")) { - option.mp4_save_path = obj["mp4_save_path"].asString(); - } - if (obj.isMember("mp4_max_second")) { - option.mp4_max_second = obj["mp4_max_second"].asUInt(); - } - if (obj.isMember("hls_save_path")) { - option.hls_save_path = obj["hls_save_path"].asString(); - } - if (obj.isMember("enable_rtsp")) { - option.enable_rtsp = obj["enable_rtsp"].asBool(); - } - if (obj.isMember("enable_rtmp")) { - option.enable_rtmp = obj["enable_rtmp"].asBool(); - } - if (obj.isMember("enable_ts")) { - option.enable_ts = obj["enable_ts"].asBool(); - } - if (obj.isMember("enable_fmp4")) { - option.enable_fmp4 = obj["enable_fmp4"].asBool(); - } - if (obj.isMember("continue_push_ms")) { - option.continue_push_ms = obj["continue_push_ms"].asUInt(); - } - if (obj.isMember("mp4_as_player")) { - option.mp4_as_player = obj["mp4_as_player"].asBool(); - } - if (obj.isMember("modify_stamp")) { - option.modify_stamp = obj["modify_stamp"].asBool(); - } - invoker(err, option); + invoker(err, ProtocolOption(jsonToMini(obj))); } else { //推流鉴权失败 - invoker(err, option); + invoker(err, ProtocolOption()); } }); }); @@ -505,8 +479,17 @@ void installWebHook(){ body["ip"] = sender.get_peer_ip(); body["port"] = sender.get_peer_port(); body["id"] = sender.getIdentifier(); + + // Hook回复立即关闭流 + auto res_cb = [closePlayer](const Value &res, const string &err) { + bool flag = res["close"].asBool(); + if (flag) { + closePlayer(); + } + }; + //执行hook - do_http_hook(hook_stream_not_found, body, nullptr); + do_http_hook(hook_stream_not_found, body, res_cb); }); static auto getRecordInfo = [](const RecordInfo &info) { @@ -667,6 +650,21 @@ void installWebHook(){ }); }); + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) { + GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout); + if (!hook_enable || rtp_server_timeout.empty()) { + return; + } + + ArgsType body; + body["local_port"] = local_port; + body["stream_id"] = stream_id; + body["tcp_mode"] = tcp_mode; + body["re_use_port"] = re_use_port; + body["ssrc"] = ssrc; + do_http_hook(rtp_server_timeout, body); + }); + //汇报服务器重新启动 reportServerStarted(); diff --git a/server/WebHook.h b/server/WebHook.h index a1adc012..ea99f736 100755 --- a/server/WebHook.h +++ b/server/WebHook.h @@ -13,7 +13,7 @@ #include #include -#include "jsoncpp/json.h" +#include "json/json.h" //支持json或urlencoded方式传输参数 #define JSON_ARGS diff --git a/server/main.cpp b/server/main.cpp index f98e9640..8d3b0353 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -165,6 +165,7 @@ public: [](const std::shared_ptr &stream, const string &arg) -> bool { //版本信息 *stream << "编译日期: " << BUILD_TIME << std::endl; + *stream << "代码日期: " << COMMIT_TIME << std::endl; *stream << "当前git分支: " << BRANCH_NAME << std::endl; *stream << "当前git hash值: " << COMMIT_HASH << std::endl; throw ExitException(); @@ -222,6 +223,9 @@ int start_main(int argc,char *argv[]) { //启动异步日志线程 Logger::Instance().setWriter(std::make_shared()); + + InfoL << kServerName; + //加载配置文件,如果配置文件不存在就创建一个 loadIniConfig(g_ini_file.data()); @@ -273,9 +277,10 @@ int start_main(int argc,char *argv[]) { #endif//defined(ENABLE_RTPPROXY) #if defined(ENABLE_WEBRTC) + auto rtcSrv_tcp = std::make_shared(); //webrtc udp服务器 - auto rtcSrv = std::make_shared(); - rtcSrv->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) { + auto rtcSrv_udp = std::make_shared(); + rtcSrv_udp->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) { if (!buf) { return Socket::createSocket(poller, false); } @@ -286,7 +291,8 @@ int start_main(int argc,char *argv[]) { } return Socket::createSocket(new_poller, false); }); - uint16_t rtcPort = mINI::Instance()[RTC::kPort]; + uint16_t rtcPort = mINI::Instance()[Rtc::kPort]; + uint16_t rtcTcpPort = mINI::Instance()[Rtc::kTcpPort]; #endif//defined(ENABLE_WEBRTC) @@ -333,15 +339,15 @@ int start_main(int argc,char *argv[]) { #if defined(ENABLE_WEBRTC) //webrtc udp服务器 - if (rtcPort) { rtcSrv->start(rtcPort); } -#endif//defined(ENABLE_WEBRTC) + if (rtcPort) { rtcSrv_udp->start(rtcPort);} + if (rtcTcpPort) { rtcSrv_tcp->start(rtcTcpPort);} + +#endif//defined(ENABLE_WEBRTC) #if defined(ENABLE_SRT) // srt udp服务器 - if(srtPort){ - srtSrv->start(srtPort); - } + if(srtPort) { srtSrv->start(srtPort); } #endif//defined(ENABLE_SRT) } catch (std::exception &ex) { diff --git a/src/Codec/Transcode.cpp b/src/Codec/Transcode.cpp index 18d0cfae..b89fc951 100644 --- a/src/Codec/Transcode.cpp +++ b/src/Codec/Transcode.cpp @@ -420,9 +420,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num) { FFmpegDecoder::~FFmpegDecoder() { stopThread(true); if (_do_merger) { - _merger.inputFrame(nullptr, [&](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { - decodeFrame(buffer->data(), buffer->size(), dts, pts, false); - }); + _merger.flush(); } flush(); } @@ -452,7 +450,7 @@ const AVCodecContext *FFmpegDecoder::getContext() const { bool FFmpegDecoder::inputFrame_l(const Frame::Ptr &frame, bool live, bool enable_merge) { if (_do_merger && enable_merge) { - return _merger.inputFrame(frame, [&](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { + return _merger.inputFrame(frame, [this, live](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { decodeFrame(buffer->data(), buffer->size(), dts, pts, live); }); } diff --git a/src/Common/MediaSink.cpp b/src/Common/MediaSink.cpp index 1f8050ab..2cd3749a 100644 --- a/src/Common/MediaSink.cpp +++ b/src/Common/MediaSink.cpp @@ -37,7 +37,7 @@ bool MediaSink::addTrack(const Track::Ptr &track_in) { }; _ticker.resetTime(); - track->addDelegate(std::make_shared([this](const Frame::Ptr &frame) { + track->addDelegate([this](const Frame::Ptr &frame) { if (_all_track_ready) { return onTrackFrame(frame); } @@ -52,7 +52,7 @@ bool MediaSink::addTrack(const Track::Ptr &track_in) { //还有Track未就绪,先缓存之 frame_unread.emplace_back(Frame::getCacheAbleFrame(frame)); return true; - })); + }); return true; } @@ -223,7 +223,7 @@ static uint8_t s_mute_adts[] = {0xff, 0xf1, 0x6c, 0x40, 0x2d, 0x3f, 0xfc, 0x00, #define MUTE_ADTS_DATA s_mute_adts #define MUTE_ADTS_DATA_LEN sizeof(s_mute_adts) -#define MUTE_ADTS_DATA_MS 130 +#define MUTE_ADTS_DATA_MS 128 bool MuteAudioMaker::inputFrame(const Frame::Ptr &frame) { if (frame->getTrackType() == TrackVideo) { @@ -247,13 +247,13 @@ bool MediaSink::addMuteAudioTrack() { } auto audio = std::make_shared(makeAacConfig(MUTE_ADTS_DATA, ADTS_HEADER_LEN)); _track_map[audio->getTrackType()] = std::make_pair(audio, true); - audio->addDelegate(std::make_shared([this](const Frame::Ptr &frame) { + audio->addDelegate([this](const Frame::Ptr &frame) { return onTrackFrame(frame); - })); + }); _mute_audio_maker = std::make_shared(); - _mute_audio_maker->addDelegate(std::make_shared([audio](const Frame::Ptr &frame) { + _mute_audio_maker->addDelegate([audio](const Frame::Ptr &frame) { return audio->inputFrame(frame); - })); + }); onTrackReady(audio); TraceL << "mute aac track added"; return true; diff --git a/src/Common/MediaSink.h b/src/Common/MediaSink.h index 449f666e..0e41821b 100644 --- a/src/Common/MediaSink.h +++ b/src/Common/MediaSink.h @@ -61,7 +61,7 @@ public: bool inputFrame(const Frame::Ptr &frame) override; private: - uint32_t _audio_idx = 0; + uint64_t _audio_idx = 0; }; /** diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 810aead7..bcd16cb9 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -8,11 +8,13 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#include "MediaSource.h" -#include "Record/MP4Reader.h" #include "Util/util.h" +#include "Util/NoticeCenter.h" #include "Network/sockutil.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" +#include "MediaSource.h" +#include "Common/config.h" +#include "Record/MP4Reader.h" using namespace std; using namespace toolkit; @@ -24,7 +26,11 @@ namespace toolkit { namespace mediakit { static recursive_mutex s_media_source_mtx; -static MediaSource::SchemaVhostAppStreamMap s_media_source_map; +using StreamMap = unordered_map >; +using AppStreamMap = unordered_map; +using VhostAppStreamMap = unordered_map; +using SchemaVhostAppStreamMap = unordered_map; +static SchemaVhostAppStreamMap s_media_source_map; string getOriginTypeString(MediaOriginType type){ #define SWITCH_CASE(type) case MediaOriginType::type : return #type @@ -43,11 +49,60 @@ string getOriginTypeString(MediaOriginType type){ } } -static string getOriginUrl_l(const MediaSource *thiz) { - return thiz->getSchema() + "://" + thiz->getVhost() + "/" + thiz->getApp() + "/" + thiz->getId(); +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ProtocolOption::ProtocolOption() { + GET_CONFIG(bool, s_modify_stamp, Protocol::kModifyStamp); + GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio); + GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio); + GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); + + GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); + GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4); + GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp); + GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp); + GET_CONFIG(bool, s_enable_ts, Protocol::kEnableTS); + GET_CONFIG(bool, s_enable_fmp4, Protocol::kEnableFMP4); + + GET_CONFIG(bool, s_hls_demand, Protocol::kHlsDemand); + GET_CONFIG(bool, s_rtsp_demand, Protocol::kRtspDemand); + GET_CONFIG(bool, s_rtmp_demand, Protocol::kRtmpDemand); + GET_CONFIG(bool, s_ts_demand, Protocol::kTSDemand); + GET_CONFIG(bool, s_fmp4_demand, Protocol::kFMP4Demand); + + GET_CONFIG(bool, s_mp4_as_player, Protocol::kMP4AsPlayer); + GET_CONFIG(uint32_t, s_mp4_max_second, Protocol::kMP4MaxSecond); + GET_CONFIG(string, s_mp4_save_path, Protocol::kMP4SavePath); + + GET_CONFIG(string, s_hls_save_path, Protocol::kHlsSavePath); + + modify_stamp = s_modify_stamp; + enable_audio = s_enabel_audio; + add_mute_audio = s_add_mute_audio; + continue_push_ms = s_continue_push_ms; + + enable_hls = s_enable_hls; + enable_mp4 = s_enable_mp4; + enable_rtsp = s_enable_rtsp; + enable_rtmp = s_enable_rtmp; + enable_ts = s_enable_ts; + enable_fmp4 = s_enable_fmp4; + + hls_demand = s_hls_demand; + rtsp_demand = s_rtsp_demand; + rtmp_demand = s_rtmp_demand; + ts_demand = s_ts_demand; + fmp4_demand = s_fmp4_demand; + + mp4_as_player = s_mp4_as_player; + mp4_max_second = s_mp4_max_second; + mp4_save_path = s_mp4_save_path; + + hls_save_path = s_hls_save_path; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + struct MediaSourceNull : public MediaSource { MediaSourceNull() : MediaSource("schema", "vhost", "app", "stream") {}; int readerCount() override { return 0; } @@ -109,16 +164,12 @@ std::shared_ptr MediaSource::getOwnership() { } int MediaSource::getBytesSpeed(TrackType type){ - if(type == TrackInvalid){ + if(type == TrackInvalid || type == TrackMax){ return _speed[TrackVideo].getSpeed() + _speed[TrackAudio].getSpeed(); } return _speed[type].getSpeed(); } -uint64_t MediaSource::getCreateStamp() const { - return _create_stamp; -} - uint64_t MediaSource::getAliveSecond() const { //使用Ticker对象获取存活时间的目的是防止修改系统时间导致回退 return _ticker.createdTime() / 1000; @@ -140,6 +191,7 @@ std::weak_ptr MediaSource::getListener(bool next) const{ if (!next) { return _listener; } + auto listener = dynamic_pointer_cast(_listener.lock()); if (!listener) { //不是MediaSourceEventInterceptor对象或者对象已经销毁 @@ -170,13 +222,13 @@ MediaOriginType MediaSource::getOriginType() const { string MediaSource::getOriginUrl() const { auto listener = _listener.lock(); if (!listener) { - return getOriginUrl_l(this); + return getUrl(); } auto ret = listener->getOriginUrl(const_cast(*this)); if (!ret.empty()) { return ret; } - return getOriginUrl_l(this); + return getUrl(); } std::shared_ptr MediaSource::getOriginSock() const { @@ -213,10 +265,14 @@ bool MediaSource::speed(float speed) { bool MediaSource::close(bool force) { auto listener = _listener.lock(); - if(!listener){ + if (!listener) { return false; } - return listener->close(*this,force); + if (!force && totalReaderCount()) { + //有人观看,不强制关闭 + return false; + } + return listener->close(*this); } float MediaSource::getLossRate(mediakit::TrackType type) { @@ -231,29 +287,31 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() { toolkit::EventPoller::Ptr ret; auto listener = _listener.lock(); if (listener) { - ret = listener->getOwnerPoller(*this); + return listener->getOwnerPoller(*this); } - return ret ? ret : _default_poller; + WarnL << toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed, now return default poller: " + getUrl(); + return _default_poller; } void MediaSource::onReaderChanged(int size) { weak_ptr weak_self = shared_from_this(); - getOwnerPoller()->async([weak_self, size]() { + auto listener = _listener.lock(); + if (!listener) { + return; + } + getOwnerPoller()->async([weak_self, size, listener]() { auto strong_self = weak_self.lock(); if (!strong_self) { return; } - auto listener = strong_self->_listener.lock(); - if (listener) { - listener->onReaderChanged(*strong_self, size); - } + listener->onReaderChanged(*strong_self, size); }); } bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path, size_t max_second){ auto listener = _listener.lock(); if (!listener) { - WarnL << "未设置MediaSource的事件监听者,setupRecord失败:" << getSchema() << "/" << getVhost() << "/" << getApp() << "/" << getId(); + WarnL << "未设置MediaSource的事件监听者,setupRecord失败:" << getUrl(); return false; } return listener->setupRecord(*this, type, start, custom_path, max_second); @@ -337,7 +395,7 @@ void MediaSource::for_each_media(const function &cb, static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, const string &app, const string &id, bool from_mp4) { string vhost = vhost_in; - GET_CONFIG(bool,enableVhost,General::kEnableVhost); + GET_CONFIG(bool, enableVhost, General::kEnableVhost); if(vhost.empty() || !enableVhost){ vhost = DEFAULT_VHOST; } @@ -351,7 +409,7 @@ static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, con MediaSource::for_each_media([&](const MediaSource::Ptr &src) { ret = std::move(const_cast(src)); }, schema, vhost, app, id); if(!ret && from_mp4 && schema != HLS_SCHEMA){ - //未查找媒体源,则读取mp4创建一个 + //未找到媒体源,则读取mp4创建一个 //播放hls不触发mp4点播(因为HLS也可以用于录像,不是纯粹的直播) ret = MediaSource::createFromMP4(schema, vhost, app, id); } @@ -379,7 +437,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr &s }; auto on_timeout = poller->doDelayTask(maxWaitMS, [cb_once, listener_tag]() { - //最多等待一定时间,如果这个时间内,流未注册上,那么返回未找到流 + // 最多等待一定时间,如在这个时间内,流还未注册上,则返回空 NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); cb_once(nullptr); return 0; @@ -402,17 +460,15 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr &s //不是自己感兴趣的事件,忽略之 return; } + poller->async([weak_session, cancel_all, info, cb_once]() { cancel_all(); - auto strong_session = weak_session.lock(); - if (!strong_session) { - //自己已经销毁 - return; + if (auto strong_session = weak_session.lock()) { + //播发器请求的流终于注册上了,切换到自己的线程再回复 + DebugL << "收到媒体注册事件,回复播放器:" << info.getUrl(); + //再找一遍媒体源,一般能找到 + findAsync_l(info, strong_session, false, cb_once); } - //播发器请求的流终于注册上了,切换到自己的线程再回复 - DebugL << "收到媒体注册事件,回复播放器:" << info._schema << "/" << info._vhost << "/" << info._app << "/" << info._streamid; - //再找一遍媒体源,一般能找到 - findAsync_l(info, strong_session, false, cb_once); }, false); }; @@ -458,7 +514,7 @@ void MediaSource::emitEvent(bool regist){ } //触发广播 NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this); - InfoL << (regist ? "媒体注册:" : "媒体注销:") << _schema << " " << _vhost << " " << _app << " " << _stream_id; + InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl(); } void MediaSource::regist() { @@ -472,7 +528,7 @@ void MediaSource::regist() { return; } //增加判断, 防止当前流已注册时再次注册 - throw std::invalid_argument("media source already existed:" + _schema + "/" + _vhost + "/" + _app + "/" + _stream_id); + throw std::invalid_argument("media source already existed:" + getUrl()); } ref = shared_from_this(); } @@ -519,9 +575,9 @@ bool MediaSource::unregist() { /////////////////////////////////////MediaInfo////////////////////////////////////// -void MediaInfo::parse(const string &url_in){ +void MediaInfo::parse(const std::string &url_in){ _full_url = url_in; - string url = url_in; + auto url = url_in; auto pos = url.find("?"); if (pos != string::npos) { _param_strs = url.substr(pos + 1); @@ -621,11 +677,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); } else { //这个是mp4点播,我们自动关闭 - WarnL << "MP4点播无人观看,自动关闭:" - << strong_sender->getSchema() << "/" - << strong_sender->getVhost() << "/" - << strong_sender->getApp() << "/" - << strong_sender->getId(); + WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); strong_sender->close(false); } return false; @@ -633,7 +685,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ } string MediaSourceEvent::getOriginUrl(MediaSource &sender) const { - return getOriginUrl_l(&sender); + return sender.getUrl(); } MediaOriginType MediaSourceEventInterceptor::getOriginType(MediaSource &sender) const { @@ -688,12 +740,12 @@ bool MediaSourceEventInterceptor::speed(MediaSource &sender, float speed) { return listener->speed(sender, speed); } -bool MediaSourceEventInterceptor::close(MediaSource &sender, bool force) { +bool MediaSourceEventInterceptor::close(MediaSource &sender) { auto listener = _listener.lock(); if (!listener) { return false; } - return listener->close(sender, force); + return listener->close(sender); } int MediaSourceEventInterceptor::totalReaderCount(MediaSource &sender) { @@ -733,7 +785,7 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc if (listener) { return listener->getOwnerPoller(sender); } - return EventPollerPool::Instance().getPoller(); + throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed"); } bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 7c7a96b1..698cefca 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -19,19 +19,14 @@ #include #include "Common/config.h" #include "Common/Parser.h" -#include "Util/logger.h" -#include "Util/TimeTicker.h" -#include "Util/NoticeCenter.h" #include "Util/List.h" #include "Network/Socket.h" -#include "Rtsp/Rtsp.h" -#include "Rtmp/Rtmp.h" #include "Extension/Track.h" #include "Record/Recorder.h" -namespace toolkit{ - class Session; -}// namespace toolkit +namespace toolkit { +class Session; +} // namespace toolkit namespace mediakit { @@ -62,8 +57,8 @@ public: ~NotImplemented() override = default; }; - MediaSourceEvent(){}; - virtual ~MediaSourceEvent(){}; + MediaSourceEvent() = default; + virtual ~MediaSourceEvent() = default; // 获取媒体源类型 virtual MediaOriginType getOriginType(MediaSource &sender) const { return MediaOriginType::unknown; } @@ -79,13 +74,13 @@ public: // 通知倍数 virtual bool speed(MediaSource &sender, float speed) { return false; } // 通知其停止产生流 - virtual bool close(MediaSource &sender, bool force) { return false; } + virtual bool close(MediaSource &sender) { return false; } // 获取观看总人数,此函数一般强制重载 virtual int totalReaderCount(MediaSource &sender) { throw NotImplemented(toolkit::demangle(typeid(*this).name()) + "::totalReaderCount not implemented"); } // 通知观看人数变化 virtual void onReaderChanged(MediaSource &sender, int size); //流注册或注销事件 - virtual void onRegist(MediaSource &sender, bool regist) {}; + virtual void onRegist(MediaSource &sender, bool regist) {} // 获取丢包率 virtual float getLossRate(MediaSource &sender, TrackType type) { return -1; } // 获取所在线程, 此函数一般强制重载 @@ -95,7 +90,7 @@ public: // 开启或关闭录制 virtual bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const std::string &custom_path, size_t max_second) { return false; }; // 获取录制状态 - virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }; + virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; } // 获取所有track相关信息 virtual std::vector getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector(); }; @@ -139,11 +134,96 @@ private: toolkit::Timer::Ptr _async_close_timer; }; -//该对象用于拦截感兴趣的MediaSourceEvent事件 -class MediaSourceEventInterceptor : public MediaSourceEvent{ +class ProtocolOption { public: - MediaSourceEventInterceptor(){} - ~MediaSourceEventInterceptor() override {} + ProtocolOption(); + + //时间戳修复这一路流标志位 + bool modify_stamp; + //转协议是否开启音频 + bool enable_audio; + //添加静音音频,在关闭音频时,此开关无效 + bool add_mute_audio; + //断连续推延时,单位毫秒,默认采用配置文件 + uint32_t continue_push_ms; + + //是否开启转换为hls + bool enable_hls; + //是否开启MP4录制 + bool enable_mp4; + //是否开启转换为rtsp/webrtc + bool enable_rtsp; + //是否开启转换为rtmp/flv + bool enable_rtmp; + //是否开启转换为http-ts/ws-ts + bool enable_ts; + //是否开启转换为http-fmp4/ws-fmp4 + bool enable_fmp4; + + // hls协议是否按需生成,如果hls.segNum配置为0(意味着hls录制),那么hls将一直生成(不管此开关) + bool hls_demand; + // rtsp[s]协议是否按需生成 + bool rtsp_demand; + // rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成 + bool rtmp_demand; + // http[s]-ts协议是否按需生成 + bool ts_demand; + // http[s]-fmp4、ws[s]-fmp4协议是否按需生成 + bool fmp4_demand; + + //是否将mp4录制当做观看者 + bool mp4_as_player; + //mp4切片大小,单位秒 + size_t mp4_max_second; + //mp4录制保存路径 + std::string mp4_save_path; + + //hls录制保存路径 + std::string hls_save_path; + + template + ProtocolOption(const MAP &allArgs) : ProtocolOption() { +#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key) + GET_OPT_VALUE(modify_stamp); + GET_OPT_VALUE(enable_audio); + GET_OPT_VALUE(add_mute_audio); + GET_OPT_VALUE(continue_push_ms); + + GET_OPT_VALUE(enable_hls); + GET_OPT_VALUE(enable_mp4); + GET_OPT_VALUE(enable_rtsp); + GET_OPT_VALUE(enable_rtmp); + GET_OPT_VALUE(enable_ts); + GET_OPT_VALUE(enable_fmp4); + + GET_OPT_VALUE(hls_demand); + GET_OPT_VALUE(rtsp_demand); + GET_OPT_VALUE(rtmp_demand); + GET_OPT_VALUE(ts_demand); + GET_OPT_VALUE(fmp4_demand); + + GET_OPT_VALUE(mp4_max_second); + GET_OPT_VALUE(mp4_as_player); + GET_OPT_VALUE(mp4_save_path); + + GET_OPT_VALUE(hls_save_path); + } + +private: + template + static void getArgsValue(const MAP &allArgs, const KEY &key, TYPE &value) { + auto val = ((MAP &)allArgs)[key]; + if (!val.empty()) { + value = (TYPE)val; + } + } +}; + +//该对象用于拦截感兴趣的MediaSourceEvent事件 +class MediaSourceEventInterceptor : public MediaSourceEvent { +public: + MediaSourceEventInterceptor() = default; + ~MediaSourceEventInterceptor() override = default; void setDelegate(const std::weak_ptr &listener); std::shared_ptr getDelegate() const; @@ -155,7 +235,7 @@ public: bool seekTo(MediaSource &sender, uint32_t stamp) override; bool pause(MediaSource &sender, bool pause) override; bool speed(MediaSource &sender, float speed) override; - bool close(MediaSource &sender, bool force) override; + bool close(MediaSource &sender) override; int totalReaderCount(MediaSource &sender) override; void onReaderChanged(MediaSource &sender, int size) override; void onRegist(MediaSource &sender, bool regist) override; @@ -174,18 +254,20 @@ private: /** * 解析url获取媒体相关信息 */ -class MediaInfo{ +class MediaInfo { public: - ~MediaInfo() {} - MediaInfo() {} + ~MediaInfo() = default; + MediaInfo() = default; MediaInfo(const std::string &url) { parse(url); } void parse(const std::string &url); + std::string shortUrl() const { return _vhost + "/" + _app + "/" + _streamid; } + std::string getUrl() const { return _schema + "://" + shortUrl(); } public: + uint16_t _port = 0; std::string _full_url; std::string _schema; std::string _host; - uint16_t _port = 0; std::string _vhost; std::string _app; std::string _streamid; @@ -199,12 +281,8 @@ class MediaSource: public TrackSource, public std::enable_shared_from_this; - using StreamMap = std::unordered_map >; - using AppStreamMap = std::unordered_map; - using VhostAppStreamMap = std::unordered_map; - using SchemaVhostAppStreamMap = std::unordered_map; - MediaSource(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &stream_id) ; + MediaSource(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &stream_id); virtual ~MediaSource(); ////////////////获取MediaSource相关信息//////////////// @@ -218,6 +296,10 @@ public: // 流id const std::string& getId() const; + std::string shortUrl() const { return _vhost + "/" + _app + "/" + _stream_id; } + + std::string getUrl() const { return _schema + "://" + shortUrl(); } + //获取对象所有权 std::shared_ptr getOwnership(); @@ -232,7 +314,7 @@ public: // 获取数据速率,单位bytes/s int getBytesSpeed(TrackType type = TrackInvalid); // 获取流创建GMT unix时间戳,单位秒 - uint64_t getCreateStamp() const; + uint64_t getCreateStamp() const { return _create_stamp; } // 获取流上线时间,单位秒 uint64_t getAliveSecond() const; @@ -263,9 +345,9 @@ public: // 拖动进度条 bool seekTo(uint32_t stamp); - //暂停 + // 暂停 bool pause(bool pause); - //倍数播放 + // 倍数播放 bool speed(float speed); // 关闭该流 bool close(bool force); @@ -288,8 +370,11 @@ public: // 同步查找流 static Ptr find(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &id, bool from_mp4 = false); + static Ptr find(const MediaInfo &info, bool from_mp4 = false) { + return find(info._schema, info._vhost, info._app, info._streamid, from_mp4); + } - // 忽略类型,同步查找流,可能返回rtmp/rtsp/hls类型 + // 忽略schema,同步查找流,可能返回rtmp/rtsp/hls类型 static Ptr find(const std::string &vhost, const std::string &app, const std::string &stream_id, bool from_mp4 = false); // 异步查找流 @@ -304,9 +389,9 @@ protected: void regist(); private: - //媒体注销 + // 媒体注销 bool unregist(); - //触发媒体事件 + // 触发媒体事件 void emitEvent(bool regist); protected: @@ -322,11 +407,11 @@ private: std::string _stream_id; std::weak_ptr _listener; toolkit::EventPoller::Ptr _default_poller; - //对象个数统计 + // 对象个数统计 toolkit::ObjectStatistic _statistic; }; -///缓存刷新策略类 +/// 缓存刷新策略类 class FlushPolicy { public: FlushPolicy() = default; @@ -335,7 +420,8 @@ public: bool isFlushAble(bool is_video, bool is_key, uint64_t new_stamp, size_t cache_size); private: - uint64_t _last_stamp[2] = {0, 0}; + // 音视频的最后时间戳 + uint64_t _last_stamp[2] = { 0, 0 }; }; /// 合并写缓存模板 @@ -345,16 +431,14 @@ private: template > > class PacketCache { public: - PacketCache(){ - _cache = std::make_shared(); - } + PacketCache() { _cache = std::make_shared(); } virtual ~PacketCache() = default; void inputPacket(uint64_t stamp, bool is_video, std::shared_ptr pkt, bool key_pos) { - bool flush = flushImmediatelyWhenCloseMerge(); - if (!flush && _policy.isFlushAble(is_video, key_pos, stamp, _cache->size())) { - flushAll(); + bool flag = flushImmediatelyWhenCloseMerge(); + if (!flag && _policy.isFlushAble(is_video, key_pos, stamp, _cache->size())) { + flush(); } //追加数据到最后 @@ -363,11 +447,20 @@ public: _key_pos = key_pos; } - if (flush) { - flushAll(); + if (flag) { + flush(); } } + void flush() { + if (_cache->empty()) { + return; + } + onFlush(std::move(_cache), _key_pos); + _cache = std::make_shared(); + _key_pos = false; + } + virtual void clearCache() { _cache->clear(); } @@ -375,22 +468,19 @@ public: virtual void onFlush(std::shared_ptr, bool key_pos) = 0; private: - void flushAll() { - if (_cache->empty()) { - return; - } - onFlush(std::move(_cache), _key_pos); - _cache = std::make_shared(); - _key_pos = false; - } - bool flushImmediatelyWhenCloseMerge() { - //一般的协议关闭合并写时,立即刷新缓存,这样可以减少一帧的延时,但是rtp例外 - //因为rtp的包很小,一个RtpPacket包中也不是完整的一帧图像,所以在关闭合并写时, - //还是有必要缓冲一帧的rtp(也就是时间戳相同的rtp)再输出,这样虽然会增加一帧的延时 - //但是却对性能提升很大,这样做还是比较划算的 + // 一般的协议关闭合并写时,立即刷新缓存,这样可以减少一帧的延时,但是rtp例外 + // 因为rtp的包很小,一个RtpPacket包中也不是完整的一帧图像,所以在关闭合并写时, + // 还是有必要缓冲一帧的rtp(也就是时间戳相同的rtp)再输出,这样虽然会增加一帧的延时 + // 但是却对性能提升很大,这样做还是比较划算的 GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS); + + GET_CONFIG(int, rtspLowLatency, Rtsp::kLowLatency); + if (std::is_same::value && rtspLowLatency) { + return true; + } + return std::is_same::value ? false : (mergeWriteMS <= 0); } diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 3461ab5a..6f6a3da4 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). @@ -21,26 +21,8 @@ namespace toolkit { namespace mediakit { -ProtocolOption::ProtocolOption() { - GET_CONFIG(bool, s_to_hls, General::kPublishToHls); - GET_CONFIG(bool, s_to_mp4, General::kPublishToMP4); - GET_CONFIG(bool, s_enabel_audio, General::kEnableAudio); - GET_CONFIG(bool, s_add_mute_audio, General::kAddMuteAudio); - GET_CONFIG(bool, s_mp4_as_player, Record::kMP4AsPlayer); - GET_CONFIG(uint32_t, s_continue_push_ms, General::kContinuePushMS); - GET_CONFIG(bool, s_modify_stamp, General::kModifyStamp); - - enable_hls = s_to_hls; - enable_mp4 = s_to_mp4; - enable_audio = s_enabel_audio; - add_mute_audio = s_add_mute_audio; - continue_push_ms = s_continue_push_ms; - mp4_as_player = s_mp4_as_player; - modify_stamp = s_modify_stamp; -} - -static std::shared_ptr makeRecorder(MediaSource &sender, const vector &tracks, Recorder::type type, const string &custom_path, size_t max_second){ - auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId(), custom_path, max_second); +static std::shared_ptr makeRecorder(MediaSource &sender, const vector &tracks, Recorder::type type, const ProtocolOption &option){ + auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId(), option); for (auto &track : tracks) { recorder->addTrack(track); } @@ -89,38 +71,40 @@ const std::string &MultiMediaSourceMuxer::getStreamId() const { return _stream_id; } +std::string MultiMediaSourceMuxer::shortUrl() const { + auto ret = getOriginUrl(MediaSource::NullMediaSource()); + if (!ret.empty()) { + return ret; + } + return _vhost + "/" + _app + "/" + _stream_id; +} + MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec, const ProtocolOption &option) { _poller = EventPollerPool::Instance().getPoller(); + _create_in_poller = _poller->isCurrentThread(); _vhost = vhost; _app = app; _stream_id = stream; _option = option; - _get_origin_url = [this, vhost, app, stream]() { - auto ret = getOriginUrl(MediaSource::NullMediaSource()); - if (!ret.empty()) { - return ret; - } - return vhost + "/" + app + "/" + stream; - }; if (option.enable_rtmp) { - _rtmp = std::make_shared(vhost, app, stream, std::make_shared(dur_sec)); + _rtmp = std::make_shared(vhost, app, stream, option, std::make_shared(dur_sec)); } if (option.enable_rtsp) { - _rtsp = std::make_shared(vhost, app, stream, std::make_shared(dur_sec)); + _rtsp = std::make_shared(vhost, app, stream, option, std::make_shared(dur_sec)); } if (option.enable_hls) { - _hls = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream, option.hls_save_path)); + _hls = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream, option)); } if (option.enable_mp4) { - _mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream, option.mp4_save_path, option.mp4_max_second); + _mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream, option); } if (option.enable_ts) { - _ts = std::make_shared(vhost, app, stream); + _ts = std::make_shared(vhost, app, stream, option); } #if defined(ENABLE_MP4) if (option.enable_fmp4) { - _fmp4 = std::make_shared(vhost, app, stream); + _fmp4 = std::make_shared(vhost, app, stream, option); } #endif @@ -200,12 +184,19 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { //此函数可能跨线程调用 bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { + onceToken token(nullptr, [&]() { + if (_option.mp4_as_player && type == Recorder::type_mp4) { + //开启关闭mp4录制,触发观看人数变化相关事件 + onReaderChanged(sender, totalReaderCount()); + } + }); switch (type) { case Recorder::type_hls : { if (!_hls) { //创建hls对象 - auto hls = dynamic_pointer_cast(makeRecorder(sender, getTracks(), type, custom_path, max_second)); + _option.hls_save_path = custom_path; + auto hls = dynamic_pointer_cast(makeRecorder(sender, getTracks(), type, _option)); if (hls) { //设置HlsMediaSource的事件监听器 hls->setListener(shared_from_this()); @@ -218,7 +209,9 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type case Recorder::type_mp4 : { if (start && !_mp4) { //开始录制 - _mp4 = makeRecorder(sender, getTracks(), type, custom_path, max_second); + _option.mp4_save_path = custom_path; + _option.mp4_max_second = max_second; + _mp4 = makeRecorder(sender, getTracks(), type, _option); } else if (!start && _mp4) { //停止录制 _mp4 = nullptr; @@ -264,7 +257,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE auto ssrc = args.ssrc; rtp_sender->setOnClose([weak_self, ssrc](const toolkit::SockException &ex) { if (auto strong_self = weak_self.lock()) { - WarnL << "stream:" << strong_self->_get_origin_url() << " stop send rtp:" << ssrc << ", reason:" << ex.what(); + WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex.what(); strong_self->_rtp_sender.erase(ssrc); //触发观看人数统计 strong_self->onReaderChanged(MediaSource::NullMediaSource(), strong_self->totalReaderCount()); @@ -308,7 +301,12 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { return _poller; } try { - return listener->getOwnerPoller(sender); + auto ret = listener->getOwnerPoller(sender); + if (ret != _poller) { + WarnL << "OwnerPoller changed:" << shortUrl(); + _poller = ret; + } + return ret; } catch (MediaSourceEvent::NotImplemented &) { // listener未重载getOwnerPoller return _poller; @@ -316,10 +314,6 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { } bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { - if (CodecL16 == track->getCodecId()) { - WarnL << "L16音频格式目前只支持RTSP协议推流拉流!!!"; - return false; - } bool ret = false; if (_rtmp) { @@ -350,6 +344,7 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { } void MultiMediaSourceMuxer::onAllTrackReady() { + CHECK(!_create_in_poller || getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread()); setMediaListener(getDelegate()); if (_rtmp) { @@ -367,7 +362,7 @@ void MultiMediaSourceMuxer::onAllTrackReady() { if (listener) { listener->onAllTrackReady(); } - InfoL << "stream: " << _get_origin_url() << " , codec info: " << getTrackInfoStr(this); + InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this); } void MultiMediaSourceMuxer::resetTracks() { diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index 0dd99d2e..5414ee9b 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -23,50 +23,11 @@ namespace mediakit { -class ProtocolOption { -public: - ProtocolOption(); - - //是否开启转换为hls - bool enable_hls = false; - //是否开启MP4录制 - bool enable_mp4 = false; - //是否将mp4录制当做观看者 - bool mp4_as_player = false; - //是否开启转换为rtsp/webrtc - bool enable_rtsp = true; - //是否开启转换为rtmp/flv - bool enable_rtmp = true; - //是否开启转换为http-ts/ws-ts - bool enable_ts = true; - //是否开启转换为http-fmp4/ws-fmp4 - bool enable_fmp4 = true; - - //转协议是否开启音频 - bool enable_audio = true; - //添加静音音频,在关闭音频时,此开关无效 - bool add_mute_audio = true; - - //mp4录制保存路径 - std::string mp4_save_path; - //mp4切片大小,单位秒 - size_t mp4_max_second = 0; - - //hls录制保存路径 - std::string hls_save_path; - - //断连续推延时,单位毫秒,默认采用配置文件 - uint32_t continue_push_ms; - - //时间戳修复这一路流标志位 - bool modify_stamp; -}; - class MultiMediaSourceMuxer : public MediaSourceEventInterceptor, public MediaSink, public std::enable_shared_from_this{ public: typedef std::shared_ptr Ptr; - class Listener{ + class Listener { public: Listener() = default; virtual ~Listener() = default; @@ -165,6 +126,7 @@ public: const std::string& getVhost() const; const std::string& getApp() const; const std::string& getStreamId() const; + std::string shortUrl() const; protected: /////////////////////////////////MediaSink override///////////////////////////////// @@ -189,6 +151,7 @@ protected: private: bool _is_enable = false; + bool _create_in_poller = false; std::string _vhost; std::string _app; std::string _stream_id; @@ -196,7 +159,6 @@ private: toolkit::Ticker _last_check; Stamp _stamp[2]; std::weak_ptr _track_listener; - std::function _get_origin_url; #if defined(ENABLE_RTPPROXY) std::unordered_map _rtp_sender; #endif //ENABLE_RTPPROXY diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 5dd4a02c..cb24064b 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -56,6 +56,7 @@ const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream"; const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader"; const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess"; const string kBroadcastSendRtpStopped = "kBroadcastSendRtpStopped"; +const string KBroadcastRtpServerTimeout = "KBroadcastRtpServerTimeout"; } // namespace Broadcast @@ -67,53 +68,84 @@ const string kFlowThreshold = GENERAL_FIELD "flowThreshold"; const string kStreamNoneReaderDelayMS = GENERAL_FIELD "streamNoneReaderDelayMS"; const string kMaxStreamWaitTimeMS = GENERAL_FIELD "maxStreamWaitMS"; const string kEnableVhost = GENERAL_FIELD "enableVhost"; -const string kAddMuteAudio = GENERAL_FIELD "addMuteAudio"; const string kResetWhenRePlay = GENERAL_FIELD "resetWhenRePlay"; -const string kPublishToHls = GENERAL_FIELD "publishToHls"; -const string kPublishToMP4 = GENERAL_FIELD "publishToMP4"; const string kMergeWriteMS = GENERAL_FIELD "mergeWriteMS"; -const string kModifyStamp = GENERAL_FIELD "modifyStamp"; -const string kHlsDemand = GENERAL_FIELD "hls_demand"; -const string kRtspDemand = GENERAL_FIELD "rtsp_demand"; -const string kRtmpDemand = GENERAL_FIELD "rtmp_demand"; -const string kTSDemand = GENERAL_FIELD "ts_demand"; -const string kFMP4Demand = GENERAL_FIELD "fmp4_demand"; -const string kEnableAudio = GENERAL_FIELD "enable_audio"; const string kCheckNvidiaDev = GENERAL_FIELD "check_nvidia_dev"; const string kEnableFFmpegLog = GENERAL_FIELD "enable_ffmpeg_log"; const string kWaitTrackReadyMS = GENERAL_FIELD "wait_track_ready_ms"; const string kWaitAddTrackMS = GENERAL_FIELD "wait_add_track_ms"; const string kUnreadyFrameCache = GENERAL_FIELD "unready_frame_cache"; -const string kContinuePushMS = GENERAL_FIELD "continue_push_ms"; static onceToken token([]() { mINI::Instance()[kFlowThreshold] = 1024; mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000; mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000; mINI::Instance()[kEnableVhost] = 0; - mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kResetWhenRePlay] = 1; - mINI::Instance()[kPublishToHls] = 1; - mINI::Instance()[kPublishToMP4] = 0; mINI::Instance()[kMergeWriteMS] = 0; - mINI::Instance()[kModifyStamp] = 0; mINI::Instance()[kMediaServerId] = makeRandStr(16); - mINI::Instance()[kHlsDemand] = 0; - mINI::Instance()[kRtspDemand] = 0; - mINI::Instance()[kRtmpDemand] = 0; - mINI::Instance()[kTSDemand] = 0; - mINI::Instance()[kFMP4Demand] = 0; - mINI::Instance()[kEnableAudio] = 1; mINI::Instance()[kCheckNvidiaDev] = 1; mINI::Instance()[kEnableFFmpegLog] = 0; mINI::Instance()[kWaitTrackReadyMS] = 10000; mINI::Instance()[kWaitAddTrackMS] = 3000; mINI::Instance()[kUnreadyFrameCache] = 100; - mINI::Instance()[kContinuePushMS] = 15 * 1000; }); } // namespace General +namespace Protocol { +#define PROTOCOL_FIELD "protocol." +const string kModifyStamp = PROTOCOL_FIELD "modify_stamp"; +const string kEnableAudio = PROTOCOL_FIELD "enable_audio"; +const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; +const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; + +const string kEnableHls = PROTOCOL_FIELD "enable_hls"; +const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4"; +const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp"; +const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp"; +const string kEnableTS = PROTOCOL_FIELD "enable_ts"; +const string kEnableFMP4 = PROTOCOL_FIELD "enable_fmp4"; + +const string kMP4AsPlayer = PROTOCOL_FIELD "mp4_as_player"; +const string kMP4MaxSecond = PROTOCOL_FIELD "mp4_max_second"; +const string kMP4SavePath = PROTOCOL_FIELD "mp4_save_path"; + +const string kHlsSavePath = PROTOCOL_FIELD "hls_save_path"; + +const string kHlsDemand = PROTOCOL_FIELD "hls_demand"; +const string kRtspDemand = PROTOCOL_FIELD "rtsp_demand"; +const string kRtmpDemand = PROTOCOL_FIELD "rtmp_demand"; +const string kTSDemand = PROTOCOL_FIELD "ts_demand"; +const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand"; + +static onceToken token([]() { + mINI::Instance()[kModifyStamp] = 0; + mINI::Instance()[kEnableAudio] = 1; + mINI::Instance()[kAddMuteAudio] = 1; + mINI::Instance()[kContinuePushMS] = 15000; + + mINI::Instance()[kEnableHls] = 1; + mINI::Instance()[kEnableMP4] = 0; + mINI::Instance()[kEnableRtsp] = 1; + mINI::Instance()[kEnableRtmp] = 1; + mINI::Instance()[kEnableTS] = 1; + mINI::Instance()[kEnableFMP4] = 1; + + mINI::Instance()[kMP4AsPlayer] = 0; + mINI::Instance()[kMP4MaxSecond] = 3600; + mINI::Instance()[kMP4SavePath] = "./www"; + + mINI::Instance()[kHlsSavePath] = "./www"; + + mINI::Instance()[kHlsDemand] = 0; + mINI::Instance()[kRtspDemand] = 0; + mINI::Instance()[kRtmpDemand] = 0; + mINI::Instance()[kTSDemand] = 0; + mINI::Instance()[kFMP4Demand] = 0; +}); +} // !Protocol + ////////////HTTP配置/////////// namespace Http { #define HTTP_FIELD "http." @@ -173,6 +205,7 @@ const string kAuthBasic = RTSP_FIELD "authBasic"; const string kHandshakeSecond = RTSP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond"; const string kDirectProxy = RTSP_FIELD "directProxy"; +const string kLowLatency = RTSP_FIELD"lowLatency"; static onceToken token([]() { // 默认Md5方式认证 @@ -180,6 +213,7 @@ static onceToken token([]() { mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; mINI::Instance()[kDirectProxy] = 1; + mINI::Instance()[kLowLatency] = 0; }); } // namespace Rtsp @@ -206,10 +240,14 @@ const string kAudioMtuSize = RTP_FIELD "audioMtuSize"; // rtp包最大长度限制,单位是KB const string kRtpMaxSize = RTP_FIELD "rtpMaxSize"; +const string kLowLatency = RTP_FIELD "lowLatency"; + static onceToken token([]() { mINI::Instance()[kVideoMtuSize] = 1400; mINI::Instance()[kAudioMtuSize] = 600; mINI::Instance()[kRtpMaxSize] = 10; + mINI::Instance()[kLowLatency] = 0; + }); } // namespace Rtp @@ -235,22 +273,16 @@ namespace Record { #define RECORD_FIELD "record." const string kAppName = RECORD_FIELD "appName"; const string kSampleMS = RECORD_FIELD "sampleMS"; -const string kFileSecond = RECORD_FIELD "fileSecond"; -const string kFilePath = RECORD_FIELD "filePath"; const string kFileBufSize = RECORD_FIELD "fileBufSize"; const string kFastStart = RECORD_FIELD "fastStart"; const string kFileRepeat = RECORD_FIELD "fileRepeat"; -const string kMP4AsPlayer = RECORD_FIELD "mp4_as_player"; static onceToken token([]() { mINI::Instance()[kAppName] = "record"; mINI::Instance()[kSampleMS] = 500; - mINI::Instance()[kFileSecond] = 60 * 60; - mINI::Instance()[kFilePath] = "./www"; mINI::Instance()[kFileBufSize] = 64 * 1024; mINI::Instance()[kFastStart] = false; mINI::Instance()[kFileRepeat] = false; - mINI::Instance()[kMP4AsPlayer] = false; }); } // namespace Record @@ -262,7 +294,6 @@ const string kSegmentNum = HLS_FIELD "segNum"; const string kSegmentKeep = HLS_FIELD "segKeep"; const string kSegmentRetain = HLS_FIELD "segRetain"; const string kFileBufSize = HLS_FIELD "fileBufSize"; -const string kFilePath = HLS_FIELD "filePath"; const string kBroadcastRecordTs = HLS_FIELD "broadcastRecordTs"; const string kDeleteDelaySec = HLS_FIELD "deleteDelaySec"; @@ -272,9 +303,8 @@ static onceToken token([]() { mINI::Instance()[kSegmentKeep] = false; mINI::Instance()[kSegmentRetain] = 5; mINI::Instance()[kFileBufSize] = 64 * 1024; - mINI::Instance()[kFilePath] = "./www"; mINI::Instance()[kBroadcastRecordTs] = false; - mINI::Instance()[kDeleteDelaySec] = 0; + mINI::Instance()[kDeleteDelaySec] = 10; }); } // namespace Hls @@ -317,6 +347,7 @@ const string kMediaTimeoutMS = "media_timeout_ms"; const string kBeatIntervalMS = "beat_interval_ms"; const string kBenchmarkMode = "benchmark_mode"; const string kWaitTrackReady = "wait_track_ready"; +const string kPlayTrack = "play_track"; } // namespace Client } // namespace mediakit diff --git a/src/Common/config.h b/src/Common/config.h index 7e6b68a9..36177ec4 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -45,14 +45,11 @@ extern const std::string kBroadcastRecordTs; // 收到http api请求广播 extern const std::string kBroadcastHttpRequest; -#define BroadcastHttpRequestArgs \ - const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, SockInfo &sender +#define BroadcastHttpRequestArgs const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, SockInfo &sender // 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 extern const std::string kBroadcastHttpAccess; -#define BroadcastHttpAccessArgs \ - const Parser &parser, const std::string &path, const bool &is_dir, \ - const HttpSession::HttpAccessPathInvoker &invoker, SockInfo &sender +#define BroadcastHttpAccessArgs const Parser &parser, const std::string &path, const bool &is_dir, const HttpSession::HttpAccessPathInvoker &invoker, SockInfo &sender // 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 // 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 @@ -66,9 +63,7 @@ extern const std::string kBroadcastOnGetRtspRealm; // 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 // 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码 extern const std::string kBroadcastOnRtspAuth; -#define BroadcastOnRtspAuthArgs \ - const MediaInfo &args, const std::string &realm, const std::string &user_name, const bool &must_no_encrypt, \ - const RtspSession::onAuth &invoker, SockInfo &sender +#define BroadcastOnRtspAuthArgs const MediaInfo &args, const std::string &realm, const std::string &user_name, const bool &must_no_encrypt, const RtspSession::onAuth &invoker, SockInfo &sender // 推流鉴权结果回调对象 // 如果err为空则代表鉴权成功 @@ -76,8 +71,7 @@ using PublishAuthInvoker = std::functiondecodeAble()) { _have_decode_able_frame = true; } + _cb = std::move(cb); _frame_cache.emplace_back(Frame::getCacheAbleFrame(frame)); return true; } @@ -203,4 +204,38 @@ void FrameMerger::clear() { _have_decode_able_frame = false; } +void FrameMerger::flush() { + if (_cb) { + inputFrame(nullptr, std::move(_cb), nullptr); + } + clear(); +} +/** + * 写帧接口转function,辅助类 + */ +class FrameWriterInterfaceHelper : public FrameWriterInterface { +public: + using Ptr = std::shared_ptr; + using onWriteFrame = std::function; + + /** + * inputFrame后触发onWriteFrame回调 + */ + FrameWriterInterfaceHelper(onWriteFrame cb) { _callback = std::move(cb); } + + virtual ~FrameWriterInterfaceHelper() = default; + + /** + * 写入帧数据 + */ + bool inputFrame(const Frame::Ptr &frame) override { return _callback(frame); } + +private: + onWriteFrame _callback; +}; + +FrameWriterInterface* FrameDispatcher::addDelegate(std::function cb) { + return addDelegate(std::make_shared(std::move(cb))); +} + }//namespace mediakit diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 72a8a7ed..c298ed11 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -38,7 +38,7 @@ typedef enum { XX(CodecL16, TrackAudio, 6, "L16", PSI_STREAM_RESERVED) \ XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \ XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \ - XX(CodecAV1, TrackVideo, 9, "AV1X", PSI_STREAM_AV1) + XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1) typedef enum { CodecInvalid = -1, @@ -271,29 +271,11 @@ public: * 写入帧数据 */ virtual bool inputFrame(const Frame::Ptr &frame) = 0; -}; - -/** - * 写帧接口转function,辅助类 - */ -class FrameWriterInterfaceHelper : public FrameWriterInterface { -public: - typedef std::shared_ptr Ptr; - typedef std::function onWriteFrame; /** - * inputFrame后触发onWriteFrame回调 + * 刷新输出所有frame缓存 */ - FrameWriterInterfaceHelper(const onWriteFrame &cb) { _writeCallback = cb; } - virtual ~FrameWriterInterfaceHelper() = default; - - /** - * 写入帧数据 - */ - bool inputFrame(const Frame::Ptr &frame) override { return _writeCallback(frame); } - -private: - onWriteFrame _writeCallback; + virtual void flush() {}; }; /** @@ -308,11 +290,13 @@ public: /** * 添加代理 */ - void addDelegate(const FrameWriterInterface::Ptr &delegate) { + FrameWriterInterface* addDelegate(FrameWriterInterface::Ptr delegate) { std::lock_guard lck(_mtx); - _delegates.emplace(delegate.get(), delegate); + return _delegates.emplace(delegate.get(), std::move(delegate)).first->second.get(); } + FrameWriterInterface* addDelegate(std::function cb); + /** * 删除代理 */ @@ -542,8 +526,13 @@ public: FrameMerger(int type); ~FrameMerger() = default; + /** + * 刷新输出缓冲,注意此时会调用FrameMerger::inputFrame传入的onOutput回调 + * 请注意回调捕获参数此时是否有效 + */ + void flush(); void clear(); - bool inputFrame(const Frame::Ptr &frame, const onOutput &cb, toolkit::BufferLikeString *buffer = nullptr); + bool inputFrame(const Frame::Ptr &frame, onOutput cb, toolkit::BufferLikeString *buffer = nullptr); private: bool willFlush(const Frame::Ptr &frame) const; @@ -552,6 +541,7 @@ private: private: int _type; bool _have_decode_able_frame = false; + onOutput _cb; toolkit::List _frame_cache; }; diff --git a/src/Extension/H264.cpp b/src/Extension/H264.cpp index 3426f5cc..00217df8 100644 --- a/src/Extension/H264.cpp +++ b/src/Extension/H264.cpp @@ -245,23 +245,19 @@ public: _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n"; _printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id="; - char strTemp[1024]; uint32_t profile_level_id = 0; if (strSPS.length() >= 4) { // sanity check profile_level_id = (uint8_t(strSPS[1]) << 16) | (uint8_t(strSPS[2]) << 8) | (uint8_t(strSPS[3])); // profile_idc|constraint_setN_flag|level_idc } - memset(strTemp, 0, sizeof(strTemp)); - snprintf(strTemp, sizeof(strTemp), "%06X", profile_level_id); - _printer << strTemp; + + char profile[8]; + snprintf(profile, sizeof(profile), "%06X", profile_level_id); + _printer << profile; _printer << "; sprop-parameter-sets="; - memset(strTemp, 0, sizeof(strTemp)); - av_base64_encode(strTemp, sizeof(strTemp), (uint8_t *)strSPS.data(), (int)strSPS.size()); - _printer << strTemp << ","; - memset(strTemp, 0, sizeof(strTemp)); - av_base64_encode(strTemp, sizeof(strTemp), (uint8_t *)strPPS.data(), (int)strPPS.size()); - _printer << strTemp << "\r\n"; + _printer << encodeBase64(strSPS) << ","; + _printer << encodeBase64(strPPS) << "\r\n"; _printer << "a=control:trackID=" << (int)TrackVideo << "\r\n"; } diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index 2af6c0b2..f85ae425 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -124,26 +124,32 @@ void H264RtmpEncoder::makeConfigPacket(){ } } +void H264RtmpEncoder::flush() { + inputFrame(nullptr); +} + bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { - auto data = frame->data() + frame->prefixSize(); - auto len = frame->size() - frame->prefixSize(); - auto type = H264_TYPE(data[0]); - switch (type) { - case H264Frame::NAL_SPS: { - if (!_got_config_frame) { - _sps = string(data, len); - makeConfigPacket(); + if (frame) { + auto data = frame->data() + frame->prefixSize(); + auto len = frame->size() - frame->prefixSize(); + auto type = H264_TYPE(data[0]); + switch (type) { + case H264Frame::NAL_SPS: { + if (!_got_config_frame) { + _sps = string(data, len); + makeConfigPacket(); + } + break; } - break; - } - case H264Frame::NAL_PPS: { - if (!_got_config_frame) { - _pps = string(data, len); - makeConfigPacket(); + case H264Frame::NAL_PPS: { + if (!_got_config_frame) { + _pps = string(data, len); + makeConfigPacket(); + } + break; } - break; + default: break; } - default : break; } if (!_rtmp_packet) { diff --git a/src/Extension/H264Rtmp.h b/src/Extension/H264Rtmp.h index c38e667b..b27309dc 100644 --- a/src/Extension/H264Rtmp.h +++ b/src/Extension/H264Rtmp.h @@ -62,7 +62,7 @@ public: * @param track */ H264RtmpEncoder(const Track::Ptr &track); - ~H264RtmpEncoder() {} + ~H264RtmpEncoder() = default; /** * 输入264帧,可以不带sps pps @@ -70,6 +70,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + /** * 生成config包 */ diff --git a/src/Extension/H264Rtp.cpp b/src/Extension/H264Rtp.cpp index dc13a03a..1130102f 100644 --- a/src/Extension/H264Rtp.cpp +++ b/src/Extension/H264Rtp.cpp @@ -283,14 +283,30 @@ bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { default: break; } - if (_last_frame) { - //如果时间戳发生了变化,那么markbit才置true - inputFrame_l(_last_frame, _last_frame->pts() != frame->pts()); + GET_CONFIG(int,lowLatency,Rtp::kLowLatency); + if (lowLatency) { // 低延迟模式 + if (_last_frame) { + flush(); + } + inputFrame_l(frame, true); + } else { + if (_last_frame) { + //如果时间戳发生了变化,那么markbit才置true + inputFrame_l(_last_frame, _last_frame->pts() != frame->pts()); + } + _last_frame = Frame::getCacheAbleFrame(frame); } - _last_frame = Frame::getCacheAbleFrame(frame); return true; } +void H264RtpEncoder::flush() { + if (_last_frame) { + // 如果时间戳发生了变化,那么markbit才置true + inputFrame_l(_last_frame, true); + _last_frame = nullptr; + } +} + bool H264RtpEncoder::inputFrame_l(const Frame::Ptr &frame, bool is_mark){ if (frame->keyFrame()) { //保证每一个关键帧前都有SPS与PPS diff --git a/src/Extension/H264Rtp.h b/src/Extension/H264Rtp.h index be00f7af..4736f512 100644 --- a/src/Extension/H264Rtp.h +++ b/src/Extension/H264Rtp.h @@ -85,6 +85,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + private: void insertConfigFrame(uint64_t pts); bool inputFrame_l(const Frame::Ptr &frame, bool is_mark); diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index 19e450e8..493edac8 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -138,33 +138,39 @@ void H265RtmpEncoder::makeConfigPacket(){ } } +void H265RtmpEncoder::flush() { + inputFrame(nullptr); +} + bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { - auto data = frame->data() + frame->prefixSize(); - auto len = frame->size() - frame->prefixSize(); - auto type = H265_TYPE(data[0]); - switch (type) { - case H265Frame::NAL_SPS: { - if (!_got_config_frame) { - _sps = string(data, len); - makeConfigPacket(); + if (frame) { + auto data = frame->data() + frame->prefixSize(); + auto len = frame->size() - frame->prefixSize(); + auto type = H265_TYPE(data[0]); + switch (type) { + case H265Frame::NAL_SPS: { + if (!_got_config_frame) { + _sps = string(data, len); + makeConfigPacket(); + } + break; } - break; - } - case H265Frame::NAL_PPS: { - if (!_got_config_frame) { - _pps = string(data, len); - makeConfigPacket(); + case H265Frame::NAL_PPS: { + if (!_got_config_frame) { + _pps = string(data, len); + makeConfigPacket(); + } + break; } - break; - } - case H265Frame::NAL_VPS: { - if (!_got_config_frame) { - _vps = string(data, len); - makeConfigPacket(); + case H265Frame::NAL_VPS: { + if (!_got_config_frame) { + _vps = string(data, len); + makeConfigPacket(); + } + break; } - break; + default: break; } - default: break; } if (!_rtmp_packet) { diff --git a/src/Extension/H265Rtmp.h b/src/Extension/H265Rtmp.h index 8200ac98..3c062cfc 100644 --- a/src/Extension/H265Rtmp.h +++ b/src/Extension/H265Rtmp.h @@ -60,7 +60,7 @@ public: * @param track */ H265RtmpEncoder(const Track::Ptr &track); - ~H265RtmpEncoder() {} + ~H265RtmpEncoder() = default; /** * 输入265帧,可以不带sps pps @@ -68,6 +68,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + /** * 生成config包 */ diff --git a/src/FMP4/FMP4MediaSource.h b/src/FMP4/FMP4MediaSource.h index 2b4a1504..52174027 100644 --- a/src/FMP4/FMP4MediaSource.h +++ b/src/FMP4/FMP4MediaSource.h @@ -31,7 +31,7 @@ public: }; //FMP4直播源 -class FMP4MediaSource : public MediaSource, public toolkit::RingDelegate, private PacketCache{ +class FMP4MediaSource final : public MediaSource, public toolkit::RingDelegate, private PacketCache{ public: using Ptr = std::shared_ptr; using RingDataType = std::shared_ptr >; @@ -42,7 +42,7 @@ public: const std::string &stream_id, int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} - ~FMP4MediaSource() override = default; + ~FMP4MediaSource() override { flush(); } /** * 获取媒体源的环形缓冲 diff --git a/src/FMP4/FMP4MediaSourceMuxer.h b/src/FMP4/FMP4MediaSourceMuxer.h index cad7caad..ad16f352 100644 --- a/src/FMP4/FMP4MediaSourceMuxer.h +++ b/src/FMP4/FMP4MediaSourceMuxer.h @@ -18,18 +18,20 @@ namespace mediakit { -class FMP4MediaSourceMuxer : public MP4MuxerMemory, public MediaSourceEventInterceptor, - public std::enable_shared_from_this { +class FMP4MediaSourceMuxer final : public MP4MuxerMemory, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; FMP4MediaSourceMuxer(const std::string &vhost, const std::string &app, - const std::string &stream_id) { + const std::string &stream_id, + const ProtocolOption &option) { + _option = option; _media_src = std::make_shared(vhost, app, stream_id); } - ~FMP4MediaSourceMuxer() override = default; + ~FMP4MediaSourceMuxer() override { MP4MuxerMemory::flush(); }; void setListener(const std::weak_ptr &listener){ setDelegate(listener); @@ -41,30 +43,27 @@ public: } void onReaderChanged(MediaSource &sender, int size) override { - GET_CONFIG(bool, fmp4_demand, General::kFMP4Demand); - _enabled = fmp4_demand ? size : true; - if (!size && fmp4_demand) { + _enabled = _option.fmp4_demand ? size : true; + if (!size && _option.fmp4_demand) { _clear_cache = true; } MediaSourceEventInterceptor::onReaderChanged(sender, size); } bool inputFrame(const Frame::Ptr &frame) override { - GET_CONFIG(bool, fmp4_demand, General::kFMP4Demand); - if (_clear_cache && fmp4_demand) { + if (_clear_cache && _option.fmp4_demand) { _clear_cache = false; _media_src->clearCache(); } - if (_enabled || !fmp4_demand) { + if (_enabled || !_option.fmp4_demand) { return MP4MuxerMemory::inputFrame(frame); } return false; } bool isEnabled() { - GET_CONFIG(bool, fmp4_demand, General::kFMP4Demand); //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 - return fmp4_demand ? (_clear_cache ? true : _enabled) : true; + return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true; } void onAllTrackReady() { @@ -84,6 +83,7 @@ protected: private: bool _enabled = true; bool _clear_cache = false; + ProtocolOption _option; FMP4MediaSource::Ptr _media_src; }; diff --git a/src/Http/HlsPlayer.cpp b/src/Http/HlsPlayer.cpp index 25429824..f22ca7c4 100644 --- a/src/Http/HlsPlayer.cpp +++ b/src/Http/HlsPlayer.cpp @@ -380,10 +380,14 @@ void HlsPlayerImp::onPlayResult(const SockException &ex) { void HlsPlayerImp::onShutdown(const SockException &ex) { while (_demuxer) { try { + //shared_from_this()可能抛异常 std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); + if (_decoder) { + _decoder->flush(); + } + //等待所有frame flush输出后,再触发onShutdown事件 static_pointer_cast(_demuxer)->pushTask([weak_self, ex]() { - auto strong_self = weak_self.lock(); - if (strong_self) { + if (auto strong_self = weak_self.lock()) { strong_self->_demuxer = nullptr; strong_self->onShutdown(ex); } diff --git a/src/Http/HttpCookieManager.h b/src/Http/HttpCookieManager.h index f1dcfa68..e55c2086 100644 --- a/src/Http/HttpCookieManager.h +++ b/src/Http/HttpCookieManager.h @@ -91,8 +91,8 @@ public: * 获取附加数据 */ template - const T& getAttach() const { - return *static_cast(_attach.get()); + T& getAttach() { + return *static_cast(_attach.get()); } private: diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 51d9af2b..50014825 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -33,8 +33,9 @@ static int kHlsCookieSecond = 60; static const string kCookieName = "ZL_COOKIE"; static const string kHlsSuffix = "/hls.m3u8"; -class HttpCookieAttachment { -public: +struct HttpCookieAttachment { + //是否已经查找到过MediaSource + bool _find_src = false; //cookie生效作用域,本cookie只对该目录下的文件生效 string _path; //上次鉴权失败信息,为空则上次鉴权成功 @@ -180,7 +181,7 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st } //拦截hls的播放请求 -static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){ +static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, const HttpSession::HttpAccessPathInvoker &invoker,Session &sender){ //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) { //cookie有效期为kHlsCookieSecond @@ -235,7 +236,7 @@ public: * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码 * 5、触发kBroadcastHttpAccess事件 */ -static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir, +static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir, const function &callback) { //获取用户唯一id auto uid = parser.Params(); @@ -352,7 +353,7 @@ static string pathCat(const string &a, const string &b){ * @param file_path 文件绝对路径 * @param cb 回调对象 */ -static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) { +static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) { bool is_hls = end_with(file_path, kHlsSuffix); if (!is_hls && !File::fileExist(file_path.data())) { //文件不存在且不是hls,那么直接返回404 @@ -365,7 +366,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo replace(const_cast(media_info._streamid), kHlsSuffix, ""); } - weak_ptr weakSession = sender.shared_from_this(); + weak_ptr weakSession = sender.shared_from_this(); //判断是否有权限访问该文件 canAccessPath(sender, parser, media_info, false, [cb, file_path, parser, is_hls, media_info, weakSession](const string &err_msg, const HttpServerCookie::Ptr &cookie) { auto strongSession = weakSession.lock(); @@ -425,6 +426,11 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo response_file(cookie, cb, file_path, parser, src->getIndexFile()); return; } + if (cookie->getAttach()._find_src) { + //查找过MediaSource,但是流已经注销了,不用再查找 + response_file(cookie, cb, file_path, parser); + return; + } //hls流可能未注册,MediaSource::findAsync可以触发not_found事件,然后再按需推拉流 MediaSource::findAsync(media_info, strongSession, [response_file, cookie, cb, file_path, parser](const MediaSource::Ptr &src) { @@ -439,6 +445,8 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo attach._hls_data->setMediaSource(hls); //添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成) attach._hls_data->addByteUsage(0); + //标记找到MediaSource + attach._find_src = true; // m3u8文件可能不存在, 等待m3u8索引文件按需生成 hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) { @@ -448,7 +456,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo }); } -static string getFilePath(const Parser &parser,const MediaInfo &media_info, TcpSession &sender){ +static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){ GET_CONFIG(bool, enableVhost, General::kEnableVhost); GET_CONFIG(string, rootPath, Http::kRootPath); GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) { @@ -483,7 +491,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, TcpS * @param parser http请求 * @param cb 回调对象 */ -void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const HttpFileManager::invoker &cb) { +void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) { auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl(); MediaInfo media_info(fullUrl); auto file_path = getFilePath(parser, media_info, sender); diff --git a/src/Http/HttpFileManager.h b/src/Http/HttpFileManager.h index 7a3766b1..d30b89b3 100644 --- a/src/Http/HttpFileManager.h +++ b/src/Http/HttpFileManager.h @@ -14,7 +14,7 @@ #include "HttpBody.h" #include "HttpCookie.h" #include "Common/Parser.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" #include "Util/function_traits.h" namespace mediakit { @@ -54,7 +54,7 @@ public: * @param parser http请求 * @param cb 回调对象 */ - static void onAccessPath(toolkit::TcpSession &sender, Parser &parser, const invoker &cb); + static void onAccessPath(toolkit::Session &sender, Parser &parser, const invoker &cb); /** * 获取mime值 diff --git a/src/Http/HttpRequestSplitter.cpp b/src/Http/HttpRequestSplitter.cpp index 4e82b83b..4d859686 100644 --- a/src/Http/HttpRequestSplitter.cpp +++ b/src/Http/HttpRequestSplitter.cpp @@ -14,8 +14,8 @@ using namespace toolkit; using namespace std; -//协议解析最大缓存1兆数据 -static constexpr size_t kMaxCacheSize = 1 * 1024 * 1024; +//协议解析最大缓存4兆数据 +static constexpr size_t kMaxCacheSize = 4 * 1024 * 1024; namespace mediakit { diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 413bf9dd..fd59416b 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -23,7 +23,7 @@ using namespace toolkit; namespace mediakit { -HttpSession::HttpSession(const Socket::Ptr &pSock) : TcpSession(pSock) { +HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) { TraceP(this); GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond); pSock->setSendTimeOutSecond(keep_alive_sec); @@ -103,9 +103,7 @@ void HttpSession::onError(const SockException& err) { //flv/ts播放器 uint64_t duration = _ticker.createdTime() / 1000; WarnP(this) << "FLV/TS/FMP4播放器(" - << _mediaInfo._vhost << "/" - << _mediaInfo._app << "/" - << _mediaInfo._streamid + << _mediaInfo.shortUrl() << ")断开:" << err.what() << ",耗时(s):" << duration; @@ -442,7 +440,7 @@ class AsyncSenderData { public: friend class AsyncSender; typedef std::shared_ptr Ptr; - AsyncSenderData(const TcpSession::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) { + AsyncSenderData(const Session::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) { _session = dynamic_pointer_cast(session); _body = body; _close_when_complete = close_when_complete; @@ -674,7 +672,10 @@ bool HttpSession::emitHttpEvent(bool doInvoke){ std::string HttpSession::get_peer_ip() { GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader); - return forwarded_ip_header.empty() ? TcpSession::get_peer_ip() : _parser.getHeader()[forwarded_ip_header]; + if(!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()){ + return _parser.getHeader()[forwarded_ip_header]; + } + return Session::get_peer_ip(); } void HttpSession::Handle_Req_POST(ssize_t &content_len) { diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 6f5bebdf..dadb5e52 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -12,7 +12,7 @@ #define SRC_HTTP_HTTPSESSION_H_ #include -#include "Network/TcpSession.h" +#include "Network/Session.h" #include "Rtmp/RtmpMediaSource.h" #include "Rtmp/FlvMuxer.h" #include "HttpRequestSplitter.h" @@ -24,7 +24,7 @@ namespace mediakit { -class HttpSession: public toolkit::TcpSession, +class HttpSession: public toolkit::Session, public FlvMuxer, public HttpRequestSplitter, public WebSocketSplitter { @@ -139,7 +139,7 @@ private: std::function _contentCallBack; }; -using HttpsSession = toolkit::TcpSessionWithSSL; +using HttpsSession = toolkit::SessionWithSSL; } /* namespace mediakit */ diff --git a/src/Http/TsplayerImp.cpp b/src/Http/TsplayerImp.cpp index 042c1b43..05db8a61 100644 --- a/src/Http/TsplayerImp.cpp +++ b/src/Http/TsplayerImp.cpp @@ -47,10 +47,14 @@ void TsPlayerImp::onPlayResult(const SockException &ex) { void TsPlayerImp::onShutdown(const SockException &ex) { while (_demuxer) { try { + //shared_from_this()可能抛异常 std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); + if (_decoder) { + _decoder->flush(); + } + //等待所有frame flush输出后,再触发onShutdown事件 static_pointer_cast(_demuxer)->pushTask([weak_self, ex]() { - auto strong_self = weak_self.lock(); - if (strong_self) { + if (auto strong_self = weak_self.lock()) { strong_self->_demuxer = nullptr; strong_self->onShutdown(ex); } diff --git a/src/Http/WebSocketSession.h b/src/Http/WebSocketSession.h index 832fe338..c7eb9b8f 100644 --- a/src/Http/WebSocketSession.h +++ b/src/Http/WebSocketSession.h @@ -27,18 +27,18 @@ public: }; /** - * 该类实现了TcpSession派生类发送数据的截取 + * 该类实现了Session派生类发送数据的截取 * 目的是发送业务数据前进行websocket协议的打包 */ -template -class TcpSessionTypeImp : public TcpSessionType, public SendInterceptor{ +template +class SessionTypeImp : public SessionType, public SendInterceptor{ public: - typedef std::shared_ptr Ptr; + using Ptr = std::shared_ptr; - TcpSessionTypeImp(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock) : - TcpSessionType(pSock), _identifier(parent.getIdentifier()) {} + SessionTypeImp(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock) : + SessionType(pSock), _identifier(parent.getIdentifier()) {} - ~TcpSessionTypeImp() {} + ~SessionTypeImp() = default; /** * 设置发送数据截取回调函数 @@ -58,7 +58,7 @@ protected: if (_beforeSendCB) { return _beforeSendCB(buf); } - return TcpSessionType::send(std::move(buf)); + return SessionType::send(std::move(buf)); } std::string getIdentifier() const override { @@ -70,12 +70,12 @@ private: onBeforeSendCB _beforeSendCB; }; -template -class TcpSessionCreator { +template +class SessionCreator { public: - //返回的TcpSession必须派生于SendInterceptor,可以返回null - toolkit::TcpSession::Ptr operator()(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock){ - return std::make_shared >(header,parent,pSock); + //返回的Session必须派生于SendInterceptor,可以返回null + toolkit::Session::Ptr operator()(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock){ + return std::make_shared >(header,parent,pSock); } }; @@ -228,15 +228,15 @@ private: std::string _payload_cache; std::string _payload_section; std::weak_ptr _weak_server; - toolkit::TcpSession::Ptr _session; + toolkit::Session::Ptr _session; Creator _creator; }; -template -class WebSocketSession : public WebSocketSessionBase,HttpSessionType,DataType>{ +template +class WebSocketSession : public WebSocketSessionBase,HttpSessionType,DataType>{ public: - WebSocketSession(const toolkit::Socket::Ptr &pSock) : WebSocketSessionBase,HttpSessionType,DataType>(pSock){} + WebSocketSession(const toolkit::Socket::Ptr &pSock) : WebSocketSessionBase,HttpSessionType,DataType>(pSock){} virtual ~WebSocketSession(){} }; diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index 9fb6843d..68dc6159 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -112,9 +112,9 @@ bool Demuxer::addTrack(const Track::Ptr &track) { } if (_sink->addTrack(track)) { - track->addDelegate(std::make_shared([this](const Frame::Ptr &frame) { + track->addDelegate([this](const Frame::Ptr &frame) { return _sink->inputFrame(frame); - })); + }); return true; } return false; diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index ec0eb210..477b83f0 100644 --- a/src/Player/PlayerProxy.cpp +++ b/src/Player/PlayerProxy.cpp @@ -143,11 +143,7 @@ void PlayerProxy::rePlay(const string &strUrl, int iFailedCnt) { }, getPoller()); } -bool PlayerProxy::close(MediaSource &sender, bool force) { - if (!force && totalReaderCount()) { - return false; - } - +bool PlayerProxy::close(MediaSource &sender) { //通知其停止推流 weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); getPoller()->async_first([weakSelf]() { @@ -160,7 +156,7 @@ bool PlayerProxy::close(MediaSource &sender, bool force) { strongSelf->teardown(); }); _on_close(SockException(Err_shutdown, "closed by user")); - WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + WarnL << "close media: " << sender.getUrl(); return true; } diff --git a/src/Player/PlayerProxy.h b/src/Player/PlayerProxy.h index 6e3a2401..970c620a 100644 --- a/src/Player/PlayerProxy.h +++ b/src/Player/PlayerProxy.h @@ -54,7 +54,7 @@ public: private: //MediaSourceEvent override - bool close(MediaSource &sender,bool force) override; + bool close(MediaSource &sender) override; int totalReaderCount(MediaSource &sender) override; MediaOriginType getOriginType(MediaSource &sender) const override; std::string getOriginUrl(MediaSource &sender) const override; diff --git a/src/Record/HlsMediaSource.cpp b/src/Record/HlsMediaSource.cpp index 63c0085b..adcc909a 100644 --- a/src/Record/HlsMediaSource.cpp +++ b/src/Record/HlsMediaSource.cpp @@ -40,8 +40,7 @@ HlsCookieData::~HlsCookieData() { if (*_added) { uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000; WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port() - << ") " << "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid - << ")断开,耗时(s):" << duration; + << ") " << "HLS播放器(" << _info.shortUrl() << ")断开,耗时(s):" << duration; GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); uint64_t bytes = _bytes.load(); diff --git a/src/Record/HlsRecorder.h b/src/Record/HlsRecorder.h index f3ddfcc0..47fdf4fe 100644 --- a/src/Record/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). @@ -17,22 +17,23 @@ namespace mediakit { -class HlsRecorder : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this { +class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; - HlsRecorder(const std::string &m3u8_file, const std::string ¶ms) : MpegMuxer(false) { + HlsRecorder(const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) : MpegMuxer(false) { GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum); GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); - // _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + // _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + _option = option; _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); //清空上次的残余文件 _hls->clearCache(); } - ~HlsRecorder() = default; + ~HlsRecorder() { MpegMuxer::flush(); }; void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id) { _hls->setMediaSource(vhost, app, stream_id); @@ -46,10 +47,9 @@ public: int readerCount() { return _hls->getMediaSource()->readerCount(); } void onReaderChanged(MediaSource &sender, int size) override { - GET_CONFIG(bool, hls_demand, General::kHlsDemand); // hls保留切片个数为0时代表为hls录制(不删除切片),那么不管有无观看者都一直生成hls - _enabled = hls_demand ? (_hls->isLive() ? size : true) : true; - if (!size && _hls->isLive() && hls_demand) { + _enabled = _option.hls_demand ? (_hls->isLive() ? size : true) : true; + if (!size && _hls->isLive() && _option.hls_demand) { // hls直播时,如果无人观看就删除视频缓存,目的是为了防止视频跳跃 _clear_cache = true; } @@ -57,23 +57,21 @@ public: } bool inputFrame(const Frame::Ptr &frame) override { - GET_CONFIG(bool, hls_demand, General::kHlsDemand); - if (_clear_cache && hls_demand) { + if (_clear_cache && _option.hls_demand) { _clear_cache = false; //清空旧的m3u8索引文件于ts切片 _hls->clearCache(); _hls->getMediaSource()->setIndexFile(""); } - if (_enabled || !hls_demand) { + if (_enabled || !_option.hls_demand) { return MpegMuxer::inputFrame(frame); } return false; } bool isEnabled() { - GET_CONFIG(bool, hls_demand, General::kHlsDemand); //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 - return hls_demand ? (_clear_cache ? true : _enabled) : true; + return _option.hls_demand ? (_clear_cache ? true : _enabled) : true; } void startRecord(bool flag) { _hls->startRecord(flag); @@ -94,6 +92,7 @@ private: bool _enabled = true; bool _clear_cache = false; //std::shared_ptr _hls; + ProtocolOption _option; std::shared_ptr _hls; bool _isRecord = false; }; diff --git a/src/Record/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp index 0b4357a6..02957968 100644 --- a/src/Record/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -19,8 +19,6 @@ using namespace toolkit; namespace mediakit { -MP4Muxer::MP4Muxer() {} - MP4Muxer::~MP4Muxer() { closeMP4(); } @@ -61,6 +59,16 @@ bool MP4MuxerInterface::haveVideo() const { return _have_video; } +uint64_t MP4MuxerInterface::getDuration() const { + uint64_t ret = 0; + for (auto &pr : _codec_to_trackid) { + if (pr.second.stamp.getRelativeStamp() > (int64_t)ret) { + ret = pr.second.stamp.getRelativeStamp(); + } + } + return ret; +} + void MP4MuxerInterface::resetTracks() { _started = false; _have_video = false; @@ -69,6 +77,10 @@ void MP4MuxerInterface::resetTracks() { _codec_to_trackid.clear(); } +void MP4MuxerInterface::flush() { + _frame_merger.flush(); +} + bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) { auto it = _codec_to_trackid.find(frame->getCodecId()); if (it == _codec_to_trackid.end()) { @@ -88,12 +100,12 @@ bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) { //mp4文件时间戳需要从0开始 auto &track_info = it->second; - int64_t dts_out, pts_out; switch (frame->getCodecId()) { case CodecH264: case CodecH265: { //这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理, - _frame_merger.inputFrame(frame, [&](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { + _frame_merger.inputFrame(frame, [this, &track_info](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { + int64_t dts_out, pts_out; track_info.stamp.revise(dts, pts, dts_out, pts_out); mp4_writer_write(_mov_writter.get(), track_info.track_id, @@ -107,6 +119,7 @@ bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) { } default: { + int64_t dts_out, pts_out; track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out); mp4_writer_write(_mov_writter.get(), track_info.track_id, diff --git a/src/Record/MP4Muxer.h b/src/Record/MP4Muxer.h index 25fd0447..64947d82 100644 --- a/src/Record/MP4Muxer.h +++ b/src/Record/MP4Muxer.h @@ -43,6 +43,11 @@ public: */ void resetTracks() override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + /** * 是否包含视频 */ @@ -58,6 +63,11 @@ public: */ void initSegment(); + /** + * 获取mp4时长,单位毫秒 + */ + uint64_t getDuration() const; + protected: virtual MP4FileIO::Writer createWriter() = 0; @@ -73,14 +83,14 @@ private: Stamp stamp; }; std::unordered_map _codec_to_trackid; - FrameMerger _frame_merger{FrameMerger::mp4_nal_size}; + FrameMerger _frame_merger { FrameMerger::mp4_nal_size }; }; class MP4Muxer : public MP4MuxerInterface{ public: typedef std::shared_ptr Ptr; - MP4Muxer(); + MP4Muxer() = default; ~MP4Muxer() override; /** diff --git a/src/Record/MP4Reader.cpp b/src/Record/MP4Reader.cpp index f58299fa..fbc6d8d0 100644 --- a/src/Record/MP4Reader.cpp +++ b/src/Record/MP4Reader.cpp @@ -24,7 +24,7 @@ MP4Reader::MP4Reader(const string &vhost, const string &app, const string &strea _poller = WorkThreadPool::Instance().getPoller(); _file_path = file_path; if (_file_path.empty()) { - GET_CONFIG(string, recordPath, Record::kFilePath); + GET_CONFIG(string, recordPath, Protocol::kMP4SavePath); GET_CONFIG(bool, enableVhost, General::kEnableVhost); if (enableVhost) { _file_path = vhost + "/" + app + "/" + stream_id; @@ -111,9 +111,10 @@ void MP4Reader::startReadMP4(uint64_t sample_ms, bool ref_self, bool file_repeat GET_CONFIG(uint32_t, sampleMS, Record::kSampleMS); auto strong_self = shared_from_this(); if (_muxer) { - _muxer->setMediaListener(strong_self); //一直读到所有track就绪为止 - while (!_muxer->isAllTrackReady() && readNextSample()) {} + while (!_muxer->isAllTrackReady() && readNextSample()); + //注册后再切换OwnerPoller + _muxer->setMediaListener(strong_self); } auto timer_sec = (sample_ms ? sample_ms : sampleMS) / 1000.0f; @@ -230,12 +231,9 @@ bool MP4Reader::seekTo(uint32_t stamp_seek) { return false; } -bool MP4Reader::close(MediaSource &sender, bool force) { - if (!_muxer || (!force && _muxer->totalReaderCount())) { - return false; - } - _timer.reset(); - WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; +bool MP4Reader::close(MediaSource &sender) { + _timer = nullptr; + WarnL << "close media: " << sender.getUrl(); return true; } diff --git a/src/Record/MP4Reader.h b/src/Record/MP4Reader.h index 10a7ff35..b2cb87ed 100644 --- a/src/Record/MP4Reader.h +++ b/src/Record/MP4Reader.h @@ -55,7 +55,7 @@ private: bool pause(MediaSource &sender, bool pause) override; bool speed(MediaSource &sender, float speed) override; - bool close(MediaSource &sender,bool force) override; + bool close(MediaSource &sender) override; MediaOriginType getOriginType(MediaSource &sender) const override; std::string getOriginUrl(MediaSource &sender) const override; toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index af955e7a..a2fdfaeb 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). @@ -28,12 +28,13 @@ MP4Recorder::MP4Recorder(const string &path, const string &vhost, const string & _info.stream = stream_id; _info.vhost = vhost; _info.folder = path; - GET_CONFIG(size_t ,recordSec,Record::kFileSecond); - _max_second = max_second ? max_second : recordSec; + GET_CONFIG(uint32_t, s_max_second, Protocol::kMP4MaxSecond); + _max_second = max_second ? max_second : s_max_second; } MP4Recorder::~MP4Recorder() { - //closeFile(); + flush(); + closeFile(); } void MP4Recorder::createFile() { @@ -63,30 +64,28 @@ void MP4Recorder::createFile() { WarnL << ex.what(); } } + void MP4Recorder::asyncClose() { auto muxer = _muxer; auto full_path_tmp = _full_path_tmp; auto full_path = _full_path; auto info = _info; WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable { - //获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间 - info.time_len = (float) (::time(NULL) - info.start_time); - //关闭mp4非常耗时,所以要放在后台线程执行 + info.time_len = muxer->getDuration() / 1000.0f; + // 关闭mp4可能非常耗时,所以要放在后台线程执行 muxer->closeMP4(); - - if(!full_path_tmp.empty()) { - //获取文件大小 + if (!full_path_tmp.empty()) { + // 获取文件大小 info.file_size = File::fileSize(full_path_tmp.data()); if (info.file_size < 1024) { - //录像文件太小,删除之 + // 录像文件太小,删除之 File::delete_file(full_path_tmp.data()); return; } - //临时文件名改成正式文件名,防止mp4未完成时被访问 + // 临时文件名改成正式文件名,防止mp4未完成时被访问 rename(full_path_tmp.data(), full_path.data()); } - - /////record 业务逻辑////// + //触发mp4录制切片生成事件 NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info); }); } @@ -98,6 +97,12 @@ void MP4Recorder::closeFile() { } } +void MP4Recorder::flush() { + if (_muxer) { + _muxer->flush(); + } +} + bool MP4Recorder::inputFrame(const Frame::Ptr &frame) { if (!(_have_video && frame->getTrackType() == TrackAudio)) { //如果有视频且输入的是音频,那么应该忽略切片逻辑 diff --git a/src/Record/MP4Recorder.h b/src/Record/MP4Recorder.h index 9989d4e4..6ef35a8c 100644 --- a/src/Record/MP4Recorder.h +++ b/src/Record/MP4Recorder.h @@ -24,7 +24,7 @@ namespace mediakit { #ifdef ENABLE_MP4 -class MP4Recorder : public MediaSinkInterface { +class MP4Recorder final : public MediaSinkInterface { public: using Ptr = std::shared_ptr; @@ -41,6 +41,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + /** * 添加ready状态的track */ diff --git a/src/Record/MPEG.cpp b/src/Record/MPEG.cpp index 085e9f38..985cf98f 100644 --- a/src/Record/MPEG.cpp +++ b/src/Record/MPEG.cpp @@ -13,7 +13,7 @@ #if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY) -#include "mpeg-ts-proto.h" +#include "mpeg-ts.h" #include "mpeg-muxer.h" using namespace toolkit; @@ -63,7 +63,7 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) { case CodecH264: case CodecH265: { //这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理, - return _frame_merger.inputFrame(frame,[&](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { + return _frame_merger.inputFrame(frame,[this, track_id](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { _key_pos = have_idr; //取视频时间戳为TS的时间戳 _timestamp = dts; @@ -153,6 +153,10 @@ void MpegMuxer::releaseContext() { _frame_merger.clear(); } +void MpegMuxer::flush() { + _frame_merger.flush(); +} + }//mediakit #endif \ No newline at end of file diff --git a/src/Record/MPEG.h b/src/Record/MPEG.h index 5d4b05cd..9aabccdd 100644 --- a/src/Record/MPEG.h +++ b/src/Record/MPEG.h @@ -45,6 +45,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + protected: /** * 输出ts/ps数据回调 diff --git a/src/Record/Recorder.cpp b/src/Record/Recorder.cpp index 13bd256f..546780d8 100644 --- a/src/Record/Recorder.cpp +++ b/src/Record/Recorder.cpp @@ -23,7 +23,7 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s GET_CONFIG(bool, enableVhost, General::kEnableVhost); switch (type) { case Recorder::type_hls: { - GET_CONFIG(string, hlsPath, Hls::kFilePath); + GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath); string m3u8FilePath; if (enableVhost) { m3u8FilePath = vhost + "/" + app + "/" + stream_id + "/hls.m3u8"; @@ -37,7 +37,7 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s return File::absolutePath(m3u8FilePath, hlsPath); } case Recorder::type_mp4: { - GET_CONFIG(string, recordPath, Record::kFilePath); + GET_CONFIG(string, recordPath, Protocol::kMP4SavePath); GET_CONFIG(string, recordAppName, Record::kAppName); string mp4FilePath; if (enableVhost) { @@ -56,13 +56,13 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s } } -std::shared_ptr Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, size_t max_second){ - auto path = Recorder::getRecordPath(type, vhost, app, stream_id, customized_path); +std::shared_ptr Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option){ switch (type) { case Recorder::type_hls: { #if defined(ENABLE_HLS) + auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.hls_save_path); GET_CONFIG(bool, enable_vhost, General::kEnableVhost); - auto ret = std::make_shared(path, enable_vhost ? string(VHOST_KEY) + "=" + vhost : ""); + auto ret = std::make_shared(path, enable_vhost ? string(VHOST_KEY) + "=" + vhost : "", option); ret->setMediaSource(vhost, app, stream_id); return ret; #else @@ -73,7 +73,8 @@ std::shared_ptr Recorder::createRecorder(type type, const st case Recorder::type_mp4: { #if defined(ENABLE_MP4) - return std::make_shared(path, vhost, app, stream_id, max_second); + auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.mp4_save_path); + return std::make_shared(path, vhost, app, stream_id, option.mp4_max_second); #else throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试"); #endif diff --git a/src/Record/Recorder.h b/src/Record/Recorder.h index d3529dea..491088ff 100644 --- a/src/Record/Recorder.h +++ b/src/Record/Recorder.h @@ -10,11 +10,13 @@ #ifndef SRC_MEDIAFILE_RECORDER_H_ #define SRC_MEDIAFILE_RECORDER_H_ + #include #include namespace mediakit { class MediaSinkInterface; +class ProtocolOption; class RecordInfo { public: @@ -60,7 +62,7 @@ public: * @param max_second mp4录制最大切片时间,单位秒,置0则采用配置文件配置 * @return 对象指针,可能为nullptr */ - static std::shared_ptr createRecorder(type type, const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &customized_path = "", size_t max_second = 0); + static std::shared_ptr createRecorder(type type, const std::string &vhost, const std::string &app, const std::string &stream_id, const ProtocolOption &option); private: Recorder() = delete; diff --git a/src/Rtmp/RtmpCodec.h b/src/Rtmp/RtmpCodec.h index baa61084..476d0a39 100644 --- a/src/Rtmp/RtmpCodec.h +++ b/src/Rtmp/RtmpCodec.h @@ -56,8 +56,8 @@ protected: class RtmpCodec : public RtmpRing, public FrameDispatcher , public CodecInfo{ public: typedef std::shared_ptr Ptr; - RtmpCodec(){} - virtual ~RtmpCodec(){} + RtmpCodec() = default; + ~RtmpCodec() override = default; virtual void makeConfigPacket() {}; }; diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index 8ee954ca..5bd8ea10 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -60,7 +60,7 @@ public: MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) { } - ~RtmpMediaSource() override{} + ~RtmpMediaSource() override { flush(); } /** * 获取媒体源的环形缓冲 diff --git a/src/Rtmp/RtmpMediaSourceImp.h b/src/Rtmp/RtmpMediaSourceImp.h index 915b8fd1..90258198 100644 --- a/src/Rtmp/RtmpMediaSourceImp.h +++ b/src/Rtmp/RtmpMediaSourceImp.h @@ -26,9 +26,9 @@ namespace mediakit { -class RtmpMediaSourceImp: public RtmpMediaSource, private TrackListener, public MultiMediaSourceMuxer::Listener { +class RtmpMediaSourceImp final : public RtmpMediaSource, private TrackListener, public MultiMediaSourceMuxer::Listener { public: - typedef std::shared_ptr Ptr; + using Ptr = std::shared_ptr; /** * 构造函数 @@ -42,7 +42,7 @@ public: _demuxer->setTrackListener(this); } - ~RtmpMediaSourceImp() = default; + ~RtmpMediaSourceImp() override = default; /** * 设置metadata diff --git a/src/Rtmp/RtmpMediaSourceMuxer.h b/src/Rtmp/RtmpMediaSourceMuxer.h index 7134c71b..29a1e8d0 100644 --- a/src/Rtmp/RtmpMediaSourceMuxer.h +++ b/src/Rtmp/RtmpMediaSourceMuxer.h @@ -16,20 +16,22 @@ namespace mediakit { -class RtmpMediaSourceMuxer : public RtmpMuxer, public MediaSourceEventInterceptor, - public std::enable_shared_from_this { +class RtmpMediaSourceMuxer final : public RtmpMuxer, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { public: typedef std::shared_ptr Ptr; RtmpMediaSourceMuxer(const std::string &vhost, const std::string &strApp, const std::string &strId, - const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title){ + const ProtocolOption &option, + const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title) { + _option = option; _media_src = std::make_shared(vhost, strApp, strId); getRtmpRing()->setDelegate(_media_src); } - ~RtmpMediaSourceMuxer() override{} + ~RtmpMediaSourceMuxer() override { RtmpMuxer::flush(); } void setListener(const std::weak_ptr &listener){ setDelegate(listener); @@ -50,35 +52,33 @@ public: } void onReaderChanged(MediaSource &sender, int size) override { - GET_CONFIG(bool, rtmp_demand, General::kRtmpDemand); - _enabled = rtmp_demand ? size : true; - if (!size && rtmp_demand) { + _enabled = _option.rtmp_demand ? size : true; + if (!size && _option.rtmp_demand) { _clear_cache = true; } MediaSourceEventInterceptor::onReaderChanged(sender, size); } bool inputFrame(const Frame::Ptr &frame) override { - GET_CONFIG(bool, rtmp_demand, General::kRtmpDemand); - if (_clear_cache && rtmp_demand) { + if (_clear_cache && _option.rtmp_demand) { _clear_cache = false; _media_src->clearCache(); } - if (_enabled || !rtmp_demand) { + if (_enabled || !_option.rtmp_demand) { return RtmpMuxer::inputFrame(frame); } return false; } bool isEnabled() { - GET_CONFIG(bool, rtmp_demand, General::kRtmpDemand); //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 - return rtmp_demand ? (_clear_cache ? true : _enabled) : true; + return _option.rtmp_demand ? (_clear_cache ? true : _enabled) : true; } private: bool _enabled = true; bool _clear_cache = false; + ProtocolOption _option; RtmpMediaSource::Ptr _media_src; }; diff --git a/src/Rtmp/RtmpMuxer.cpp b/src/Rtmp/RtmpMuxer.cpp index 2dcec636..4be4ac5d 100644 --- a/src/Rtmp/RtmpMuxer.cpp +++ b/src/Rtmp/RtmpMuxer.cpp @@ -43,6 +43,14 @@ bool RtmpMuxer::inputFrame(const Frame::Ptr &frame) { return encoder ? encoder->inputFrame(frame) : false; } +void RtmpMuxer::flush() { + for (auto &encoder : _encoder) { + if (encoder) { + encoder->flush(); + } + } +} + void RtmpMuxer::makeConfigPacket(){ for(auto &encoder : _encoder){ if(encoder){ diff --git a/src/Rtmp/RtmpMuxer.h b/src/Rtmp/RtmpMuxer.h index 993d0aed..73cacfb2 100644 --- a/src/Rtmp/RtmpMuxer.h +++ b/src/Rtmp/RtmpMuxer.h @@ -18,7 +18,7 @@ namespace mediakit{ -class RtmpMuxer : public MediaSinkInterface{ +class RtmpMuxer : public MediaSinkInterface { public: typedef std::shared_ptr Ptr; @@ -26,7 +26,7 @@ public: * 构造函数 */ RtmpMuxer(const TitleMeta::Ptr &title); - virtual ~RtmpMuxer(){} + ~RtmpMuxer() override = default; /** * 获取完整的SDP字符串 @@ -51,6 +51,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + /** * 重置所有track */ diff --git a/src/Rtmp/RtmpPlayer.cpp b/src/Rtmp/RtmpPlayer.cpp index ceddaa01..e0185ba3 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -44,11 +44,16 @@ void RtmpPlayer::teardown() { _deque_on_status.clear(); } -void RtmpPlayer::play(const string &strUrl) { +void RtmpPlayer::play(const string &url) { teardown(); - string host_url = FindField(strUrl.data(), "://", "/"); - _app = FindField(strUrl.data(), (host_url + "/").data(), "/"); - _stream_id = FindField(strUrl.data(), (host_url + "/" + _app + "/").data(), NULL); + string host_url = FindField(url.data(), "://", "/"); + { + auto pos = url.find_last_of('/'); + if (pos != string::npos) { + _stream_id = url.substr(pos + 1); + } + } + _app = FindField(url.data(), (host_url + "/").data(), ("/" + _stream_id).data()); _tc_url = string("rtmp://") + host_url + "/" + _app; if (!_app.size() || !_stream_id.size()) { @@ -109,16 +114,16 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { //播放成功,恢复rtmp接收超时定时器 _rtmp_recv_ticker.resetTime(); auto timeout_ms = (*this)[Client::kMediaTimeoutMS].as(); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - auto lam = [weakSelf, timeout_ms]() { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { + weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + auto lam = [weak_self, timeout_ms]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { return false; } - if (strongSelf->_rtmp_recv_ticker.elapsedTime() > timeout_ms) { + if (strong_self->_rtmp_recv_ticker.elapsedTime() > timeout_ms) { //接收rtmp媒体数据超时 SockException ex(Err_timeout, "receive rtmp timeout"); - strongSelf->onPlayResult_l(ex, true); + strong_self->onPlayResult_l(ex, true); return false; } return true; @@ -130,19 +135,17 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { } } -void RtmpPlayer::onConnect(const SockException &err){ +void RtmpPlayer::onConnect(const SockException &err) { if (err.getErrCode() != Err_success) { onPlayResult_l(err, false); return; } - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - startClientSession([weakSelf]() { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { - return; + weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + startClientSession([weak_self]() { + if (auto strong_self = weak_self.lock()) { + strong_self->send_connect(); } - strongSelf->send_connect(); - }); + },_app.find("vod") != 0); // 实测发现vod点播时,使用复杂握手fms无响应:issue #2007 } void RtmpPlayer::onRecv(const Buffer::Ptr &buf){ @@ -249,14 +252,14 @@ inline void RtmpPlayer::send_pause(bool pause) { _beat_timer.reset(); if (pause) { - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - _beat_timer.reset(new Timer((*this)[Client::kBeatIntervalMS].as() / 1000.0f, [weakSelf]() { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { + weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + _beat_timer.reset(new Timer((*this)[Client::kBeatIntervalMS].as() / 1000.0f, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { return false; } uint32_t timeStamp = (uint32_t)::time(NULL); - strongSelf->sendUserControl(CONTROL_PING_REQUEST, timeStamp); + strong_self->sendUserControl(CONTROL_PING_REQUEST, timeStamp); return true; }, getPoller())); } diff --git a/src/Rtmp/RtmpProtocol.cpp b/src/Rtmp/RtmpProtocol.cpp index ee391b61..dc536c03 100644 --- a/src/Rtmp/RtmpProtocol.cpp +++ b/src/Rtmp/RtmpProtocol.cpp @@ -288,12 +288,14 @@ const char *RtmpProtocol::onSearchPacketTail(const char *data,size_t len){ } ////for client//// -void RtmpProtocol::startClientSession(const function &func) { +void RtmpProtocol::startClientSession(const function &func, bool complex) { //发送 C0C1 char handshake_head = HANDSHAKE_PLAINTEXT; onSendRawData(obtainBuffer(&handshake_head, 1)); RtmpHandshake c1(0); - c1.create_complex_c0c1(); + if (complex) { + c1.create_complex_c0c1(); + } onSendRawData(obtainBuffer((char *) (&c1), sizeof(c1))); _next_step_func = [this, func](const char *data, size_t len) { //等待 S0+S1+S2 @@ -754,7 +756,8 @@ void RtmpProtocol::handle_chunk(RtmpPacket::Ptr packet) { case MSG_WIN_SIZE: { //如果窗口太小,会导致发送sendAcknowledgement时无限递归:https://github.com/ZLMediaKit/ZLMediaKit/issues/1839 - _windows_size = max(load_be32(&chunk_data.buffer[0]), 32 * 1024U); + //窗口太大,也可能导致fms服务器认为播放器心跳超时 + _windows_size = min(max(load_be32(&chunk_data.buffer[0]), 32 * 1024U), 1280 * 1024U); TraceL << "MSG_WIN_SIZE:" << _windows_size; break; } @@ -806,7 +809,15 @@ void RtmpProtocol::handle_chunk(RtmpPacket::Ptr packet) { break; } - default: onRtmpChunk(std::move(packet)); break; + default: { + _bytes_recv += packet->size(); + if (_windows_size > 0 && _bytes_recv - _bytes_recv_last >= _windows_size) { + _bytes_recv_last = _bytes_recv; + sendAcknowledgement(_bytes_recv); + } + onRtmpChunk(std::move(packet)); + break; + } } } diff --git a/src/Rtmp/RtmpProtocol.h b/src/Rtmp/RtmpProtocol.h index ccd0e4c4..ee05cd33 100644 --- a/src/Rtmp/RtmpProtocol.h +++ b/src/Rtmp/RtmpProtocol.h @@ -32,7 +32,7 @@ public: void onParseRtmp(const char *data, size_t size); //作为客户端发送c0c1,等待s0s1s2并且回调 - void startClientSession(const std::function &cb); + void startClientSession(const std::function &cb, bool complex = true); protected: virtual void onSendRawData(toolkit::Buffer::Ptr buffer) = 0; @@ -84,18 +84,20 @@ private: protected: int _send_req_id = 0; + int _now_stream_index = 0; uint32_t _stream_index = STREAM_CONTROL; private: - int _now_stream_index = 0; - int _now_chunk_id = 0; bool _data_started = false; + int _now_chunk_id = 0; ////////////ChunkSize//////////// size_t _chunk_size_in = DEFAULT_CHUNK_LEN; size_t _chunk_size_out = DEFAULT_CHUNK_LEN; ////////////Acknowledgement//////////// - uint32_t _bytes_sent = 0; - uint32_t _bytes_sent_last = 0; + uint64_t _bytes_sent = 0; + uint64_t _bytes_sent_last = 0; + uint64_t _bytes_recv = 0; + uint64_t _bytes_recv_last = 0; uint32_t _windows_size = 0; ///////////PeerBandwidth/////////// uint32_t _bandwidth = 2500000; diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index aa9d5e9c..9d8acf51 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -17,7 +17,7 @@ using namespace toolkit; namespace mediakit { -RtmpSession::RtmpSession(const Socket::Ptr &sock) : TcpSession(sock) { +RtmpSession::RtmpSession(const Socket::Ptr &sock) : Session(sock) { DebugP(this); GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond); sock->setSendTimeOutSecond(keep_alive_sec); @@ -31,9 +31,7 @@ void RtmpSession::onError(const SockException& err) { bool is_player = !_push_src_ownership; uint64_t duration = _ticker.createdTime() / 1000; WarnP(this) << (is_player ? "RTMP播放器(" : "RTMP推流器(") - << _media_info._vhost << "/" - << _media_info._app << "/" - << _media_info._streamid + << _media_info.shortUrl() << ")断开:" << err.what() << ",耗时(s):" << duration; @@ -124,9 +122,9 @@ void RtmpSession::onCmd_createStream(AMFDecoder &dec) { void RtmpSession::onCmd_publish(AMFDecoder &dec) { std::shared_ptr ticker(new Ticker); weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); - std::shared_ptr pToken(new onceToken(nullptr,[ticker,weak_self](){ + std::shared_ptr token(new onceToken(nullptr, [ticker, weak_self]() { auto strong_self = weak_self.lock(); - if(strong_self){ + if (strong_self) { DebugP(strong_self.get()) << "publish 回复时间:" << ticker->elapsedTime() << "ms"; } })); @@ -134,7 +132,9 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { _media_info.parse(_tc_url + "/" + getStreamId(dec.load())); _media_info._schema = RTMP_SCHEMA; - auto on_res = [this, pToken](const string &err, const ProtocolOption &option) { + auto now_stream_index = _now_stream_index; + auto on_res = [this, token, now_stream_index](const string &err, const ProtocolOption &option) { + _now_stream_index = now_stream_index; if (!err.empty()) { sendStatus({ "level", "error", "code", "NetStream.Publish.BadAuth", @@ -198,12 +198,12 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { return; } - Broadcast::PublishAuthInvoker invoker = [weak_self, on_res, pToken](const string &err, const ProtocolOption &option) { + Broadcast::PublishAuthInvoker invoker = [weak_self, on_res, token](const string &err, const ProtocolOption &option) { auto strong_self = weak_self.lock(); if (!strong_self) { return; } - strong_self->async([weak_self, on_res, err, pToken, option]() { + strong_self->async([weak_self, on_res, err, token, option]() { auto strong_self = weak_self.lock(); if (!strong_self) { return; @@ -256,10 +256,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr "clientid", "0" }); if (!ok) { - string err_msg = StrPrinter << (auth_success ? "no such stream:" : err.data()) << " " - << _media_info._vhost << " " - << _media_info._app << " " - << _media_info._streamid; + string err_msg = StrPrinter << (auth_success ? "no such stream:" : err.data()) << " " << _media_info.shortUrl(); shutdown(SockException(Err_shutdown, err_msg)); return; } @@ -366,24 +363,26 @@ void RtmpSession::doPlay(AMFDecoder &dec){ DebugP(strong_self.get()) << "play 回复时间:" << ticker->elapsedTime() << "ms"; } })); - Broadcast::AuthInvoker invoker = [weak_self,token](const string &err){ + auto now_stream_index = _now_stream_index; + Broadcast::AuthInvoker invoker = [weak_self, token, now_stream_index](const string &err) { auto strong_self = weak_self.lock(); if (!strong_self) { return; } - strong_self->async([weak_self, err, token]() { + strong_self->async([weak_self, err, token, now_stream_index]() { auto strong_self = weak_self.lock(); if (!strong_self) { return; } + strong_self->_now_stream_index = now_stream_index; strong_self->doPlayResponse(err, [token](bool) {}); }); }; auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); - if(!flag){ - //该事件无人监听,默认不鉴权 - doPlayResponse("",[token](bool){}); + if (!flag) { + // 该事件无人监听,默认不鉴权 + doPlayResponse("", [token](bool) {}); } } @@ -535,12 +534,6 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { WarnL << "Not a rtmp push!"; return; } - GET_CONFIG(bool, rtmp_modify_stamp, Rtmp::kModifyStamp); - if (rtmp_modify_stamp) { - int64_t dts_out; - _stamp[chunk_data.type_id % 2].revise(chunk_data.time_stamp, chunk_data.time_stamp, dts_out, dts_out, true); - chunk_data.time_stamp = (uint32_t)dts_out; - } if (!_set_meta_data) { _set_meta_data = true; @@ -574,13 +567,10 @@ void RtmpSession::onSendMedia(const RtmpPacket::Ptr &pkt) { sendRtmp(pkt->type_id, pkt->stream_index, pkt, pkt->time_stamp, pkt->chunk_id); } -bool RtmpSession::close(MediaSource &sender,bool force) { +bool RtmpSession::close(MediaSource &sender) { //此回调在其他线程触发 - if(!_push_src || (!force && _push_src->totalReaderCount())){ - return false; - } - string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; - safeShutdown(SockException(Err_shutdown,err)); + string err = StrPrinter << "close media: " << sender.getUrl(); + safeShutdown(SockException(Err_shutdown, err)); return true; } @@ -600,6 +590,10 @@ std::shared_ptr RtmpSession::getOriginSock(MediaSource &sender) const return const_cast(this)->shared_from_this(); } +toolkit::EventPoller::Ptr RtmpSession::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + void RtmpSession::setSocketFlags(){ GET_CONFIG(int, merge_write_ms, General::kMergeWriteMS); if (merge_write_ms > 0) { @@ -619,6 +613,6 @@ void RtmpSession::dumpMetadata(const AMFValue &metadata) { metadata.object_for_each([&](const string &key, const AMFValue &val) { printer << "\r\n" << key << "\t:" << val.to_string(); }); - InfoL << _media_info._vhost << " " << _media_info._app << " " << _media_info._streamid << (string) printer; + InfoL << _media_info.shortUrl() << (string) printer; } } /* namespace mediakit */ diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index 7bce876c..d124d858 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -20,12 +20,12 @@ #include "RtmpMediaSourceImp.h" #include "Util/util.h" #include "Util/TimeTicker.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" #include "Common/Stamp.h" namespace mediakit { -class RtmpSession : public toolkit::TcpSession, public RtmpProtocol, public MediaSourceEvent { +class RtmpSession : public toolkit::Session, public RtmpProtocol, public MediaSourceEvent { public: using Ptr = std::shared_ptr; @@ -71,7 +71,7 @@ private: ///////MediaSourceEvent override/////// // 关闭 - bool close(MediaSource &sender, bool force) override; + bool close(MediaSource &sender) override; // 播放总人数 int totalReaderCount(MediaSource &sender) override; // 获取媒体源类型 @@ -80,6 +80,8 @@ private: std::string getOriginUrl(MediaSource &sender) const override; // 获取媒体源客户端相关信息 std::shared_ptr getOriginSock(MediaSource &sender) const override; + // 由于支持断连续推,存在OwnerPoller变更的可能 + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; void setSocketFlags(); std::string getStreamId(const std::string &str); @@ -94,8 +96,6 @@ private: //消耗的总流量 uint64_t _total_bytes = 0; std::string _tc_url; - //推流时间戳修整器 - Stamp _stamp[2]; //数据接收超时计时器 toolkit::Ticker _ticker; MediaInfo _media_info; @@ -109,7 +109,7 @@ private: /** * 支持ssl加密的rtmp服务器 */ -using RtmpSessionWithSSL = toolkit::TcpSessionWithSSL; +using RtmpSessionWithSSL = toolkit::SessionWithSSL; } /* namespace mediakit */ #endif /* SRC_RTMP_RTMPSESSION_H_ */ diff --git a/src/Rtp/Decoder.cpp b/src/Rtp/Decoder.cpp index b9827e98..37a347ac 100644 --- a/src/Rtp/Decoder.cpp +++ b/src/Rtp/Decoder.cpp @@ -18,7 +18,7 @@ #include "Extension/Opus.h" #if defined(ENABLE_RTPPROXY) || defined(ENABLE_HLS) -#include "mpeg-ts-proto.h" +#include "mpeg-ts.h" #endif using namespace toolkit; @@ -65,6 +65,10 @@ DecoderImp::Ptr DecoderImp::createDecoder(Type type, MediaSinkInterface *sink){ return DecoderImp::Ptr(new DecoderImp(decoder, sink)); } +void DecoderImp::flush() { + _merger.flush(); +} + ssize_t DecoderImp::input(const uint8_t *data, size_t bytes){ return _decoder->input(data, bytes); } @@ -219,10 +223,7 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d default: // 海康的 PS 流中会有 codecid 为 0xBD 的包 if (codecid != 0 && codecid != 0xBD) { - if (_last_unsported_print.elapsedTime() / 1000 > 5) { - _last_unsported_print.resetTime(); - WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int) codecid; - } + WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int) codecid; } break; } diff --git a/src/Rtp/Decoder.h b/src/Rtp/Decoder.h index 44fecfee..a51e4494 100644 --- a/src/Rtp/Decoder.h +++ b/src/Rtp/Decoder.h @@ -39,16 +39,14 @@ protected: class DecoderImp{ public: - typedef enum { - decoder_ts = 0, - decoder_ps - }Type; + typedef enum { decoder_ts = 0, decoder_ps } Type; typedef std::shared_ptr Ptr; ~DecoderImp() = default; static Ptr createDecoder(Type type, MediaSinkInterface *sink); ssize_t input(const uint8_t *data, size_t bytes); + void flush(); protected: void onTrack(const Track::Ptr &track); @@ -63,7 +61,6 @@ private: Decoder::Ptr _decoder; MediaSinkInterface *_sink; FrameMerger _merger{FrameMerger::none}; - toolkit::Ticker _last_unsported_print; Track::Ptr _tracks[TrackMax]; }; diff --git a/src/Rtp/GB28181Process.cpp b/src/Rtp/GB28181Process.cpp index e5afcdbb..485fed6a 100644 --- a/src/Rtp/GB28181Process.cpp +++ b/src/Rtp/GB28181Process.cpp @@ -59,12 +59,16 @@ GB28181Process::GB28181Process(const MediaInfo &media_info, MediaSinkInterface * _interface = sink; } -GB28181Process::~GB28181Process() {} - void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp) { _rtp_decoder[rtp->getHeader()->pt]->inputRtp(rtp, false); } +void GB28181Process::flush() { + if (_decoder) { + _decoder->flush(); + } +} + bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) { GET_CONFIG(uint32_t, h264_pt, RtpProxy::kH264PT); GET_CONFIG(uint32_t, h265_pt, RtpProxy::kH265PT); @@ -132,10 +136,10 @@ bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) { } } // 设置frame回调 - _rtp_decoder[pt]->addDelegate(std::make_shared([this](const Frame::Ptr &frame) { + _rtp_decoder[pt]->addDelegate([this](const Frame::Ptr &frame) { onRtpDecode(frame); return true; - })); + }); } return ref->inputRtp(TrackVideo, (unsigned char *)data, data_len); diff --git a/src/Rtp/GB28181Process.h b/src/Rtp/GB28181Process.h index 3ad078e6..4afba479 100644 --- a/src/Rtp/GB28181Process.h +++ b/src/Rtp/GB28181Process.h @@ -26,7 +26,7 @@ class GB28181Process : public ProcessInterface { public: typedef std::shared_ptr Ptr; GB28181Process(const MediaInfo &media_info, MediaSinkInterface *sink); - ~GB28181Process() override; + ~GB28181Process() override = default; /** * 输入rtp @@ -36,6 +36,11 @@ public: */ bool inputRtp(bool, const char *data, size_t data_len) override; + /** + * 刷新输出所有缓存 + */ + void flush() override; + protected: void onRtpSorted(RtpPacket::Ptr rtp); diff --git a/src/Rtp/ProcessInterface.h b/src/Rtp/ProcessInterface.h index ee5c0f53..6cd7fa9c 100644 --- a/src/Rtp/ProcessInterface.h +++ b/src/Rtp/ProcessInterface.h @@ -31,6 +31,11 @@ public: * @return 是否解析成功 */ virtual bool inputRtp(bool is_udp, const char *data, size_t data_len) = 0; + + /** + * 刷新输出所有缓存 + */ + virtual void flush() {} }; }//namespace mediakit diff --git a/src/Rtp/RtpCache.cpp b/src/Rtp/RtpCache.cpp index 89b05d4a..7a897847 100644 --- a/src/Rtp/RtpCache.cpp +++ b/src/Rtp/RtpCache.cpp @@ -19,37 +19,49 @@ namespace mediakit{ RtpCache::RtpCache(onFlushed cb) { _cb = std::move(cb); } + bool RtpCache::firstKeyReady(bool in) { - if(_first_key){ + if (_first_key) { return _first_key; } _first_key = in; return _first_key; } -void RtpCache::onFlush(std::shared_ptr > rtp_list, bool) { + +void RtpCache::onFlush(std::shared_ptr> rtp_list, bool) { _cb(std::move(rtp_list)); } -void RtpCache::input(uint64_t stamp, Buffer::Ptr buffer,bool is_key ) { +void RtpCache::input(uint64_t stamp, Buffer::Ptr buffer, bool is_key) { inputPacket(stamp, true, std::move(buffer), is_key); } -void RtpCachePS::onRTP(Buffer::Ptr buffer,bool is_key) { - if(!firstKeyReady(is_key)){ - return; - } - auto rtp = std::static_pointer_cast(buffer); - auto stamp = rtp->getStampMS(); - input(stamp, std::move(buffer),is_key); +void RtpCachePS::flush() { + PSEncoderImp::flush(); + RtpCache::flush(); } -void RtpCacheRaw::onRTP(Buffer::Ptr buffer,bool is_key) { - if(!firstKeyReady(is_key)){ +void RtpCachePS::onRTP(Buffer::Ptr buffer, bool is_key) { + if (!firstKeyReady(is_key)) { return; } auto rtp = std::static_pointer_cast(buffer); auto stamp = rtp->getStampMS(); - input(stamp, std::move(buffer),is_key); + input(stamp, std::move(buffer), is_key); +} + +void RtpCacheRaw::flush() { + RawEncoderImp::flush(); + RtpCache::flush(); +} + +void RtpCacheRaw::onRTP(Buffer::Ptr buffer, bool is_key) { + if (!firstKeyReady(is_key)) { + return; + } + auto rtp = std::static_pointer_cast(buffer); + auto stamp = rtp->getStampMS(); + input(stamp, std::move(buffer), is_key); } }//namespace mediakit diff --git a/src/Rtp/RtpCache.h b/src/Rtp/RtpCache.h index b83c8282..1121f384 100644 --- a/src/Rtp/RtpCache.h +++ b/src/Rtp/RtpCache.h @@ -19,7 +19,7 @@ namespace mediakit{ -class RtpCache : private PacketCache { +class RtpCache : protected PacketCache { public: using onFlushed = std::function >)>; RtpCache(onFlushed cb); @@ -33,30 +33,33 @@ protected: void input(uint64_t stamp, toolkit::Buffer::Ptr buffer,bool is_key = false); bool firstKeyReady(bool in); + protected: void onFlush(std::shared_ptr > rtp_list, bool) override; private: - onFlushed _cb; bool _first_key = false; + onFlushed _cb; }; -class RtpCachePS : public RtpCache, public PSEncoderImp{ +class RtpCachePS : public RtpCache, public PSEncoderImp { public: RtpCachePS(onFlushed cb, uint32_t ssrc, uint8_t payload_type = 96) : RtpCache(std::move(cb)), PSEncoderImp(ssrc, payload_type) {}; ~RtpCachePS() override = default; + void flush() override; protected: - void onRTP(toolkit::Buffer::Ptr rtp,bool is_key = false) override; + void onRTP(toolkit::Buffer::Ptr rtp, bool is_key = false) override; }; -class RtpCacheRaw : public RtpCache, public RawEncoderImp{ +class RtpCacheRaw : public RtpCache, public RawEncoderImp { public: - RtpCacheRaw(onFlushed cb, uint32_t ssrc, uint8_t payload_type = 96, bool sendAudio = true) : RtpCache(std::move(cb)), RawEncoderImp(ssrc, payload_type,sendAudio) {}; + RtpCacheRaw(onFlushed cb, uint32_t ssrc, uint8_t payload_type = 96, bool send_audio = true) : RtpCache(std::move(cb)), RawEncoderImp(ssrc, payload_type, send_audio) {}; ~RtpCacheRaw() override = default; + void flush() override; protected: - void onRTP(toolkit::Buffer::Ptr rtp,bool is_key = false) override; + void onRTP(toolkit::Buffer::Ptr rtp, bool is_key = false) override; }; }//namespace mediakit diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp index 0e1822f8..4f1f0ce5 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -49,12 +49,16 @@ RtpProcess::RtpProcess(const string &stream_id) { } } +void RtpProcess::flush() { + if (_process) { + _process->flush(); + } +} + RtpProcess::~RtpProcess() { uint64_t duration = (_last_frame_time.createdTime() - _last_frame_time.elapsedTime()) / 1000; WarnP(this) << "RTP推流器(" - << _media_info._vhost << "/" - << _media_info._app << "/" - << _media_info._streamid + << _media_info.shortUrl() << ")断开,耗时(s):" << duration; //流量统计事件广播 @@ -65,23 +69,14 @@ RtpProcess::~RtpProcess() { } bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data, size_t len, const struct sockaddr *addr, uint64_t *dts_out) { - auto is_busy = _busy_flag.test_and_set(); - if (is_busy) { - //其他线程正在执行本函数 - WarnP(this) << "其他线程正在执行本函数"; - return false; - } - //没有其他线程执行本函数 - onceToken token(nullptr, [&]() { - //本函数执行完毕时,释放状态 - _busy_flag.clear(); - }); - - if (!_sock) { - //第一次运行本函数 + if (_sock != sock) { + // 第一次运行本函数 + bool first = !_sock; _sock = sock; _addr.reset(new sockaddr_storage(*((sockaddr_storage *)addr))); - emitOnPublish(); + if (first) { + emitOnPublish(); + } } _total_bytes += len; @@ -196,8 +191,8 @@ void RtpProcess::onDetach() { } } -void RtpProcess::setOnDetach(const function &cb) { - _on_detach = cb; +void RtpProcess::setOnDetach(function cb) { + _on_detach = std::move(cb); } string RtpProcess::get_peer_ip() { @@ -239,7 +234,7 @@ void RtpProcess::emitOnPublish() { if (!strong_self) { return; } - auto poller = strong_self->_sock ? strong_self->_sock->getPoller() : EventPollerPool::Instance().getPoller(); + auto poller = strong_self->getOwnerPoller(MediaSource::NullMediaSource()); poller->async([weak_self, err, option]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -272,7 +267,7 @@ MediaOriginType RtpProcess::getOriginType(MediaSource &sender) const{ } string RtpProcess::getOriginUrl(MediaSource &sender) const { - return _media_info._schema + "://" + _media_info._vhost + "/" + _media_info._app + "/" + _media_info._streamid; + return _media_info.getUrl(); } std::shared_ptr RtpProcess::getOriginSock(MediaSource &sender) const { @@ -280,7 +275,10 @@ std::shared_ptr RtpProcess::getOriginSock(MediaSource &sender) const { } toolkit::EventPoller::Ptr RtpProcess::getOwnerPoller(MediaSource &sender) { - return _sock ? _sock->getPoller() : EventPollerPool::Instance().getPoller(); + if (_sock) { + return _sock->getPoller(); + } + throw std::runtime_error("RtpProcess::getOwnerPoller failed:" + _media_info._streamid); } float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { diff --git a/src/Rtp/RtpProcess.h b/src/Rtp/RtpProcess.h index 2c27c143..afa3e06f 100644 --- a/src/Rtp/RtpProcess.h +++ b/src/Rtp/RtpProcess.h @@ -18,7 +18,7 @@ namespace mediakit { -class RtpProcess : public RtcpContextForRecv, public toolkit::SockInfo, public MediaSinkInterface, public MediaSourceEventInterceptor, public std::enable_shared_from_this{ +class RtpProcess final : public RtcpContextForRecv, public toolkit::SockInfo, public MediaSinkInterface, public MediaSourceEventInterceptor, public std::enable_shared_from_this{ public: typedef std::shared_ptr Ptr; friend class RtpProcessHelper; @@ -50,13 +50,18 @@ public: /** * 设置onDetach事件回调 */ - void setOnDetach(const std::function &cb); + void setOnDetach(std::function cb); /** * 设置onDetach事件回调,false检查RTP超时,true停止 */ void setStopCheckRtp(bool is_check=false); + /** + * flush输出缓存 + */ + void flush() override; + /// SockInfo override std::string get_local_ip() override; uint16_t get_local_port() override; @@ -94,7 +99,6 @@ private: ProcessInterface::Ptr _process; MultiMediaSourceMuxer::Ptr _muxer; std::atomic_bool _stop_rtp_check{false}; - std::atomic_flag _busy_flag{false}; toolkit::Ticker _last_check_alive; std::recursive_mutex _func_mtx; std::deque > _cached_func; diff --git a/src/Rtp/RtpSelector.cpp b/src/Rtp/RtpSelector.cpp index 507ec2fc..8ac620ac 100644 --- a/src/Rtp/RtpSelector.cpp +++ b/src/Rtp/RtpSelector.cpp @@ -25,25 +25,6 @@ void RtpSelector::clear(){ _map_rtp_process.clear(); } -bool RtpSelector::inputRtp(const Socket::Ptr &sock, const char *data, size_t data_len, const struct sockaddr *addr, - uint64_t *dts_out) { - uint32_t ssrc = 0; - if (!getSSRC(data, data_len, ssrc)) { - WarnL << "get ssrc from rtp failed:" << data_len; - return false; - } - auto process = getProcess(printSSRC(ssrc), true); - if (process) { - try { - return process->inputRtp(true, sock, data, data_len, addr, dts_out); - } catch (...) { - delProcess(printSSRC(ssrc), process.get()); - throw; - } - } - return false; -} - bool RtpSelector::getSSRC(const char *data, size_t data_len, uint32_t &ssrc){ if (data_len < 12) { return false; @@ -59,6 +40,10 @@ RtpProcess::Ptr RtpSelector::getProcess(const string &stream_id,bool makeNew) { if (it == _map_rtp_process.end() && !makeNew) { return nullptr; } + if (it != _map_rtp_process.end() && makeNew) { + //已经被其他线程持有了,不得再被持有,否则会存在线程安全的问题 + throw std::runtime_error(StrPrinter << "RtpProcess(" << stream_id << ") already existed"); + } RtpProcessHelper::Ptr &ref = _map_rtp_process[stream_id]; if (!ref) { ref = std::make_shared(stream_id, shared_from_this()); @@ -127,6 +112,13 @@ RtpProcessHelper::RtpProcessHelper(const string &stream_id, const weak_ptrgetOwnerPoller(MediaSource::NullMediaSource())->async([process]() { process->flush(); }); + } catch (...) { + // 忽略getOwnerPoller可能抛出的异常 + } } void RtpProcessHelper::attachEvent() { @@ -134,17 +126,14 @@ void RtpProcessHelper::attachEvent() { _process->setDelegate(shared_from_this()); } -bool RtpProcessHelper::close(MediaSource &sender, bool force) { +bool RtpProcessHelper::close(MediaSource &sender) { //此回调在其他线程触发 - if (!_process || (!force && _process->totalReaderCount(sender))) { - return false; - } auto parent = _parent.lock(); if (!parent) { return false; } parent->delProcess(_stream_id, _process.get()); - WarnL << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + WarnL << "close media: " << sender.getUrl(); return true; } diff --git a/src/Rtp/RtpSelector.h b/src/Rtp/RtpSelector.h index ae796ac6..96888598 100644 --- a/src/Rtp/RtpSelector.h +++ b/src/Rtp/RtpSelector.h @@ -31,7 +31,7 @@ public: protected: // 通知其停止推流 - bool close(MediaSource &sender,bool force) override; + bool close(MediaSource &sender) override; private: std::string _stream_id; @@ -52,22 +52,10 @@ public: */ void clear(); - /** - * 输入多个rtp流,根据ssrc分流 - * @param sock 本地socket - * @param data 收到的数据 - * @param data_len 收到的数据长度 - * @param addr rtp流源地址 - * @param dts_out 解析出最新的dts - * @return 是否成功 - */ - bool inputRtp(const toolkit::Socket::Ptr &sock, const char *data, size_t data_len, - const struct sockaddr *addr, uint64_t *dts_out = nullptr); - /** * 获取一个rtp处理器 * @param stream_id 流id - * @param makeNew 不存在时是否新建 + * @param makeNew 不存在时是否新建, 该参数为true时,必须确保之前未创建同名对象 * @return rtp处理器 */ RtpProcess::Ptr getProcess(const std::string &stream_id, bool makeNew); diff --git a/src/Rtp/RtpSender.cpp b/src/Rtp/RtpSender.cpp index 98a70b4e..ea7910ba 100644 --- a/src/Rtp/RtpSender.cpp +++ b/src/Rtp/RtpSender.cpp @@ -25,6 +25,10 @@ RtpSender::RtpSender(EventPoller::Ptr poller) { _socket_rtp = Socket::createSocket(_poller, false); } +RtpSender::~RtpSender() { + flush(); +} + void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const function &cb){ _args = args; if (!_interface) { @@ -41,6 +45,8 @@ void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const funct if (args.passive) { // tcp被动发流模式 _args.is_udp = false; + // 默认等待链接 + bool is_wait = true; try { auto tcp_listener = Socket::createSocket(_poller, false); if (args.src_port) { @@ -49,18 +55,23 @@ void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const funct throw std::invalid_argument(StrPrinter << "open tcp passive server failed on port:" << args.src_port << ", err:" << get_uv_errmsg(true)); } + is_wait = true; } else { auto pr = std::make_pair(tcp_listener, Socket::createSocket(_poller, false)); //从端口池获取随机端口 makeSockPair(pr, "::", false, false); + // 随机端口不等待,保证调用者可以知道端口 + is_wait = false; } // tcp服务器默认开启5秒 - auto delay_task = _poller->doDelayTask(_args.tcp_passive_close_delay_ms, [tcp_listener, cb]() mutable { - cb(0, SockException(Err_timeout, "wait tcp connection timeout")); + auto delay_task = _poller->doDelayTask(_args.tcp_passive_close_delay_ms, [tcp_listener, cb,is_wait]() mutable { + if (is_wait) { + cb(0, SockException(Err_timeout, "wait tcp connection timeout")); + } tcp_listener = nullptr; return 0; }); - tcp_listener->setOnAccept([weak_self, cb, delay_task](Socket::Ptr &sock, std::shared_ptr &complete) { + tcp_listener->setOnAccept([weak_self, cb, delay_task,is_wait](Socket::Ptr &sock, std::shared_ptr &complete) { auto strong_self = weak_self.lock(); if (!strong_self) { return; @@ -69,10 +80,16 @@ void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const funct delay_task->cancel(); strong_self->_socket_rtp = sock; strong_self->onConnect(); - cb(sock->get_local_port(), SockException()); + if (is_wait) { + cb(sock->get_local_port(), SockException()); + } InfoL << "accept connection from:" << sock->get_peer_ip() << ":" << sock->get_peer_port(); }); InfoL << "start tcp passive server on:" << tcp_listener->get_local_port(); + if (!is_wait) { + // 随机端口马上返回端口,保证调用者知道端口 + cb(tcp_listener->get_local_port(), SockException()); + } } catch (std::exception &ex) { cb(0, SockException(Err_other, ex.what())); return; @@ -218,6 +235,12 @@ void RtpSender::resetTracks(){ _interface->resetTracks(); } +void RtpSender::flush() { + if (_interface) { + _interface->flush(); + } +} + //此函数在其他线程执行 bool RtpSender::inputFrame(const Frame::Ptr &frame) { //连接成功后才做实质操作(节省cpu资源) diff --git a/src/Rtp/RtpSender.h b/src/Rtp/RtpSender.h index 9bafd537..61bdfc3c 100644 --- a/src/Rtp/RtpSender.h +++ b/src/Rtp/RtpSender.h @@ -18,12 +18,12 @@ namespace mediakit{ //rtp发送客户端,支持发送GB28181协议 -class RtpSender : public MediaSinkInterface, public std::enable_shared_from_this{ +class RtpSender final : public MediaSinkInterface, public std::enable_shared_from_this{ public: typedef std::shared_ptr Ptr; RtpSender(toolkit::EventPoller::Ptr poller = nullptr); - ~RtpSender() override = default; + ~RtpSender() override; /** * 开始发送ps-rtp包 @@ -37,6 +37,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出frame缓存 + */ + void flush() override; + /** * 添加track,内部会调用Track的clone方法 * 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系 diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index 8c596357..ccf8590b 100644 --- a/src/Rtp/RtpServer.cpp +++ b/src/Rtp/RtpServer.cpp @@ -19,11 +19,9 @@ using namespace toolkit; namespace mediakit{ -RtpServer::RtpServer() {} - RtpServer::~RtpServer() { - if(_on_clearup){ - _on_clearup(); + if (_on_cleanup) { + _on_cleanup(); } } @@ -31,27 +29,56 @@ class RtcpHelper: public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; - RtcpHelper(Socket::Ptr rtcp_sock, RtpProcess::Ptr process) { + RtcpHelper(Socket::Ptr rtcp_sock, std::string stream_id) { _rtcp_sock = std::move(rtcp_sock); - _process = std::move(process); + _stream_id = std::move(stream_id); } - void onRecvRtp(const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len){ - //统计rtp接受情况,用于发送rr包 - auto header = (RtpHeader *) buf->data(); - sendRtcp(ntohl(header->ssrc), addr, addr_len); + ~RtcpHelper() { + if (_process) { + // 删除rtp处理器 + RtpSelector::Instance().delProcess(_stream_id, _process.get()); + } } - void startRtcp(){ + void setRtpServerInfo(uint16_t local_port,RtpServer::TcpMode mode,bool re_use_port,uint32_t ssrc){ + _local_port = local_port; + _tcp_mode = mode; + _re_use_port = re_use_port; + _ssrc = ssrc; + } + + void setOnDetach(function cb) { + if (_process) { + _process->setOnDetach(std::move(cb)); + } else { + _on_detach = std::move(cb); + } + } + + void onRecvRtp(const Socket::Ptr &sock, const Buffer::Ptr &buf, struct sockaddr *addr) { + if (!_process) { + _process = RtpSelector::Instance().getProcess(_stream_id, true); + _process->setOnDetach(std::move(_on_detach)); + cancelDelayTask(); + } + _process->inputRtp(true, sock, buf->data(), buf->size(), addr); + + // 统计rtp接受情况,用于发送rr包 + auto header = (RtpHeader *)buf->data(); + sendRtcp(ntohl(header->ssrc), addr); + } + + void startRtcp() { weak_ptr weak_self = shared_from_this(); _rtcp_sock->setOnRead([weak_self](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { - //用于接受rtcp打洞包 + // 用于接受rtcp打洞包 auto strong_self = weak_self.lock(); - if (!strong_self) { + if (!strong_self || !strong_self->_process) { return; } if (!strong_self->_rtcp_addr) { - //只设置一次rtcp对端端口 + // 只设置一次rtcp对端端口 strong_self->_rtcp_addr = std::make_shared(); memcpy(strong_self->_rtcp_addr.get(), addr, addr_len); } @@ -60,37 +87,69 @@ public: strong_self->_process->onRtcp(rtcp); } }); + + GET_CONFIG(uint64_t, timeoutSec, RtpProxy::kTimeoutSec); + _delay_task = _rtcp_sock->getPoller()->doDelayTask(timeoutSec * 1000, [weak_self]() { + if (auto strong_self = weak_self.lock()) { + auto process = RtpSelector::Instance().getProcess(strong_self->_stream_id, false); + if (!process && strong_self->_on_detach) { + strong_self->_on_detach(); + } + if(process && strong_self->_on_detach){// tcp 链接防止断开不删除rtpServer + process->setOnDetach(std::move(strong_self->_on_detach)); + } + if (!process) { // process 未创建,触发rtp server 超时事件 + NoticeCenter::Instance().emitEvent(Broadcast::KBroadcastRtpServerTimeout,strong_self->_local_port,strong_self->_stream_id,(int)strong_self->_tcp_mode,strong_self->_re_use_port,strong_self->_ssrc); + } + } + return 0; + }); + } + + void cancelDelayTask() { + if (_delay_task) { + _delay_task->cancel(); + _delay_task = nullptr; + } } private: - void sendRtcp(uint32_t rtp_ssrc, struct sockaddr *addr, int addr_len){ - //每5秒发送一次rtcp - if (_ticker.elapsedTime() < 5000) { + void sendRtcp(uint32_t rtp_ssrc, struct sockaddr *addr) { + // 每5秒发送一次rtcp + if (_ticker.elapsedTime() < 5000 || !_process) { return; } _ticker.resetTime(); auto rtcp_addr = (struct sockaddr *)_rtcp_addr.get(); if (!rtcp_addr) { - //默认的,rtcp端口为rtp端口+1 - switch(addr->sa_family){ - case AF_INET: ((sockaddr_in *) addr)->sin_port = htons(ntohs(((sockaddr_in *) addr)->sin_port) + 1); break; - case AF_INET6: ((sockaddr_in6 *) addr)->sin6_port = htons(ntohs(((sockaddr_in6 *) addr)->sin6_port) + 1); break; + // 默认的,rtcp端口为rtp端口+1 + switch (addr->sa_family) { + case AF_INET: ((sockaddr_in *)addr)->sin_port = htons(ntohs(((sockaddr_in *)addr)->sin_port) + 1); break; + case AF_INET6: ((sockaddr_in6 *)addr)->sin6_port = htons(ntohs(((sockaddr_in6 *)addr)->sin6_port) + 1); break; } - //未收到rtcp打洞包时,采用默认的rtcp端口 + // 未收到rtcp打洞包时,采用默认的rtcp端口 rtcp_addr = addr; } - _rtcp_sock->send(_process->createRtcpRR(rtp_ssrc + 1, rtp_ssrc), rtcp_addr, addr_len); + _rtcp_sock->send(_process->createRtcpRR(rtp_ssrc + 1, rtp_ssrc), rtcp_addr); } private: + bool _re_use_port = false; + uint16_t _local_port = 0; + uint32_t _ssrc = 0; + RtpServer::TcpMode _tcp_mode = RtpServer::NONE; + Ticker _ticker; Socket::Ptr _rtcp_sock; RtpProcess::Ptr _process; + std::string _stream_id; + function _on_detach; std::shared_ptr _rtcp_addr; + EventPoller::DelayTask::Ptr _delay_task; }; -void RtpServer::start(uint16_t local_port, const string &stream_id, bool enable_tcp, const char *local_ip, bool re_use_port, uint32_t ssrc) { +void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_mode, const char *local_ip, bool re_use_port, uint32_t ssrc) { //创建udp服务器 Socket::Ptr rtp_socket = Socket::createSocket(nullptr, true); Socket::Ptr rtcp_socket = Socket::createSocket(nullptr, true); @@ -110,38 +169,41 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, bool enable_ SockUtil::setRecvBuf(rtp_socket->rawFD(), 4 * 1024 * 1024); TcpServer::Ptr tcp_server; - if (enable_tcp) { + _tcp_mode = tcp_mode; + if (tcp_mode == PASSIVE || tcp_mode == ACTIVE) { //创建tcp服务器 tcp_server = std::make_shared(rtp_socket->getPoller()); (*tcp_server)[RtpSession::kStreamID] = stream_id; - (*tcp_server)[RtpSession::kIsUDP] = 0; (*tcp_server)[RtpSession::kSSRC] = ssrc; - tcp_server->start(rtp_socket->get_local_port(), local_ip); + if (tcp_mode == PASSIVE) { + tcp_server->start(rtp_socket->get_local_port(), local_ip); + } else if (stream_id.empty()) { + // tcp主动模式时只能一个端口一个流,必须指定流id; 创建TcpServer对象也仅用于传参 + throw std::runtime_error(StrPrinter << "tcp主动模式时必需指定流id"); + } } //创建udp服务器 UdpServer::Ptr udp_server; - RtpProcess::Ptr process; + RtcpHelper::Ptr helper; if (!stream_id.empty()) { //指定了流id,那么一个端口一个流(不管是否包含多个ssrc的多个流,绑定rtp源后,会筛选掉ip端口不匹配的流) - process = RtpSelector::Instance().getProcess(stream_id, true); - RtcpHelper::Ptr helper = std::make_shared(std::move(rtcp_socket), process); + helper = std::make_shared(std::move(rtcp_socket), stream_id); helper->startRtcp(); - rtp_socket->setOnRead([rtp_socket, process, helper, ssrc](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + helper->setRtpServerInfo(local_port,tcp_mode,re_use_port,ssrc); + rtp_socket->setOnRead([rtp_socket, helper, ssrc](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { RtpHeader *header = (RtpHeader *)buf->data(); auto rtp_ssrc = ntohl(header->ssrc); if (ssrc && rtp_ssrc != ssrc) { WarnL << "ssrc不匹配,rtp已丢弃:" << rtp_ssrc << " != " << ssrc; } else { - process->inputRtp(true, rtp_socket, buf->data(), buf->size(), addr); - helper->onRecvRtp(buf, addr, addr_len); + helper->onRecvRtp(rtp_socket, buf, addr); } }); } else { #if 1 //单端口多线程接收多个流,根据ssrc区分流 udp_server = std::make_shared(rtp_socket->getPoller()); - (*udp_server)[RtpSession::kIsUDP] = 1; udp_server->start(rtp_socket->get_local_port(), local_ip); rtp_socket = nullptr; #else @@ -153,26 +215,22 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, bool enable_ #endif } - _on_clearup = [rtp_socket, process, stream_id]() { + _on_cleanup = [rtp_socket, stream_id]() { if (rtp_socket) { //去除循环引用 rtp_socket->setOnRead(nullptr); } - if (process) { - //删除rtp处理器 - RtpSelector::Instance().delProcess(stream_id, process.get()); - } }; _tcp_server = tcp_server; _udp_server = udp_server; _rtp_socket = rtp_socket; - _rtp_process = process; + _rtcp_helper = helper; } -void RtpServer::setOnDetach(const function &cb) { - if (_rtp_process) { - _rtp_process->setOnDetach(cb); +void RtpServer::setOnDetach(function cb) { + if (_rtcp_helper) { + _rtcp_helper->setOnDetach(std::move(cb)); } } @@ -180,5 +238,42 @@ uint16_t RtpServer::getPort() { return _udp_server ? _udp_server->getPort() : _rtp_socket->get_local_port(); } +void RtpServer::connectToServer(const std::string &url, uint16_t port, const function &cb) { + if (_tcp_mode != ACTIVE || !_rtp_socket) { + cb(SockException(Err_other, "仅支持tcp主动模式")); + return; + } + weak_ptr weak_self = shared_from_this(); + _rtp_socket->connect(url, port, [url, port, cb, weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + cb(SockException(Err_other, "服务对象已释放")); + return; + } + if (err) { + WarnL << "连接到服务器 " << url << ":" << port << " 失败 " << err.what(); + } else { + InfoL << "连接到服务器 " << url << ":" << port << " 成功"; + strong_self->onConnect(); + } + cb(err); + }, + 5.0F, "::", _rtp_socket->get_local_port()); +} + +void RtpServer::onConnect() { + auto rtp_session = std::make_shared(_rtp_socket); + rtp_session->attachServer(*_tcp_server); + _rtp_socket->setOnRead([rtp_session](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + rtp_session->onRecv(buf); + }); + weak_ptr weak_self = shared_from_this(); + _rtp_socket->setOnErr([weak_self](const SockException &err) { + if (auto strong_self = weak_self.lock()) { + strong_self->_rtp_socket->setOnRead(nullptr); + } + }); +} + }//namespace mediakit #endif//defined(ENABLE_RTPPROXY) diff --git a/src/Rtp/RtpServer.h b/src/Rtp/RtpServer.h index e68abb5c..409671a1 100644 --- a/src/Rtp/RtpServer.h +++ b/src/Rtp/RtpServer.h @@ -18,30 +18,42 @@ #include "Network/UdpServer.h" #include "RtpSession.h" -namespace mediakit{ +namespace mediakit { + +class RtcpHelper; /** * RTP服务器,支持UDP/TCP */ -class RtpServer { +class RtpServer : public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; using onRecv = std::function; + enum TcpMode { NONE = 0, PASSIVE, ACTIVE }; - RtpServer(); + RtpServer() = default; ~RtpServer(); /** * 开启服务器,可能抛异常 * @param local_port 本地端口,0时为随机端口 * @param stream_id 流id,置空则使用ssrc - * @param enable_tcp 是否启用tcp服务器 + * @param tcp_mode tcp服务模式 * @param local_ip 绑定的本地网卡ip * @param re_use_port 是否设置socket为re_use属性 + * @param ssrc 指定的ssrc */ - void start(uint16_t local_port, const std::string &stream_id = "", bool enable_tcp = true, + void start(uint16_t local_port, const std::string &stream_id = "", TcpMode tcp_mode = PASSIVE, const char *local_ip = "::", bool re_use_port = true, uint32_t ssrc = 0); + /** + * 连接到tcp服务(tcp主动模式) + * @param url 服务器地址 + * @param port 服务器端口 + * @param cb 连接服务器是否成功的回调 + */ + void connectToServer(const std::string &url, uint16_t port, const std::function &cb); + /** * 获取绑定的本地端口 */ @@ -50,14 +62,21 @@ public: /** * 设置RtpProcess onDetach事件回调 */ - void setOnDetach(const std::function &cb); + void setOnDetach(std::function cb); + +private: + // tcp主动模式连接服务器成功回调 + void onConnect(); protected: toolkit::Socket::Ptr _rtp_socket; toolkit::UdpServer::Ptr _udp_server; toolkit::TcpServer::Ptr _tcp_server; - RtpProcess::Ptr _rtp_process; - std::function _on_clearup; + std::shared_ptr _rtcp_helper; + std::function _on_cleanup; + + //用于tcp主动模式 + TcpMode _tcp_mode = NONE; }; }//namespace mediakit diff --git a/src/Rtp/RtpSession.cpp b/src/Rtp/RtpSession.cpp index 4b3213e6..db72f822 100644 --- a/src/Rtp/RtpSession.cpp +++ b/src/Rtp/RtpSession.cpp @@ -20,27 +20,22 @@ using namespace toolkit; namespace mediakit{ const string RtpSession::kStreamID = "stream_id"; -const string RtpSession::kIsUDP = "is_udp"; const string RtpSession::kSSRC = "ssrc"; void RtpSession::attachServer(const Server &server) { _stream_id = const_cast(server)[kStreamID]; - _is_udp = const_cast(server)[kIsUDP]; _ssrc = const_cast(server)[kSSRC]; - - if (_is_udp) { - //设置udp socket读缓存 - SockUtil::setRecvBuf(getSock()->rawFD(), 4 * 1024 * 1024); - _statistic_udp = std::make_shared >(); - } else { - _statistic_tcp = std::make_shared >(); - } } RtpSession::RtpSession(const Socket::Ptr &sock) : Session(sock) { DebugP(this); socklen_t addr_len = sizeof(_addr); getpeername(sock->rawFD(), (struct sockaddr *)&_addr, &addr_len); + _is_udp = sock->sockType() == SockNum::Sock_UDP; + if (_is_udp) { + // 设置udp socket读缓存 + SockUtil::setRecvBuf(getSock()->rawFD(), 4 * 1024 * 1024); + } } RtpSession::~RtpSession() { @@ -124,13 +119,10 @@ void RtpSession::onRtpPacket(const char *data, size_t len) { _ticker.resetTime(); } -bool RtpSession::close(MediaSource &sender, bool force) { +bool RtpSession::close(MediaSource &sender) { //此回调在其他线程触发 - if(!_process || (!force && static_pointer_cast(_process)->totalReaderCount(sender))){ - return false; - } - string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; - safeShutdown(SockException(Err_shutdown,err)); + string err = StrPrinter << "close media: " << sender.getUrl(); + safeShutdown(SockException(Err_shutdown, err)); return true; } diff --git a/src/Rtp/RtpSession.h b/src/Rtp/RtpSession.h index 842ea53c..fa346802 100644 --- a/src/Rtp/RtpSession.h +++ b/src/Rtp/RtpSession.h @@ -13,7 +13,7 @@ #if defined(ENABLE_RTPPROXY) -#include "Network/TcpSession.h" +#include "Network/Session.h" #include "RtpSplitter.h" #include "RtpProcess.h" #include "Util/TimeTicker.h" @@ -23,7 +23,6 @@ namespace mediakit{ class RtpSession : public toolkit::Session, public RtpSplitter, public MediaSourceEvent { public: static const std::string kStreamID; - static const std::string kIsUDP; static const std::string kSSRC; RtpSession(const toolkit::Socket::Ptr &sock); @@ -35,7 +34,7 @@ public: protected: // 通知其停止推流 - bool close(MediaSource &sender,bool force) override; + bool close(MediaSource &sender) override; // 收到rtp回调 void onRtpPacket(const char *data, size_t len) override; // RtpSplitter override @@ -50,8 +49,6 @@ private: std::string _stream_id; struct sockaddr_storage _addr; RtpProcess::Ptr _process; - std::shared_ptr > _statistic_tcp; - std::shared_ptr > _statistic_udp; }; }//namespace mediakit diff --git a/src/Rtp/RtpSplitter.cpp b/src/Rtp/RtpSplitter.cpp index 636340a2..594d689b 100644 --- a/src/Rtp/RtpSplitter.cpp +++ b/src/Rtp/RtpSplitter.cpp @@ -38,7 +38,11 @@ static bool isEhome(const char *data, size_t len){ if (len < 4) { return false; } - return memcmp(data, kEHOME_MAGIC, sizeof(kEHOME_MAGIC) - 1) == 0; + if((data[0] == 0x01) && (data[1] == 0x00) && (data[2] >=0x01)){ + return true; + } + return false; + //return memcmp(data, kEHOME_MAGIC, sizeof(kEHOME_MAGIC) - 1) == 0; } const char *RtpSplitter::onSearchPacketTail(const char *data, size_t len) { diff --git a/src/Rtp/TSDecoder.cpp b/src/Rtp/TSDecoder.cpp index c31ddc9b..4f00ada0 100644 --- a/src/Rtp/TSDecoder.cpp +++ b/src/Rtp/TSDecoder.cpp @@ -55,7 +55,6 @@ const char *TSSegment::onSearchPacketTail(const char *data, size_t len) { #if defined(ENABLE_HLS) #include "mpeg-ts.h" -#include "mpeg-ts-proto.h" TSDecoder::TSDecoder() : _ts_segment() { _ts_segment.setOnSegment([this](const char *data, size_t len){ ts_demuxer_input(_demuxer_ctx,(uint8_t*)data,len); diff --git a/src/Rtsp/RtpMultiCaster.h b/src/Rtsp/RtpMultiCaster.h index 1195a92a..f55189e6 100644 --- a/src/Rtsp/RtpMultiCaster.h +++ b/src/Rtsp/RtpMultiCaster.h @@ -24,7 +24,7 @@ namespace mediakit{ class MultiCastAddressMaker { public: - ~MultiCastAddressMaker() {} + ~MultiCastAddressMaker() = default; static MultiCastAddressMaker& Instance(); static bool isMultiCastAddress(uint32_t addr); static std::string toString(uint32_t addr); @@ -32,7 +32,7 @@ public: std::shared_ptr obtain(uint32_t max_try = 10); private: - MultiCastAddressMaker() {}; + MultiCastAddressMaker() = default; void release(uint32_t addr); private: diff --git a/src/Rtsp/RtpReceiver.h b/src/Rtsp/RtpReceiver.h index d7ec3efe..f855d604 100644 --- a/src/Rtsp/RtpReceiver.h +++ b/src/Rtsp/RtpReceiver.h @@ -60,6 +60,10 @@ public: * @param packet 包负载 */ void sortPacket(SEQ seq, T packet) { + if(!_is_inited && _next_seq_out == 0){ + _next_seq_out = seq; + _is_inited = true; + } if (seq < _next_seq_out) { if (_next_seq_out < seq + kMax) { //过滤seq回退包(回环包除外) @@ -147,6 +151,9 @@ private: } private: + //第一个包是已经进入 + bool _is_inited = false; + //下次应该输出的SEQ SEQ _next_seq_out = 0; //seq回环次数计数 diff --git a/src/Rtsp/RtspMediaSource.h b/src/Rtsp/RtspMediaSource.h index adfceca0..c9bdc0df 100644 --- a/src/Rtsp/RtspMediaSource.h +++ b/src/Rtsp/RtspMediaSource.h @@ -56,7 +56,7 @@ public: int ring_size = RTP_GOP_SIZE) : MediaSource(RTSP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} - ~RtspMediaSource() override{} + ~RtspMediaSource() override { flush(); } /** * 获取媒体源的环形缓冲 diff --git a/src/Rtsp/RtspMediaSourceImp.h b/src/Rtsp/RtspMediaSourceImp.h index 36878fa8..975491bd 100644 --- a/src/Rtsp/RtspMediaSourceImp.h +++ b/src/Rtsp/RtspMediaSourceImp.h @@ -17,9 +17,9 @@ #include "Common/MultiMediaSourceMuxer.h" namespace mediakit { -class RtspMediaSourceImp : public RtspMediaSource, private TrackListener, public MultiMediaSourceMuxer::Listener { +class RtspMediaSourceImp final : public RtspMediaSource, private TrackListener, public MultiMediaSourceMuxer::Listener { public: - typedef std::shared_ptr Ptr; + using Ptr = std::shared_ptr; /** * 构造函数 @@ -33,7 +33,7 @@ public: _demuxer->setTrackListener(this); } - ~RtspMediaSourceImp() = default; + ~RtspMediaSourceImp() override = default; /** * 设置sdp diff --git a/src/Rtsp/RtspMediaSourceMuxer.h b/src/Rtsp/RtspMediaSourceMuxer.h index 539a2011..65e6f91a 100644 --- a/src/Rtsp/RtspMediaSourceMuxer.h +++ b/src/Rtsp/RtspMediaSourceMuxer.h @@ -16,20 +16,22 @@ namespace mediakit { -class RtspMediaSourceMuxer : public RtspMuxer, public MediaSourceEventInterceptor, - public std::enable_shared_from_this { +class RtspMediaSourceMuxer final : public RtspMuxer, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { public: typedef std::shared_ptr Ptr; RtspMediaSourceMuxer(const std::string &vhost, const std::string &strApp, const std::string &strId, - const TitleSdp::Ptr &title = nullptr) : RtspMuxer(title){ + const ProtocolOption &option, + const TitleSdp::Ptr &title = nullptr) : RtspMuxer(title) { + _option = option; _media_src = std::make_shared(vhost,strApp,strId); getRtpRing()->setDelegate(_media_src); } - ~RtspMediaSourceMuxer() override{} + ~RtspMediaSourceMuxer() override { RtspMuxer::flush(); } void setListener(const std::weak_ptr &listener){ setDelegate(listener); @@ -49,35 +51,33 @@ public: } void onReaderChanged(MediaSource &sender, int size) override { - GET_CONFIG(bool, rtsp_demand, General::kRtspDemand); - _enabled = rtsp_demand ? size : true; - if (!size && rtsp_demand) { + _enabled = _option.rtsp_demand ? size : true; + if (!size && _option.rtsp_demand) { _clear_cache = true; } MediaSourceEventInterceptor::onReaderChanged(sender, size); } bool inputFrame(const Frame::Ptr &frame) override { - GET_CONFIG(bool, rtsp_demand, General::kRtspDemand); - if (_clear_cache && rtsp_demand) { + if (_clear_cache && _option.rtsp_demand) { _clear_cache = false; _media_src->clearCache(); } - if (_enabled || !rtsp_demand) { + if (_enabled || !_option.rtsp_demand) { return RtspMuxer::inputFrame(frame); } return false; } bool isEnabled() { - GET_CONFIG(bool, rtsp_demand, General::kRtspDemand); //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 - return rtsp_demand ? (_clear_cache ? true : _enabled) : true; + return _option.rtsp_demand ? (_clear_cache ? true : _enabled) : true; } private: bool _enabled = true; bool _clear_cache = false; + ProtocolOption _option; RtspMediaSource::Ptr _media_src; }; diff --git a/src/Rtsp/RtspMuxer.cpp b/src/Rtsp/RtspMuxer.cpp index 890232f4..755b7f5a 100644 --- a/src/Rtsp/RtspMuxer.cpp +++ b/src/Rtsp/RtspMuxer.cpp @@ -86,6 +86,14 @@ bool RtspMuxer::inputFrame(const Frame::Ptr &frame) { return encoder ? encoder->inputFrame(frame) : false; } +void RtspMuxer::flush() { + for (auto &encoder : _encoder) { + if (encoder) { + encoder->flush(); + } + } +} + string RtspMuxer::getSdp() { return _sdp; } diff --git a/src/Rtsp/RtspMuxer.h b/src/Rtsp/RtspMuxer.h index 677eaabd..6b5cfa56 100644 --- a/src/Rtsp/RtspMuxer.h +++ b/src/Rtsp/RtspMuxer.h @@ -22,7 +22,7 @@ class RingDelegateHelper : public toolkit::RingDelegate { public: using onRtp = std::function ; - ~RingDelegateHelper() override {} + ~RingDelegateHelper() override = default; RingDelegateHelper(onRtp on_rtp) { _on_rtp = std::move(on_rtp); @@ -39,7 +39,7 @@ private: /** * rtsp生成器 */ -class RtspMuxer : public MediaSinkInterface{ +class RtspMuxer : public MediaSinkInterface { public: using Ptr = std::shared_ptr; @@ -47,8 +47,7 @@ public: * 构造函数 */ RtspMuxer(const TitleSdp::Ptr &title = nullptr); - - virtual ~RtspMuxer(){} + ~RtspMuxer() override = default; /** * 获取完整的SDP字符串 @@ -73,6 +72,11 @@ public: */ bool inputFrame(const Frame::Ptr &frame) override; + /** + * 刷新输出所有frame缓存 + */ + void flush() override; + /** * 重置所有track */ diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index 38b53669..ec63a08e 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -197,13 +197,24 @@ void RtspPlayer::handleResDESCRIBE(const Parser& parser) { _content_base.pop_back(); } - SdpParser sdpParser(parser.Content()); //解析sdp - _sdp_track = sdpParser.getAvailableTrack(); + SdpParser sdpParser(parser.Content()); + + string sdp; + auto play_track = (TrackType)((int)(*this)[Client::kPlayTrack] - 1); + if (play_track != TrackInvalid) { + auto track = sdpParser.getTrack(play_track); + _sdp_track.emplace_back(track); + sdp = track->toString(); + } else { + _sdp_track = sdpParser.getAvailableTrack(); + sdp = sdpParser.toString(); + } + if (_sdp_track.empty()) { throw std::runtime_error("无有效的Sdp Track"); } - if (!onCheckSDP(sdpParser.toString())) { + if (!onCheckSDP(sdp)) { throw std::runtime_error("onCheckSDP faied"); } _rtcp_context.clear(); @@ -569,16 +580,16 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrCaseMap &header_const) { auto header = header_const; - header.emplace("CSeq",StrPrinter << _cseq_send++); - header.emplace("User-Agent",kServerName); + header.emplace("CSeq", StrPrinter << _cseq_send++); + header.emplace("User-Agent", kServerName); - if(!_session_id.empty()){ + if (!_session_id.empty()) { header.emplace("Session", _session_id); } - if(!_realm.empty() && !(*this)[Client::kRtspUser].empty()){ - if(!_md5_nonce.empty()){ - //MD5认证 + if (!_realm.empty() && !(*this)[Client::kRtspUser].empty()) { + if (!_md5_nonce.empty()) { + // MD5认证 /* response计算方法如下: RTSP客户端应该使用username + password并计算response如下: @@ -588,7 +599,7 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrC response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); */ string encrypted_pwd = (*this)[Client::kRtspPwd]; - if(!(*this)[Client::kRtspPwdIsMD5].as()){ + if (!(*this)[Client::kRtspPwdIsMD5].as()) { encrypted_pwd = MD5((*this)[Client::kRtspUser] + ":" + _realm + ":" + encrypted_pwd).hexdigest(); } auto response = MD5(encrypted_pwd + ":" + _md5_nonce + ":" + MD5(cmd + ":" + url).hexdigest()).hexdigest(); @@ -599,13 +610,11 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrC printer << "nonce=\"" << _md5_nonce << "\", "; printer << "uri=\"" << url << "\", "; printer << "response=\"" << response << "\""; - header.emplace("Authorization",printer); - }else if(!(*this)[Client::kRtspPwdIsMD5].as()){ - //base64认证 - string authStr = StrPrinter << (*this)[Client::kRtspUser] << ":" << (*this)[Client::kRtspPwd]; - char authStrBase64[1024] = {0}; - av_base64_encode(authStrBase64, sizeof(authStrBase64), (uint8_t *) authStr.data(), (int) authStr.size()); - header.emplace("Authorization",StrPrinter << "Basic " << authStrBase64 ); + header.emplace("Authorization", printer); + } else if (!(*this)[Client::kRtspPwdIsMD5].as()) { + // base64认证 + auto authStrBase64 = encodeBase64((*this)[Client::kRtspUser] + ":" + (*this)[Client::kRtspPwd]); + header.emplace("Authorization", StrPrinter << "Basic " << authStrBase64); } } diff --git a/src/Rtsp/RtspPusher.cpp b/src/Rtsp/RtspPusher.cpp index a1fc6e48..ab783e10 100644 --- a/src/Rtsp/RtspPusher.cpp +++ b/src/Rtsp/RtspPusher.cpp @@ -537,10 +537,8 @@ void RtspPusher::sendRtspRequest(const string &cmd, const string &url,const StrC printer << "response=\"" << response << "\""; header.emplace("Authorization", printer); } else if (!(*this)[Client::kRtspPwdIsMD5].as()) { - //base64认证 - string authStr = StrPrinter << (*this)[Client::kRtspUser] << ":" << (*this)[Client::kRtspPwd]; - char authStrBase64[1024] = {0}; - av_base64_encode(authStrBase64, sizeof(authStrBase64), (uint8_t *) authStr.data(), (int)authStr.size()); + // base64认证 + auto authStrBase64 = encodeBase64((*this)[Client::kRtspUser] + ":" + (*this)[Client::kRtspPwd]); header.emplace("Authorization", StrPrinter << "Basic " << authStrBase64); } } diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index 91a51dc7..2a67a18e 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -49,7 +49,7 @@ static unordered_map > g_mapGetter; //对g_mapGetter上锁保护 static recursive_mutex g_mtxGetter; -RtspSession::RtspSession(const Socket::Ptr &sock) : TcpSession(sock) { +RtspSession::RtspSession(const Socket::Ptr &sock) : Session(sock) { DebugP(this); GET_CONFIG(uint32_t,keep_alive_sec,Rtsp::kKeepAliveSecond); sock->setSendTimeOutSecond(keep_alive_sec); @@ -63,9 +63,7 @@ void RtspSession::onError(const SockException &err) { bool is_player = !_push_src_ownership; uint64_t duration = _alive_ticker.createdTime() / 1000; WarnP(this) << (is_player ? "RTSP播放器(" : "RTSP推流器(") - << _media_info._vhost << "/" - << _media_info._app << "/" - << _media_info._streamid + << _media_info.shortUrl() << ")断开:" << err.what() << ",耗时(s):" << duration; @@ -249,9 +247,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { if (push_failed) { sendRtspResponse("406 Not Acceptable", { "Content-Type", "text/plain" }, "Already publishing."); - string err = StrPrinter << "ANNOUNCE:" - << "Already publishing:" << _media_info._vhost << " " << _media_info._app << " " - << _media_info._streamid << endl; + string err = StrPrinter << "ANNOUNCE: Already publishing:" << _media_info.shortUrl() << endl; throw SockException(Err_shutdown, err); } @@ -417,7 +413,7 @@ void RtspSession::onAuthSuccess() { auto rtsp_src = dynamic_pointer_cast(src); if (!rtsp_src) { //未找到相应的MediaSource - string err = StrPrinter << "no such stream:" << strong_self->_media_info._vhost << " " << strong_self->_media_info._app << " " << strong_self->_media_info._streamid; + string err = StrPrinter << "no such stream:" << strong_self->_media_info.shortUrl(); strong_self->send_StreamNotFound(); strong_self->shutdown(SockException(Err_shutdown,err)); return; @@ -452,31 +448,26 @@ void RtspSession::onAuthSuccess() { } void RtspSession::onAuthFailed(const string &realm,const string &why,bool close) { - GET_CONFIG(bool,authBasic,Rtsp::kAuthBasic); + GET_CONFIG(bool, authBasic, Rtsp::kAuthBasic); if (!authBasic) { - //我们需要客户端优先以md5方式认证 + // 我们需要客户端优先以md5方式认证 _auth_nonce = makeRandStr(32); - sendRtspResponse("401 Unauthorized", - {"WWW-Authenticate", - StrPrinter << "Digest realm=\"" << realm << "\",nonce=\"" << _auth_nonce << "\"" }); - }else { - //当然我们也支持base64认证,但是我们不建议这样做 - sendRtspResponse("401 Unauthorized", - {"WWW-Authenticate", - StrPrinter << "Basic realm=\"" << realm << "\"" }); + sendRtspResponse("401 Unauthorized", { "WWW-Authenticate", StrPrinter << "Digest realm=\"" << realm << "\",nonce=\"" << _auth_nonce << "\"" }); + } else { + // 当然我们也支持base64认证,但是我们不建议这样做 + sendRtspResponse("401 Unauthorized", { "WWW-Authenticate", StrPrinter << "Basic realm=\"" << realm << "\"" }); } - if(close){ - shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << why)); + if (close) { + shutdown(SockException(Err_shutdown, StrPrinter << "401 Unauthorized:" << why)); } } -void RtspSession::onAuthBasic(const string &realm,const string &auth_base64){ +void RtspSession::onAuthBasic(const string &realm, const string &auth_base64) { //base64认证 - char user_pwd_buf[512]; - av_base64_decode((uint8_t *) user_pwd_buf, auth_base64.data(), (int)auth_base64.size()); - auto user_pwd_vec = split(user_pwd_buf, ":"); + auto user_passwd = decodeBase64(auth_base64); + auto user_pwd_vec = split(user_passwd, ":"); if (user_pwd_vec.size() < 2) { - //认证信息格式不合法,回复401 Unauthorized + // 认证信息格式不合法,回复401 Unauthorized onAuthFailed(realm, "can not find user and passwd when basic64 auth"); return; } @@ -805,13 +796,14 @@ void RtspSession::handleReq_Play(const Parser &parser) { InfoP(this) << "rtsp seekTo(ms):" << iStartTime; } + vector inited_tracks; _StrPrinter rtp_info; for (auto &track : _sdp_track) { if (track->_inited == false) { - //还有track没有setup - shutdown(SockException(Err_shutdown, "track not setuped")); - return; + //为支持播放器播放单一track, 不校验没有发setup的track + continue; } + inited_tracks.emplace_back(track->_type); track->_ssrc = play_src->getSsrc(track->_type); track->_seq = play_src->getSeqence(track->_type); track->_time_stamp = play_src->getTimeStamp(track->_type); @@ -828,6 +820,12 @@ void RtspSession::handleReq_Play(const Parser &parser) { res_header.emplace("Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << play_src->getTimeStamp(TrackInvalid) / 1000.0); sendRtspResponse("200 OK", res_header); + //设置播放track + if (inited_tracks.size() == 1) { + _target_play_track = inited_tracks[0]; + InfoP(this) << "指定播放track:" << _target_play_track; + } + //在回复rtsp信令后再恢复播放 play_src->pause(false); @@ -960,13 +958,17 @@ void RtspSession::onRcvPeerUdpData(int interleaved, const Buffer::Ptr &buf, cons } else if (!_udp_connected_flags.count(interleaved)) { //这是rtsp播放器的rtp打洞包 _udp_connected_flags.emplace(interleaved); - _rtp_socks[interleaved / 2]->bindPeerAddr((struct sockaddr *)&addr); + if (_rtp_socks[interleaved / 2]) { + _rtp_socks[interleaved / 2]->bindPeerAddr((struct sockaddr *)&addr); + } } } else { //rtcp包 if (!_udp_connected_flags.count(interleaved)) { _udp_connected_flags.emplace(interleaved); - _rtcp_socks[(interleaved - 1) / 2]->bindPeerAddr((struct sockaddr *)&addr); + if (_rtcp_socks[(interleaved - 1) / 2]) { + _rtcp_socks[(interleaved - 1) / 2]->bindPeerAddr((struct sockaddr *)&addr); + } } onRtcpPacket((interleaved - 1) / 2, _sdp_track[(interleaved - 1) / 2], buf->data(), buf->size()); } @@ -1076,7 +1078,7 @@ ssize_t RtspSession::send(Buffer::Ptr pkt){ // DebugP(this) << pkt->data(); // } _bytes_usage += pkt->size(); - return TcpSession::send(std::move(pkt)); + return Session::send(std::move(pkt)); } bool RtspSession::sendRtspResponse(const string &res_code, const std::initializer_list &header, const string &sdp, const char *protocol) { @@ -1129,12 +1131,9 @@ int RtspSession::getTrackIndexByInterleaved(int interleaved) { throw SockException(Err_shutdown, StrPrinter << "no such track with interleaved:" << interleaved); } -bool RtspSession::close(MediaSource &sender, bool force) { +bool RtspSession::close(MediaSource &sender) { //此回调在其他线程触发 - if(!_push_src || (!force && _push_src->totalReaderCount())){ - return false; - } - string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + string err = StrPrinter << "close media: " << sender.getUrl(); safeShutdown(SockException(Err_shutdown,err)); return true; } @@ -1155,6 +1154,10 @@ std::shared_ptr RtspSession::getOriginSock(MediaSource &sender) const return const_cast(this)->shared_from_this(); } +toolkit::EventPoller::Ptr RtspSession::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + void RtspSession::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_index){ updateRtcpContext(rtp); } @@ -1194,32 +1197,39 @@ void RtspSession::updateRtcpContext(const RtpPacket::Ptr &rtp){ void RtspSession::sendRtpPacket(const RtspMediaSource::RingDataType &pkt) { switch (_rtp_type) { case Rtsp::RTP_TCP: { - size_t i = 0; - auto size = pkt->size(); setSendFlushFlag(false); pkt->for_each([&](const RtpPacket::Ptr &rtp) { - updateRtcpContext(rtp); - if (++i == size) { - setSendFlushFlag(true); + if (_target_play_track == TrackInvalid || _target_play_track == rtp->type) { + updateRtcpContext(rtp); + send(rtp); } - send(rtp); }); + flushAll(); + setSendFlushFlag(true); } break; case Rtsp::RTP_UDP: { - size_t i = 0; - auto size = pkt->size(); + //下标0表示视频,1表示音频 + Socket::Ptr rtp_socks[2]; + rtp_socks[TrackVideo] = _rtp_socks[getTrackIndexByTrackType(TrackVideo)]; + rtp_socks[TrackAudio] = _rtp_socks[getTrackIndexByTrackType(TrackAudio)]; pkt->for_each([&](const RtpPacket::Ptr &rtp) { - updateRtcpContext(rtp); - int track_index = getTrackIndexByTrackType(rtp->type); - auto &pSock = _rtp_socks[track_index]; - if (!pSock) { - shutdown(SockException(Err_shutdown, "udp sock not opened yet")); - return; + if (_target_play_track == TrackInvalid || _target_play_track == rtp->type) { + updateRtcpContext(rtp); + auto &sock = rtp_socks[rtp->type]; + if (!sock) { + shutdown(SockException(Err_shutdown, "udp sock not opened yet")); + return; + } + _bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize; + sock->send(std::make_shared(rtp, RtpPacket::kRtpTcpHeaderSize), nullptr, 0, false); } - _bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize; - pSock->send(std::make_shared(rtp, RtpPacket::kRtpTcpHeaderSize), nullptr, 0, ++i == size); }); + for (auto &sock : rtp_socks) { + if (sock) { + sock->flushAll(); + } + } } break; default: diff --git a/src/Rtsp/RtspSession.h b/src/Rtsp/RtspSession.h index b07428aa..fb8f080b 100644 --- a/src/Rtsp/RtspSession.h +++ b/src/Rtsp/RtspSession.h @@ -18,7 +18,7 @@ #include "Util/util.h" #include "Util/logger.h" #include "Common/config.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" #include "Player/PlayerBase.h" #include "RtpMultiCaster.h" #include "RtspMediaSource.h" @@ -37,7 +37,7 @@ public: using Ptr = std::shared_ptr; BufferRtp(Buffer::Ptr pkt, size_t offset = 0) : _offset(offset), _rtp(std::move(pkt)) {} - ~BufferRtp() override {} + ~BufferRtp() override = default; char *data() const override { return (char *)_rtp->data() + _offset; @@ -52,7 +52,7 @@ private: Buffer::Ptr _rtp; }; -class RtspSession : public toolkit::TcpSession, public RtspSplitter, public RtpReceiver, public MediaSourceEvent { +class RtspSession : public toolkit::Session, public RtspSplitter, public RtpReceiver, public MediaSourceEvent { public: using Ptr = std::shared_ptr; using onGetRealm = std::function; @@ -62,7 +62,7 @@ public: RtspSession(const toolkit::Socket::Ptr &sock); virtual ~RtspSession(); - ////TcpSession override//// + ////Session override//// void onRecv(const toolkit::Buffer::Ptr &buf) override; void onError(const toolkit::SockException &err) override; void onManager() override; @@ -82,7 +82,7 @@ protected: ///////MediaSourceEvent override/////// // 关闭 - bool close(MediaSource &sender, bool force) override; + bool close(MediaSource &sender) override; // 播放总人数 int totalReaderCount(MediaSource &sender) override; // 获取媒体源类型 @@ -91,8 +91,10 @@ protected: std::string getOriginUrl(MediaSource &sender) const override; // 获取媒体源客户端相关信息 std::shared_ptr getOriginSock(MediaSource &sender) const override; + // 由于支持断连续推,存在OwnerPoller变更的可能 + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; - /////TcpSession override//// + /////Session override//// ssize_t send(toolkit::Buffer::Ptr pkt) override; //收到RTCP包回调 virtual void onRtcpPacket(int track_idx, SdpTrack::Ptr &track, const char *data, size_t len); @@ -195,6 +197,8 @@ private: RtspMediaSource::RingType::RingReader::Ptr _play_reader; //sdp里面有效的track,包含音频或视频 std::vector _sdp_track; + //播放器setup指定的播放track,默认为TrackInvalid表示不指定即音视频都推 + TrackType _target_play_track = TrackInvalid; ////////RTP over udp//////// //RTP端口,trackid idx 为数组下标 @@ -221,7 +225,7 @@ private: /** * 支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 */ -using RtspSessionWithSSL = toolkit::TcpSessionWithSSL; +using RtspSessionWithSSL = toolkit::SessionWithSSL; } /* namespace mediakit */ diff --git a/src/Shell/ShellCMD.h b/src/Shell/ShellCMD.h index 95c99a20..2e283b00 100644 --- a/src/Shell/ShellCMD.h +++ b/src/Shell/ShellCMD.h @@ -23,12 +23,7 @@ public: MediaSource::for_each_media([&](const MediaSource::Ptr &media) { if (ini.find("list") != ini.end()) { //列出源 - (*stream) << "\t" - << media->getSchema() << "/" - << media->getVhost() << "/" - << media->getApp() << "/" - << media->getId() - << "\r\n"; + (*stream) << "\t" << media->getUrl() << "\r\n"; return; } @@ -42,20 +37,10 @@ public: if (!media->close(true)) { break; } - (*stream) << "\t踢出成功:" - << media->getSchema() << "/" - << media->getVhost() << "/" - << media->getApp() << "/" - << media->getId() - << "\r\n"; + (*stream) << "\t踢出成功:" << media->getUrl() << "\r\n"; return; } while (0); - (*stream) << "\t踢出失败:" - << media->getSchema() << "/" - << media->getVhost() << "/" - << media->getApp() << "/" - << media->getId() - << "\r\n"; + (*stream) << "\t踢出失败:" << media->getUrl() << "\r\n"; } }, false); diff --git a/src/Shell/ShellSession.cpp b/src/Shell/ShellSession.cpp index 79c7b1b9..fe3ac75b 100644 --- a/src/Shell/ShellSession.cpp +++ b/src/Shell/ShellSession.cpp @@ -24,7 +24,7 @@ static onceToken s_token([]() { REGIST_CMD(media); }, nullptr); -ShellSession::ShellSession(const Socket::Ptr &_sock) : TcpSession(_sock) { +ShellSession::ShellSession(const Socket::Ptr &_sock) : Session(_sock) { DebugP(this); pleaseInputUser(); } diff --git a/src/Shell/ShellSession.h b/src/Shell/ShellSession.h index f061a55f..6f1a1b7b 100644 --- a/src/Shell/ShellSession.h +++ b/src/Shell/ShellSession.h @@ -14,11 +14,11 @@ #include #include "Common/config.h" #include "Util/TimeTicker.h" -#include "Network/TcpSession.h" +#include "Network/Session.h" namespace mediakit { -class ShellSession: public toolkit::TcpSession { +class ShellSession: public toolkit::Session { public: ShellSession(const toolkit::Socket::Ptr &_sock); virtual ~ShellSession(); diff --git a/src/TS/TSMediaSource.h b/src/TS/TSMediaSource.h index d1e18e41..253e1652 100644 --- a/src/TS/TSMediaSource.h +++ b/src/TS/TSMediaSource.h @@ -31,7 +31,7 @@ public: }; //TS直播源 -class TSMediaSource : public MediaSource, public toolkit::RingDelegate, private PacketCache{ +class TSMediaSource final : public MediaSource, public toolkit::RingDelegate, private PacketCache{ public: using Ptr = std::shared_ptr; using RingDataType = std::shared_ptr >; @@ -42,7 +42,7 @@ public: const std::string &stream_id, int ring_size = TS_GOP_SIZE) : MediaSource(TS_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} - ~TSMediaSource() override = default; + ~TSMediaSource() override { flush(); } /** * 获取媒体源的环形缓冲 diff --git a/src/TS/TSMediaSourceMuxer.h b/src/TS/TSMediaSourceMuxer.h index 2b3e5d80..392b3236 100644 --- a/src/TS/TSMediaSourceMuxer.h +++ b/src/TS/TSMediaSourceMuxer.h @@ -16,18 +16,20 @@ namespace mediakit { -class TSMediaSourceMuxer : public MpegMuxer, public MediaSourceEventInterceptor, - public std::enable_shared_from_this { +class TSMediaSourceMuxer final : public MpegMuxer, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; TSMediaSourceMuxer(const std::string &vhost, const std::string &app, - const std::string &stream_id) : MpegMuxer(false) { + const std::string &stream_id, + const ProtocolOption &option) : MpegMuxer(false) { + _option = option; _media_src = std::make_shared(vhost, app, stream_id); } - ~TSMediaSourceMuxer() override = default; + ~TSMediaSourceMuxer() override { MpegMuxer::flush(); }; void setListener(const std::weak_ptr &listener){ setDelegate(listener); @@ -39,30 +41,27 @@ public: } void onReaderChanged(MediaSource &sender, int size) override { - GET_CONFIG(bool, ts_demand, General::kTSDemand); - _enabled = ts_demand ? size : true; - if (!size && ts_demand) { + _enabled = _option.ts_demand ? size : true; + if (!size && _option.ts_demand) { _clear_cache = true; } MediaSourceEventInterceptor::onReaderChanged(sender, size); } bool inputFrame(const Frame::Ptr &frame) override { - GET_CONFIG(bool, ts_demand, General::kTSDemand); - if (_clear_cache && ts_demand) { + if (_clear_cache && _option.ts_demand) { _clear_cache = false; _media_src->clearCache(); } - if (_enabled || !ts_demand) { + if (_enabled || !_option.ts_demand) { return MpegMuxer::inputFrame(frame); } return false; } bool isEnabled() { - GET_CONFIG(bool, ts_demand, General::kTSDemand); //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 - return ts_demand ? (_clear_cache ? true : _enabled) : true; + return _option.ts_demand ? (_clear_cache ? true : _enabled) : true; } protected: @@ -78,6 +77,7 @@ protected: private: bool _enabled = true; bool _clear_cache = false; + ProtocolOption _option; TSMediaSource::Ptr _media_src; }; diff --git a/srt/Ack.cpp b/srt/Ack.cpp index f0afc017..44249cb4 100644 --- a/srt/Ack.cpp +++ b/srt/Ack.cpp @@ -59,10 +59,10 @@ bool ACKPacket::storeToData() { storeUint32(ptr, rtt_variance); ptr += 4; - storeUint32(ptr, pkt_recv_rate); + storeUint32(ptr, available_buf_size); ptr += 4; - storeUint32(ptr, available_buf_size); + storeUint32(ptr, pkt_recv_rate); ptr += 4; storeUint32(ptr, estimated_link_capacity); diff --git a/srt/Common.hpp b/srt/Common.hpp index de5d3aaa..ba7dc3ff 100644 --- a/srt/Common.hpp +++ b/srt/Common.hpp @@ -13,8 +13,9 @@ #endif // defined(_WIN32) #include -#define MAX_SEQ 0x7fffffff -#define MAX_TS 0xffffffff +#define MAX_SEQ 0x7fffffff +#define SEQ_NONE 0xffffffff +#define MAX_TS 0xffffffff namespace SRT { using SteadyClock = std::chrono::steady_clock; @@ -35,6 +36,25 @@ static inline uint16_t loadUint16(uint8_t *ptr) { return ptr[0] << 8 | ptr[1]; } +inline static int64_t seqCmp(uint32_t seq1, uint32_t seq2) { + if(seq1 > seq2){ + if((seq1 - seq2) >(MAX_SEQ>>1)){ + return (int64_t)seq1 - (int64_t)(seq2+MAX_SEQ); + }else{ + return (int64_t)seq1 - (int64_t)seq2; + } + }else{ + if((seq2-seq1) >(MAX_SEQ>>1)){ + return (int64_t)(seq1+MAX_SEQ) - (int64_t)seq2; + }else{ + return (int64_t)seq1 - (int64_t)seq2; + } + } +} + +inline static uint32_t incSeq(int32_t seq) { + return (seq == MAX_SEQ) ? 0 : seq + 1; +} static inline void storeUint32(uint8_t *buf, uint32_t val) { buf[0] = val >> 24; buf[1] = (val >> 16) & 0xff; diff --git a/srt/Packet.hpp b/srt/Packet.hpp index 546ece0c..601bf94f 100644 --- a/srt/Packet.hpp +++ b/srt/Packet.hpp @@ -14,6 +14,20 @@ namespace SRT { using namespace toolkit; + +static const size_t HDR_SIZE = 16; // packet header size = SRT_PH_E_SIZE * sizeof(uint32_t) + +// Can also be calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr). +static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. + +static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; + +// Maximum transmission unit size. 1500 in case of Ethernet II (RFC 1191). +static const size_t ETH_MAX_MTU_SIZE = 1500; + +// Maximum payload size of an SRT packet. +static const size_t SRT_MAX_PAYLOAD_SIZE = ETH_MAX_MTU_SIZE - SRT_DATA_HDR_SIZE; + /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/srt/SrtSession.cpp b/srt/SrtSession.cpp index d62cb7cd..5faa02d2 100644 --- a/srt/SrtSession.cpp +++ b/srt/SrtSession.cpp @@ -8,7 +8,7 @@ namespace SRT { using namespace mediakit; SrtSession::SrtSession(const Socket::Ptr &sock) - : UdpSession(sock) { + : Session(sock) { socklen_t addr_len = sizeof(_peer_addr); memset(&_peer_addr, 0, addr_len); // TraceL<<"before addr len "<async( - [transport, err] { + [transport] { //延时减引用,防止使用transport对象时,销毁对象 - transport->onShutdown(err); + //transport->onShutdown(err); }, false); } diff --git a/srt/SrtSession.hpp b/srt/SrtSession.hpp index 342a4a91..004a61a1 100644 --- a/srt/SrtSession.hpp +++ b/srt/SrtSession.hpp @@ -8,7 +8,7 @@ namespace SRT { using namespace toolkit; -class SrtSession : public UdpSession { +class SrtSession : public Session { public: SrtSession(const Socket::Ptr &sock); ~SrtSession() override; diff --git a/srt/SrtTransport.cpp b/srt/SrtTransport.cpp index ba688af1..7388f3a0 100644 --- a/srt/SrtTransport.cpp +++ b/srt/SrtTransport.cpp @@ -1,4 +1,6 @@ #include "Util/onceToken.h" +#include "Util/mini.h" + #include #include @@ -15,6 +17,13 @@ const std::string kPort = SRT_FIELD "port"; const std::string kLatencyMul = SRT_FIELD "latencyMul"; const std::string kPktBufSize = SRT_FIELD "pktBufSize"; +static onceToken token([]() { + mINI::Instance()[kTimeOutSec] = 5; + mINI::Instance()[kPort] = 9000; + mINI::Instance()[kLatencyMul] = 4; + mINI::Instance()[kPktBufSize] = 8192; +}); + static std::atomic s_srt_socket_id_generate { 125 }; //////////// SrtTransport ////////////////////////// SrtTransport::SrtTransport(const EventPoller::Ptr &poller) @@ -22,7 +31,7 @@ SrtTransport::SrtTransport(const EventPoller::Ptr &poller) _start_timestamp = SteadyClock::now(); _socket_id = s_srt_socket_id_generate.fetch_add(1); _pkt_recv_rate_context = std::make_shared(_start_timestamp); - _recv_rate_context = std::make_shared(_start_timestamp); + //_recv_rate_context = std::make_shared(_start_timestamp); _estimated_link_capacity_context = std::make_shared(_start_timestamp); } @@ -60,7 +69,29 @@ void SrtTransport::switchToOtherTransport(uint8_t *buf, int len, uint32_t socket } } +void SrtTransport::createTimerForCheckAlive(){ + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + auto timeoutSec = getTimeOutSec(); + _timer = std::make_shared( + timeoutSec/ 2, + [weak_self,timeoutSec]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + if (strong_self->_alive_ticker.elapsedTime() > timeoutSec * 1000) { + strong_self->onShutdown(SockException(Err_timeout, "接收srt数据超时")); + } + return true; + }, + getPoller()); +} + void SrtTransport::inputSockData(uint8_t *buf, int len, struct sockaddr_storage *addr) { + _alive_ticker.resetTime(); + if(!_timer){ + createTimerForCheckAlive(); + } using srt_control_handler = void (SrtTransport::*)(uint8_t * buf, int len, struct sockaddr_storage *addr); static std::unordered_map s_control_functions; static onceToken token([]() { @@ -83,11 +114,11 @@ void SrtTransport::inputSockData(uint8_t *buf, int len, struct sockaddr_storage if(_handleshake_timer){ _handleshake_timer.reset(); } - _pkt_recv_rate_context->inputPacket(_now); - _estimated_link_capacity_context->inputPacket(_now); - _recv_rate_context->inputPacket(_now, len); + _pkt_recv_rate_context->inputPacket(_now,len+UDP_HDR_SIZE); + //_recv_rate_context->inputPacket(_now, len); handleDataPacket(buf, len, addr); + checkAndSendAckNak(); } else { WarnL<<"DataPacket switch to other transport: "<inputPacket(_now); - _estimated_link_capacity_context->inputPacket(_now); - _recv_rate_context->inputPacket(_now, len); + + //_pkt_recv_rate_context->inputPacket(_now,len); + //_estimated_link_capacity_context->inputPacket(_now); + //_recv_rate_context->inputPacket(_now, len); auto it = s_control_functions.find(type); if (it == s_control_functions.end()) { @@ -113,6 +145,9 @@ void SrtTransport::inputSockData(uint8_t *buf, int len, struct sockaddr_storage } else { (this->*(it->second))(buf, len, addr); } + if(_is_handleshake_finished && isPusher()){ + checkAndSendAckNak(); + } } else { // not reach WarnL << "not reach this"; @@ -152,6 +187,7 @@ void SrtTransport::handleHandshakeInduction(HandshakePacket &pkt, struct sockadd _mtu = pkt.mtu; _last_pkt_seq = _init_seq_number - 1; + _estimated_link_capacity_context->setLastSeq(_last_pkt_seq); _peer_socket_id = pkt.srt_socket_id; HandshakePacket::Ptr res = std::make_shared(); @@ -173,8 +209,7 @@ void SrtTransport::handleHandshakeInduction(HandshakePacket &pkt, struct sockadd registerSelfHandshake(); sendControlPacket(res, true); - - _handleshake_timer = std::make_shared(0.02,[this]()->bool{ + _handleshake_timer = std::make_shared(0.2,[this]()->bool{ sendControlPacket(_handleshake_res, true); return true; },getPoller()); @@ -266,7 +301,7 @@ void SrtTransport::handleHandshakeConclusion(HandshakePacket &pkt, struct sockad return; } - _last_ack_pkt_seq_num = _init_seq_number; + _last_ack_pkt_seq = _init_seq_number; } void SrtTransport::handleHandshake(uint8_t *buf, int len, struct sockaddr_storage *addr) { @@ -368,12 +403,13 @@ void SrtTransport::handleDropReq(uint8_t *buf, int len, struct sockaddr_storage std::list list; // TraceL<<"drop "<drop(pkt.first_pkt_seq_num, pkt.last_pkt_seq_num, list); + //checkAndSendAckNak(); if (list.empty()) { return; } - uint32_t max_seq = 0; + // uint32_t max_seq = 0; for (auto& data : list) { - max_seq = data->packet_seq_number; + // max_seq = data->packet_seq_number; if (_last_pkt_seq + 1 != data->packet_seq_number) { TraceL << "pkt lost " << _last_pkt_seq + 1 << "->" << data->packet_seq_number; } @@ -392,7 +428,8 @@ void SrtTransport::handleDropReq(uint8_t *buf, int len, struct sockaddr_storage // TraceL << "check lost send nack"; } */ - +} +void SrtTransport::checkAndSendAckNak(){ auto nak_interval = (_rtt + _rtt_variance * 4) / 2; if (nak_interval <= 20 * 1000) { nak_interval = 20 * 1000; @@ -409,7 +446,13 @@ void SrtTransport::handleDropReq(uint8_t *buf, int len, struct sockaddr_storage _light_ack_pkt_count = 0; _ack_ticker.resetTime(_now); // send a ack per 10 ms for receiver - sendACKPacket(); + if(_last_ack_pkt_seq != _recv_buf->getExpectedSeq()){ + //TraceL<<"send a ack packet"; + sendACKPacket(); + }else{ + //TraceL<<" ignore repeate ack packet"; + } + } else { if (_light_ack_pkt_count >= 64) { // for high bitrate stream send light ack @@ -421,7 +464,6 @@ void SrtTransport::handleDropReq(uint8_t *buf, int len, struct sockaddr_storage } _light_ack_pkt_count++; } - void SrtTransport::handleUserDefinedType(uint8_t *buf, int len, struct sockaddr_storage *addr) { TraceL; } @@ -431,12 +473,34 @@ void SrtTransport::handleACKACK(uint8_t *buf, int len, struct sockaddr_storage * ACKACKPacket::Ptr pkt = std::make_shared(); pkt->loadFromData(buf, len); - uint32_t rtt = DurationCountMicroseconds(_now - _ack_send_timestamp[pkt->ack_number]); - _rtt_variance = (3 * _rtt_variance + abs((long)_rtt - (long)rtt)) / 4; - _rtt = (7 * rtt + _rtt) / 8; + if(_ack_send_timestamp.find(pkt->ack_number)!=_ack_send_timestamp.end()){ + uint32_t rtt = DurationCountMicroseconds(_now - _ack_send_timestamp[pkt->ack_number]); + _rtt_variance = (3 * _rtt_variance + abs((long)_rtt - (long)rtt)) / 4; + _rtt = (7 * rtt + _rtt) / 8; + // TraceL<<" rtt:"<<_rtt<<" rtt variance:"<<_rtt_variance; + _ack_send_timestamp.erase(pkt->ack_number); - // TraceL<<" rtt:"<<_rtt<<" rtt variance:"<<_rtt_variance; - _ack_send_timestamp.erase(pkt->ack_number); + if(_last_recv_ackack_seq_num < pkt->ack_number){ + _last_recv_ackack_seq_num = pkt->ack_number; + }else{ + if((_last_recv_ackack_seq_num-pkt->ack_number)>(MAX_TS>>1)){ + _last_recv_ackack_seq_num = pkt->ack_number; + } + } + + if(_ack_send_timestamp.size()>1000){ + // clear data + for(auto it = _ack_send_timestamp.begin(); it != _ack_send_timestamp.end();){ + if(DurationCountMicroseconds(_now-it->second)>5e6){ + // 超过五秒没有ackack 丢弃 + it = _ack_send_timestamp.erase(it); + }else{ + it++; + } + } + } + + } } void SrtTransport::handlePeerError(uint8_t *buf, int len, struct sockaddr_storage *addr) { @@ -444,6 +508,8 @@ void SrtTransport::handlePeerError(uint8_t *buf, int len, struct sockaddr_storag } void SrtTransport::sendACKPacket() { + uint32_t recv_rate = 0; + ACKPacket::Ptr pkt = std::make_shared(); pkt->dst_socket_id = _peer_socket_id; pkt->timestamp = DurationCountMicroseconds(_now - _start_timestamp); @@ -452,12 +518,23 @@ void SrtTransport::sendACKPacket() { pkt->rtt = _rtt; pkt->rtt_variance = _rtt_variance; pkt->available_buf_size = _recv_buf->getAvailableBufferSize(); - pkt->pkt_recv_rate = _pkt_recv_rate_context->getPacketRecvRate(); + pkt->pkt_recv_rate = _pkt_recv_rate_context->getPacketRecvRate(recv_rate); pkt->estimated_link_capacity = _estimated_link_capacity_context->getEstimatedLinkCapacity(); - pkt->recv_rate = _recv_rate_context->getRecvRate(); + pkt->recv_rate = recv_rate; + if(0){ + TraceL<pkt_recv_rate<<" pkt/s "<estimated_link_capacity<<" pkt/s (cap) "<available_buf_size<<" available buf"; + //TraceL<<_pkt_recv_rate_context->dump(); + //TraceL<<"recv estimated:"; + //TraceL<< _pkt_recv_rate_context->dump(); + //TraceL<<"recv queue:"; + //TraceL<<_recv_buf->dump(); + } + if(pkt->available_buf_size<2){ + pkt->available_buf_size = 2; + } pkt->storeToData(); _ack_send_timestamp[pkt->ack_number] = _now; - _last_ack_pkt_seq_num = pkt->last_ack_pkt_seq_number; + _last_ack_pkt_seq = pkt->last_ack_pkt_seq_number; sendControlPacket(pkt, true); // TraceL<<"send ack "<dump(); // TraceL<<_recv_buf->dump(); @@ -477,7 +554,7 @@ void SrtTransport::sendLightACKPacket() { pkt->estimated_link_capacity = 0; pkt->recv_rate = 0; pkt->storeToData(); - _last_ack_pkt_seq_num = pkt->last_ack_pkt_seq_number; + _last_ack_pkt_seq = pkt->last_ack_pkt_seq_number; sendControlPacket(pkt, true); TraceL << "send ack " << pkt->dump(); } @@ -534,6 +611,8 @@ void SrtTransport::handleDataPacket(uint8_t *buf, int len, struct sockaddr_stora DataPacket::Ptr pkt = std::make_shared(); pkt->loadFromData(buf, len); + _estimated_link_capacity_context->inputPacket(_now,pkt); + std::list list; //TraceL<<" seq="<< pkt->packet_seq_number<<" ts="<timestamp<<" size="<payloadSize()<<\ //" PP="<<(int)pkt->PP<<" O="<<(int)pkt->O<<" kK="<<(int)pkt->KK<<" R="<<(int)pkt->R; @@ -541,9 +620,9 @@ void SrtTransport::handleDataPacket(uint8_t *buf, int len, struct sockaddr_stora if (list.empty()) { // when no data ok send nack to sender immediately } else { - uint32_t last_seq; + // uint32_t last_seq; for (auto& data : list) { - last_seq = data->packet_seq_number; + // last_seq = data->packet_seq_number; if (_last_pkt_seq + 1 != data->packet_seq_number) { TraceL << "pkt lost " << _last_pkt_seq + 1 << "->" << data->packet_seq_number; } @@ -563,7 +642,7 @@ void SrtTransport::handleDataPacket(uint8_t *buf, int len, struct sockaddr_stora sendNAKPacket(lost); } */ - + /* auto nak_interval = (_rtt + _rtt_variance * 4) / 2; if (nak_interval <= 20 * 1000) { nak_interval = 20 * 1000; @@ -596,6 +675,7 @@ void SrtTransport::handleDataPacket(uint8_t *buf, int len, struct sockaddr_stora _light_ack_pkt_count = 0; } _light_ack_pkt_count++; + */ // bufCheckInterval(); } diff --git a/srt/SrtTransport.hpp b/srt/SrtTransport.hpp index 9d0c186c..094ba1b3 100644 --- a/srt/SrtTransport.hpp +++ b/srt/SrtTransport.hpp @@ -53,10 +53,13 @@ protected: virtual bool isPusher() { return true; }; virtual void onSRTData(DataPacket::Ptr pkt) {}; virtual void onShutdown(const SockException &ex); - virtual void onHandShakeFinished(std::string &streamid, struct sockaddr_storage *addr) {}; + virtual void onHandShakeFinished(std::string &streamid, struct sockaddr_storage *addr) { + _is_handleshake_finished = true; + }; virtual void sendPacket(Buffer::Ptr pkt, bool flush = true); virtual int getLatencyMul() { return 4; }; virtual int getPktBufSize() { return 8192; }; + virtual float getTimeOutSec(){return 5.0;}; private: void registerSelf(); @@ -88,6 +91,10 @@ private: size_t getPayloadSize(); + void createTimerForCheckAlive(); + + void checkAndSendAckNak(); + protected: void sendDataPacket(DataPacket::Ptr pkt, char *buf, int len, bool flush = false); void sendControlPacket(ControlPacket::Ptr pkt, bool flush = true); @@ -126,7 +133,8 @@ private: uint32_t _rtt_variance = 50 * 1000; uint32_t _light_ack_pkt_count = 0; uint32_t _ack_number_count = 0; - uint32_t _last_ack_pkt_seq_num = 0; + uint32_t _last_ack_pkt_seq = 0; + uint32_t _last_recv_ackack_seq_num = 0; uint32_t _last_pkt_seq = 0; UTicker _ack_ticker; @@ -134,7 +142,7 @@ private: std::shared_ptr _pkt_recv_rate_context; std::shared_ptr _estimated_link_capacity_context; - std::shared_ptr _recv_rate_context; + //std::shared_ptr _recv_rate_context; UTicker _nak_ticker; @@ -144,6 +152,13 @@ private: Timer::Ptr _handleshake_timer; ResourcePool _packet_pool; + + //检测超时的定时器 + Timer::Ptr _timer; + //刷新计时器 + Ticker _alive_ticker; + + bool _is_handleshake_finished = false; }; class SrtTransportManager { diff --git a/srt/SrtTransportImp.cpp b/srt/SrtTransportImp.cpp index 036aa0b7..8f0694b2 100644 --- a/srt/SrtTransportImp.cpp +++ b/srt/SrtTransportImp.cpp @@ -10,8 +10,7 @@ SrtTransportImp::SrtTransportImp(const EventPoller::Ptr &poller) SrtTransportImp::~SrtTransportImp() { InfoP(this); uint64_t duration = _alive_ticker.createdTime() / 1000; - WarnP(this) << (_is_pusher ? "srt 推流器(" : "srt 播放器(") << _media_info._vhost << "/" << _media_info._app << "/" - << _media_info._streamid << ")断开,耗时(s):" << duration; + WarnP(this) << (_is_pusher ? "srt 推流器(" : "srt 播放器(") << _media_info.shortUrl() << ")断开,耗时(s):" << duration; // 流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); @@ -23,6 +22,7 @@ SrtTransportImp::~SrtTransportImp() { } void SrtTransportImp::onHandShakeFinished(std::string &streamid, struct sockaddr_storage *addr) { + SrtTransport::onHandShakeFinished(streamid,addr); // TODO parse stream id like this zlmediakit.com/live/test?token=1213444&type=push if (!_addr) { _addr.reset(new sockaddr_storage(*((sockaddr_storage *)addr))); @@ -89,8 +89,7 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) { _media_info._app = app; _media_info._streamid = stream_name; - TraceL << " vhost=" << _media_info._vhost << " app=" << _media_info._app << " streamid=" << _media_info._streamid - << " params=" << _media_info._param_strs; + TraceL << " mediainfo=" << _media_info.shortUrl() << " params=" << _media_info._param_strs; return true; } @@ -102,21 +101,21 @@ void SrtTransportImp::onSRTData(DataPacket::Ptr pkt) { } if (_decoder) { _decoder->input(reinterpret_cast(pkt->payloadData()), pkt->payloadSize()); + //TraceL<<" size "<payloadSize(); } else { WarnP(this) << " not reach this"; } } void SrtTransportImp::onShutdown(const SockException &ex) { + if (_decoder) { + _decoder->flush(); + } SrtTransport::onShutdown(ex); } -bool SrtTransportImp::close(mediakit::MediaSource &sender, bool force) { - if (!force && totalReaderCount(sender)) { - return false; - } - std::string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" - << sender.getApp() << "/" << sender.getId() << " " << force; +bool SrtTransportImp::close(mediakit::MediaSource &sender) { + std::string err = StrPrinter << "close media: " << sender.getUrl(); weak_ptr weak_self = static_pointer_cast(shared_from_this()); getPoller()->async([weak_self, err]() { auto strong_self = weak_self.lock(); @@ -346,6 +345,15 @@ int SrtTransportImp::getLatencyMul() { return latencyMul; } +float SrtTransportImp::getTimeOutSec() { + GET_CONFIG(float, timeOutSec, kTimeOutSec); + if (timeOutSec <= 0) { + WarnL << "config srt " << kTimeOutSec << " not vaild"; + return 5.0; + } + return timeOutSec; +} + int SrtTransportImp::getPktBufSize() { // kPktBufSize GET_CONFIG(int, pktBufSize, kPktBufSize); diff --git a/srt/SrtTransportImp.hpp b/srt/SrtTransportImp.hpp index 987dee8e..71478605 100644 --- a/srt/SrtTransportImp.hpp +++ b/srt/SrtTransportImp.hpp @@ -37,6 +37,7 @@ protected: ///////SrtTransport override/////// int getLatencyMul() override; int getPktBufSize() override; + float getTimeOutSec() override; void onSRTData(DataPacket::Ptr pkt) override; void onShutdown(const SockException &ex) override; void onHandShakeFinished(std::string &streamid, struct sockaddr_storage *addr) override; @@ -50,7 +51,7 @@ protected: ///////MediaSourceEvent override/////// // 关闭 - bool close(mediakit::MediaSource &sender, bool force) override; + bool close(mediakit::MediaSource &sender) override; // 获取媒体源类型 mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override; // 获取媒体源url或者文件路径 diff --git a/srt/Statistic.cpp b/srt/Statistic.cpp index 446fc6fe..139e39b8 100644 --- a/srt/Statistic.cpp +++ b/srt/Statistic.cpp @@ -1,81 +1,211 @@ #include - +#include #include "Statistic.hpp" namespace SRT { -void PacketRecvRateContext::inputPacket(TimePoint &ts) { - if (_pkt_map.size() > 100) { - _pkt_map.erase(_pkt_map.begin()); +PacketRecvRateContext::PacketRecvRateContext(TimePoint start) + : _last_arrive_time(start) { + for (size_t i = 0; i < SIZE; i++) { + _ts_arr[i] = 1000000; + _size_arr[i] = SRT_MAX_PAYLOAD_SIZE; } - auto tmp = DurationCountMicroseconds(ts - _start); - _pkt_map.emplace(tmp, tmp); + _cur_idx = 0; +}; + +void PacketRecvRateContext::inputPacket(TimePoint &ts,size_t len) { + auto tmp = DurationCountMicroseconds(ts - _last_arrive_time); + _ts_arr[_cur_idx] = tmp; + _size_arr[_cur_idx] = len; + _cur_idx = (1+_cur_idx)%SIZE; + _last_arrive_time = ts; } -uint32_t PacketRecvRateContext::getPacketRecvRate() { - if (_pkt_map.size() < 2) { - return 50000; - } - int64_t dur = 1000; - for (auto it = _pkt_map.begin(); it != _pkt_map.end(); ++it) { - auto next = it; - ++next; - if (next == _pkt_map.end()) { - break; - } +uint32_t PacketRecvRateContext::getPacketRecvRate(uint32_t &bytesps) { + int64_t tmp_arry[SIZE]; + std::copy(_ts_arr, _ts_arr + SIZE, tmp_arry); + std::nth_element(tmp_arry, tmp_arry + (SIZE / 2), tmp_arry + SIZE); + int64_t median = tmp_arry[SIZE / 2]; - if ((next->first - it->first) < dur) { - dur = next->first - it->first; + unsigned count = 0; + int sum = 0; + int64_t upper = median << 3; + int64_t lower = median >> 3; + + int64_t min = median; + int64_t min_size = 0; + + bytesps = 0; + size_t bytes = 0; + const size_t *bp = _size_arr; + // median filtering + const int64_t *p = _ts_arr; + for (int i = 0, n = SIZE; i < n; ++i) { + if ((*p < upper) && (*p > lower)) { + ++count; // packet counter + sum += *p; // usec counter + bytes += *bp; // byte counter } + if(*p < min){ + min = *p; + min_size = *bp; + } + ++p; // advance packet pointer + ++bp; // advance bytes pointer } - double rate = 1e6 / (double)dur; - if (rate <= 1000) { - return 50000; + uint32_t max_ret = (uint32_t)ceil(1e6/min); + uint32_t max_byteps = (uint32_t)ceil(1e6*min_size/min); + + if(count>(SIZE>>1)){ + bytesps = (uint32_t)ceil(1000000.0 / (double(sum) / double(bytes))); + auto ret = (uint32_t)ceil(1000000.0 / (double(sum) / double(count))); + //bytesps = max_byteps; + + return max_ret; + }else{ + //TraceL< 16) { - _pkt_map.erase(_pkt_map.begin()); +std::string PacketRecvRateContext::dump(){ + _StrPrinter printer; + printer <<"dur array : "; + for (size_t i = 0; i < SIZE; i++) + { + printer<<_ts_arr[i]<<" "; } - auto tmp = DurationCountMicroseconds(ts - _start); - _pkt_map.emplace(tmp, tmp); + printer <<"\r\n"; + + printer <<"size array : "; + for (size_t i = 0; i < SIZE; i++) + { + printer<<_size_arr[i]<<" "; + } + printer <<"\r\n"; + + return std::move(printer); +} +EstimatedLinkCapacityContext::EstimatedLinkCapacityContext(TimePoint start) : _start(start) { + for (size_t i = 0; i < SIZE; i++) { + _dur_probe_arr[i] = 1000; + } + _cur_idx = 0; +}; +void EstimatedLinkCapacityContext::inputPacket(TimePoint &ts,DataPacket::Ptr& pkt) { + uint32_t seq = pkt->packet_seq_number; + auto diff = seqCmp(seq,_last_seq); + const bool retransmitted = pkt->R == 1; + const bool unordered = diff<=0; + uint32_t one = seq&0xf; + if(one == 0){ + probe1Arrival(ts,pkt,unordered || retransmitted); + } + if(diff>0){ + _last_seq = seq; + } + if(unordered || retransmitted){ + return; + } + if(one == 1){ + probe2Arrival(ts,pkt); + } +} + +/// Record the arrival time of the first probing packet. +void EstimatedLinkCapacityContext::probe1Arrival(TimePoint &ts, const DataPacket::Ptr &pkt, bool unordered) { + if (unordered && pkt->packet_seq_number == _probe1_seq) { + // Reset the starting probe into "undefined", when + // a packet has come as retransmitted before the + // measurement at arrival of 17th could be taken. + _probe1_seq = SEQ_NONE; + return; + } + + _ts_probe_time = ts; + _probe1_seq = pkt->packet_seq_number; // Record the sequence where 16th packet probe was taken +} + +/// Record the arrival time of the second probing packet and the interval between packet pairs. + +void EstimatedLinkCapacityContext::probe2Arrival(TimePoint &ts, const DataPacket::Ptr &pkt) { + // Reject probes that don't refer to the very next packet + // towards the one that was lately notified by probe1Arrival. + // Otherwise the result can be stupid. + + // Simply, in case when this wasn't called exactly for the + // expected packet pair, behave as if the 17th packet was lost. + + // no start point yet (or was reset) OR not very next packet + if (_probe1_seq == SEQ_NONE || incSeq(_probe1_seq) != pkt->packet_seq_number) + return; + + // Reset the starting probe to prevent checking if the + // measurement was already taken. + _probe1_seq = SEQ_NONE; + + // record the probing packets interval + // Adjust the time for what a complete packet would have take + const int64_t timediff = DurationCountMicroseconds(ts - _ts_probe_time); + const int64_t timediff_times_pl_size = timediff * SRT_MAX_PAYLOAD_SIZE; + + // Let's take it simpler than it is coded here: + // (stating that a packet has never zero size) + // + // probe_case = (now - previous_packet_time) * SRT_MAX_PAYLOAD_SIZE / pktsz; + // + // Meaning: if the packet is fully packed, probe_case = timediff. + // Otherwise the timediff will be "converted" to a time that a fully packed packet "would take", + // provided the arrival time is proportional to the payload size and skipping + // the ETH+IP+UDP+SRT header part elliminates the constant packet delivery time influence. + // + const size_t pktsz = pkt->payloadSize(); + _dur_probe_arr[_cur_idx] = pktsz ? int64_t(timediff_times_pl_size / pktsz) : int64_t(timediff); + + // the window is logically circular + _cur_idx = (_cur_idx + 1) % SIZE; } uint32_t EstimatedLinkCapacityContext::getEstimatedLinkCapacity() { - decltype(_pkt_map.begin()) next; - std::vector tmp; + int64_t tmp[SIZE]; + std::copy(_dur_probe_arr, _dur_probe_arr + SIZE , tmp); + std::nth_element(tmp, tmp + (SIZE / 2), tmp + SIZE); + int64_t median = tmp[SIZE / 2]; - for (auto it = _pkt_map.begin(); it != _pkt_map.end(); ++it) { - next = it; - ++next; - if (next != _pkt_map.end()) { - tmp.push_back(next->first - it->first); - } else { - break; - } - } - std::sort(tmp.begin(), tmp.end()); - if (tmp.empty()) { - return 1000; - } + int64_t count = 1; + int64_t sum = median; + int64_t upper = median << 3; // median*8 + int64_t lower = median >> 3; // median/8 - if (tmp.size() < 16) { - return 1000; - } + // median filtering + const int64_t* p = _dur_probe_arr; + for (int i = 0, n = SIZE; i < n; ++ i) + { + if ((*p < upper) && (*p > lower)) + { + ++ count; + sum += *p; + } + ++ p; + } - double dur = tmp[0] / 1e6; - return (uint32_t)(1.0 / dur); + return (uint32_t)ceil(1000000.0 / (double(sum) / double(count))); } +/* void RecvRateContext::inputPacket(TimePoint &ts, size_t size) { if (_pkt_map.size() > 100) { _pkt_map.erase(_pkt_map.begin()); } auto tmp = DurationCountMicroseconds(ts - _start); - _pkt_map.emplace(tmp, tmp); + _pkt_map.emplace(tmp, size); } uint32_t RecvRateContext::getRecvRate() { @@ -94,5 +224,5 @@ uint32_t RecvRateContext::getRecvRate() { double rate = (double)bytes / dur; return (uint32_t)rate; } - +*/ } // namespace SRT \ No newline at end of file diff --git a/srt/Statistic.hpp b/srt/Statistic.hpp index d2a5036e..245e55e4 100644 --- a/srt/Statistic.hpp +++ b/srt/Statistic.hpp @@ -6,32 +6,46 @@ #include "Packet.hpp" namespace SRT { - class PacketRecvRateContext { public: - PacketRecvRateContext(TimePoint start) - : _start(start) {}; + PacketRecvRateContext(TimePoint start); ~PacketRecvRateContext() = default; - void inputPacket(TimePoint &ts); - uint32_t getPacketRecvRate(); - + void inputPacket(TimePoint &ts,size_t len = 0); + uint32_t getPacketRecvRate(uint32_t& bytesps); + std::string dump(); + static const int SIZE = 16; private: - TimePoint _start; - std::map _pkt_map; + TimePoint _last_arrive_time; + int64_t _ts_arr[SIZE]; + size_t _size_arr[SIZE]; + size_t _cur_idx; + //std::map _pkt_map; }; class EstimatedLinkCapacityContext { public: - EstimatedLinkCapacityContext(TimePoint start) : _start(start) {}; + EstimatedLinkCapacityContext(TimePoint start); ~EstimatedLinkCapacityContext() = default; - void inputPacket(TimePoint &ts); + void setLastSeq(uint32_t seq){ + _last_seq = seq; + } + void inputPacket(TimePoint &ts,DataPacket::Ptr& pkt); uint32_t getEstimatedLinkCapacity(); - + static const int SIZE = 64; +private: + void probe1Arrival(TimePoint &ts,const DataPacket::Ptr& pkt, bool unordered); + void probe2Arrival(TimePoint &ts,const DataPacket::Ptr& pkt); private: TimePoint _start; - std::map _pkt_map; + TimePoint _ts_probe_time; + int64_t _dur_probe_arr[SIZE]; + size_t _cur_idx; + uint32_t _last_seq = 0; + uint32_t _probe1_seq = SEQ_NONE; + //std::map _pkt_map; }; +/* class RecvRateContext { public: RecvRateContext(TimePoint start) @@ -44,6 +58,6 @@ private: TimePoint _start; std::map _pkt_map; }; - +*/ } // namespace SRT #endif // ZLMEDIAKIT_SRT_STATISTIC_H \ No newline at end of file diff --git a/tests/test_bench_proxy.cpp b/tests/test_bench_proxy.cpp index 793b4107..c0f1fceb 100644 --- a/tests/test_bench_proxy.cpp +++ b/tests/test_bench_proxy.cpp @@ -125,11 +125,11 @@ int main(int argc, char *argv[]) { //设置合并写 mINI::Instance()[General::kMergeWriteMS] = merge_ms; - mINI::Instance()[General::kRtspDemand] = demand; - mINI::Instance()[General::kRtmpDemand] = demand; - mINI::Instance()[General::kHlsDemand] = demand; - mINI::Instance()[General::kTSDemand] = demand; - mINI::Instance()[General::kFMP4Demand] = demand; + mINI::Instance()[Protocol::kRtspDemand] = demand; + mINI::Instance()[Protocol::kRtmpDemand] = demand; + mINI::Instance()[Protocol::kHlsDemand] = demand; + mINI::Instance()[Protocol::kTSDemand] = demand; + mINI::Instance()[Protocol::kFMP4Demand] = demand; map proxyMap; ProtocolOption option; diff --git a/tests/test_pusherMp4.cpp b/tests/test_pusherMp4.cpp index c72f52b1..a29d74f3 100644 --- a/tests/test_pusherMp4.cpp +++ b/tests/test_pusherMp4.cpp @@ -105,11 +105,11 @@ int domain(const string &filePath, const string &pushUrl) { Logger::Instance().setWriter(std::make_shared()); //循环点播mp4文件 mINI::Instance()[Record::kFileRepeat] = 1; - mINI::Instance()[General::kHlsDemand] = 1; - mINI::Instance()[General::kTSDemand] = 1; - mINI::Instance()[General::kFMP4Demand] = 1; - //mINI::Instance()[General::kRtspDemand] = 1; - //mINI::Instance()[General::kRtmpDemand] = 1; + mINI::Instance()[Protocol::kHlsDemand] = 1; + mINI::Instance()[Protocol::kTSDemand] = 1; + mINI::Instance()[Protocol::kFMP4Demand] = 1; + //mINI::Instance()[Protocol::kRtspDemand] = 1; + //mINI::Instance()[Protocol::kRtmpDemand] = 1; auto poller = EventPollerPool::Instance().getPoller(); //vhost/app/stream可以随便自己填,现在不限制app应用名了 diff --git a/tests/test_rtp.cpp b/tests/test_rtp.cpp index 441d1563..bafd793c 100644 --- a/tests/test_rtp.cpp +++ b/tests/test_rtp.cpp @@ -42,6 +42,8 @@ static bool loadFile(const char *path){ addr.ss_family = AF_INET; auto sock = Socket::createSocket(); size_t total_size = 0; + RtpProcess::Ptr process; + uint32_t ssrc = 0; while (true) { if (2 != fread(&len, 1, 2, fp)) { WarnL; @@ -58,9 +60,24 @@ static bool loadFile(const char *path){ break; } total_size += len; - uint64_t timeStamp; + uint64_t timeStamp = 0; + + if (!process) { + if (!RtpSelector::getSSRC(rtp, len, ssrc)) { + WarnL << "get ssrc from rtp failed:" << len; + return false; + } + process = RtpSelector::Instance().getProcess(printSSRC(ssrc), true); + } + if (process) { + try { + process->inputRtp(true, sock, rtp, len, (struct sockaddr *)&addr, &timeStamp); + } catch (...) { + RtpSelector::Instance().delProcess(printSSRC(ssrc), process.get()); + throw; + } + } - RtpSelector::Instance().inputRtp(sock, rtp, len, (struct sockaddr *)&addr, &timeStamp); auto diff = timeStamp - timeStamp_last; if (diff > 0 && diff < 500) { usleep(diff * 1000); diff --git a/tests/test_server.cpp b/tests/test_server.cpp index da0d7bd1..7d073b5e 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -83,33 +83,29 @@ onceToken token1([](){ } // namespace mediakit -#define REALM "realm_zlmedaikit" +#define REALM "realm_zlmediakit" static map s_mapFlvRecorder; static mutex s_mtxFlvRecorder; void initEventListener() { static onceToken s_token([]() { //监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问 - NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnGetRtspRealm, - [](BroadcastOnGetRtspRealmArgs) { - DebugL << "RTSP是否需要鉴权事件:" << args._schema << " " << args._vhost << " " - << args._app << " " << args._streamid << " " - << args._param_strs; - if (string("1") == args._streamid) { - // live/1需要认证 - //该流需要认证,并且设置realm - invoker(REALM); - } else { - //有时我们要查询redis或数据库来判断该流是否需要认证,通过invoker的方式可以做到完全异步 - //该流我们不需要认证 - invoker(""); - } - }); + NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) { + DebugL << "RTSP是否需要鉴权事件:" << args.getUrl() << " " << args._param_strs; + if (string("1") == args._streamid) { + // live/1需要认证 + //该流需要认证,并且设置realm + invoker(REALM); + } else { + //有时我们要查询redis或数据库来判断该流是否需要认证,通过invoker的方式可以做到完全异步 + //该流我们不需要认证 + invoker(""); + } + }); //监听kBroadcastOnRtspAuth事件返回正确的rtsp鉴权用户密码 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnRtspAuth, [](BroadcastOnRtspAuthArgs) { - DebugL << "RTSP播放鉴权:" << args._schema << " " << args._vhost << " " << args._app << " " << args._streamid - << " " << args._param_strs; + DebugL << "RTSP播放鉴权:" << args.getUrl() << " " << args._param_strs; DebugL << "RTSP用户:" << user_name << (must_no_encrypt ? " Base64" : " MD5") << " 方式登录"; string user = user_name; //假设我们异步读取数据库 @@ -139,16 +135,14 @@ void initEventListener() { //监听rtsp/rtmp推流事件,返回结果告知是否有推流权限 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) { - DebugL << "推流鉴权:" << args._schema << " " << args._vhost << " " << args._app << " " << args._streamid << " " - << args._param_strs; + DebugL << "推流鉴权:" << args.getUrl() << " " << args._param_strs; invoker("", ProtocolOption());//鉴权成功 //invoker("this is auth failed message");//鉴权失败 }); //监听rtsp/rtsps/rtmp/http-flv播放事件,返回结果告知是否有播放权限(rtsp通过kBroadcastOnRtspAuth或此事件都可以实现鉴权) NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) { - DebugL << "播放鉴权:" << args._schema << " " << args._vhost << " " << args._app << " " << args._streamid << " " - << args._param_strs; + DebugL << "播放鉴权:" << args.getUrl() << " " << args._param_strs; invoker("");//鉴权成功 //invoker("this is auth failed message");//鉴权失败 }); @@ -164,42 +158,38 @@ void initEventListener() { NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { if (sender.getSchema() == RTMP_SCHEMA && sender.getApp() == "live") { lock_guard lck(s_mtxFlvRecorder); + auto key = sender.shortUrl(); if (bRegist) { - DebugL << "开始录制RTMP:" << sender.getSchema() << " " << sender.getVhost() << " " << sender.getApp() << " " << sender.getId(); + DebugL << "开始录制RTMP:" << sender.getUrl(); GET_CONFIG(string, http_root, Http::kRootPath); - auto path = - http_root + "/" + sender.getVhost() + "/" + sender.getApp() + "/" + sender.getId() + "_" + to_string(time(NULL)) + ".flv"; + auto path = http_root + "/" + key + "_" + to_string(time(NULL)) + ".flv"; FlvRecorder::Ptr recorder(new FlvRecorder); try { recorder->startRecord(EventPollerPool::Instance().getPoller(), dynamic_pointer_cast(sender.shared_from_this()), path); - s_mapFlvRecorder[sender.getVhost() + "/" + sender.getApp() + "/" + sender.getId()] = recorder; + s_mapFlvRecorder[key] = recorder; } catch (std::exception &ex) { WarnL << ex.what(); } } else { - s_mapFlvRecorder.erase(sender.getVhost() + "/" + sender.getApp() + "/" + sender.getId()); + s_mapFlvRecorder.erase(key); } } }); //监听播放失败(未找到特定的流)事件 - NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastNotFoundStream, - [](BroadcastNotFoundStreamArgs) { - /** - * 你可以在这个事件触发时再去拉流,这样就可以实现按需拉流 - * 拉流成功后,ZLMediaKit会把其立即转发给播放器(最大等待时间约为5秒,如果5秒都未拉流成功,播放器会播放失败) - */ - DebugL << "未找到流事件:" << args._schema << " " << args._vhost << " " - << args._app << " " << args._streamid << " " - << args._param_strs; - }); + NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastNotFoundStream, [](BroadcastNotFoundStreamArgs) { + /** + * 你可以在这个事件触发时再去拉流,这样就可以实现按需拉流 + * 拉流成功后,ZLMediaKit会把其立即转发给播放器(最大等待时间约为5秒,如果5秒都未拉流成功,播放器会播放失败) + */ + DebugL << "未找到流事件:" << args.getUrl() << " " << args._param_strs; + }); //监听播放或推流结束时消耗流量事件 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) { - DebugL << "播放器(推流器)断开连接事件:" << args._schema << " " << args._vhost << " " << args._app << " " - << args._streamid << " " << args._param_strs + DebugL << "播放器(推流器)断开连接事件:" << args.getUrl() << " " << args._param_strs << "\r\n使用流量:" << totalBytes << " bytes,连接时长:" << totalDuration << "秒"; }); diff --git a/tests/test_wsServer.cpp b/tests/test_wsServer.cpp index f3ebda9d..90dd645e 100644 --- a/tests/test_wsServer.cpp +++ b/tests/test_wsServer.cpp @@ -22,9 +22,9 @@ using namespace mediakit; /** * 回显会话 */ -class EchoSession : public TcpSession { +class EchoSession : public Session { public: - EchoSession(const Socket::Ptr &pSock) : TcpSession(pSock){ + EchoSession(const Socket::Ptr &pSock) : Session(pSock){ DebugL; } virtual ~EchoSession(){ @@ -32,7 +32,7 @@ public: } void attachServer(const Server &server) override{ - DebugL << getIdentifier() << " " << TcpSession::getIdentifier(); + DebugL << getIdentifier() << " " << Session::getIdentifier(); } void onRecv(const Buffer::Ptr &buffer) override { //回显数据 @@ -49,9 +49,9 @@ public: }; -class EchoSessionWithUrl : public TcpSession { +class EchoSessionWithUrl : public Session { public: - EchoSessionWithUrl(const Socket::Ptr &pSock) : TcpSession(pSock){ + EchoSessionWithUrl(const Socket::Ptr &pSock) : Session(pSock){ DebugL; } virtual ~EchoSessionWithUrl(){ @@ -59,7 +59,7 @@ public: } void attachServer(const Server &server) override{ - DebugL << getIdentifier() << " " << TcpSession::getIdentifier(); + DebugL << getIdentifier() << " " << Session::getIdentifier(); } void onRecv(const Buffer::Ptr &buffer) override { //回显数据 @@ -80,13 +80,13 @@ public: * 此对象可以根据websocket 客户端访问的url选择创建不同的对象 */ struct EchoSessionCreator { - //返回的TcpSession必须派生于SendInterceptor,可以返回null(拒绝连接) - TcpSession::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) { + //返回的Session必须派生于SendInterceptor,可以返回null(拒绝连接) + Session::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) { // return nullptr; if (header.Url() == "/") { - return std::make_shared >(header, parent, pSock); + return std::make_shared >(header, parent, pSock); } - return std::make_shared >(header, parent, pSock); + return std::make_shared >(header, parent, pSock); } }; @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) { httpsSrv->start >(443);//默认443 TcpServer::Ptr httpSrvOld(new TcpServer()); - //兼容之前的代码(但是不支持根据url选择生成TcpSession类型) + //兼容之前的代码(但是不支持根据url选择生成Session类型) httpSrvOld->start >(8080); DebugL << "请打开网页:http://www.websocket-test.com/,进行测试"; diff --git a/version.h.ini b/version.h.ini index c806f63d..cb685ec2 100644 --- a/version.h.ini +++ b/version.h.ini @@ -2,6 +2,7 @@ #define __GIT_VERSION_H__ #define COMMIT_HASH "@COMMIT_HASH@" +#define COMMIT_TIME "@COMMIT_TIME@" #define BRANCH_NAME "@BRANCH_NAME@" #define BUILD_TIME "@BUILD_TIME@" diff --git a/webrtc/CMakeLists.txt b/webrtc/CMakeLists.txt index 251e4038..f2c4f65d 100644 --- a/webrtc/CMakeLists.txt +++ b/webrtc/CMakeLists.txt @@ -53,7 +53,7 @@ file(GLOB WEBRTC_SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) -add_library(webrtc ${WEBRTC_SRC_LIST}) +add_library(webrtc STATIC ${WEBRTC_SRC_LIST}) add_library(ZLMediaKit::WebRTC ALIAS webrtc) target_compile_definitions(webrtc # ENABLE_SCTP 暂时需要暴露 diff --git a/webrtc/Nack.cpp b/webrtc/Nack.cpp index 1ac2ff9b..63e615b5 100644 --- a/webrtc/Nack.cpp +++ b/webrtc/Nack.cpp @@ -12,7 +12,8 @@ using namespace std; using namespace toolkit; -using namespace mediakit; + +namespace mediakit { static constexpr uint32_t kMaxNackMS = 5 * 1000; static constexpr uint32_t kRtpCacheCheckInterval = 100; @@ -92,7 +93,6 @@ int64_t NackList::getRtpStamp(uint16_t seq) { return it->second->getStampMS(false); } - //////////////////////////////////////////////////////////////////////////////////////////////// void NackContext::received(uint16_t seq, bool is_rtx) { @@ -101,7 +101,7 @@ void NackContext::received(uint16_t seq, bool is_rtx) { } if (is_rtx || (seq < _last_max_seq && !(seq < 1024 && _last_max_seq > UINT16_MAX - 1024))) { //重传包或 - //seq回退,且非回环,那么这个应该是重传包 + // seq回退,且非回环,那么这个应该是重传包 onRtx(seq); return; } @@ -127,7 +127,7 @@ void NackContext::received(uint16_t seq, bool is_rtx) { _seq.clear(); _last_max_seq = max_seq; } else { - //seq不连续,有丢包 + // seq不连续,有丢包 if (min_seq == _last_max_seq + 1) { //前面部分seq是连续的,未丢包,移除之 eraseFrontSeq(); @@ -135,7 +135,7 @@ void NackContext::received(uint16_t seq, bool is_rtx) { //有丢包,丢包从_last_max_seq开始 auto nack_rtp_count = FCI_NACK::kBitSize; - if (max_seq > nack_rtp_count + _last_max_seq) { + if (max_seq > nack_rtp_count + _last_max_seq) { vector vec; vec.resize(FCI_NACK::kBitSize, false); for (size_t i = 0; i < nack_rtp_count; ++i) { @@ -170,7 +170,7 @@ void NackContext::eraseFrontSeq() { //前面部分seq是连续的,未丢包,移除之 for (auto it = _seq.begin(); it != _seq.end();) { if (*it != _last_max_seq + 1) { - //seq不连续,丢包了 + // seq不连续,丢包了 break; } _last_max_seq = *it; @@ -187,9 +187,9 @@ void NackContext::onRtx(uint16_t seq) { _nack_send_status.erase(it); if (rtt >= 0) { - //rtt不肯小于0 + // rtt不肯小于0 _rtt = rtt; - //InfoL << "rtt:" << rtt; + // InfoL << "rtt:" << rtt; } } @@ -230,7 +230,7 @@ uint64_t NackContext::reSendNack() { //更新nack发送时间戳 it->second.update_stamp = now; if (++(it->second.nack_count) == kNackMaxCount) { - //nack次数太多,移除之 + // nack次数太多,移除之 it = _nack_send_status.erase(it); continue; } @@ -269,3 +269,5 @@ uint64_t NackContext::reSendNack() { //重传间隔不得低于5ms return max(_rtt, 5); } + +} // namespace mediakit diff --git a/webrtc/Nack.h b/webrtc/Nack.h index 761c618d..2a02f311 100644 --- a/webrtc/Nack.h +++ b/webrtc/Nack.h @@ -14,38 +14,39 @@ #include "Rtsp/Rtsp.h" #include "Rtcp/RtcpFCI.h" +namespace mediakit { class NackList { public: NackList() = default; ~NackList() = default; - void pushBack(mediakit::RtpPacket::Ptr rtp); - void forEach(const mediakit::FCI_NACK &nack, const std::function &cb); + void pushBack(RtpPacket::Ptr rtp); + void forEach(const FCI_NACK &nack, const std::function &cb); private: void popFront(); uint32_t getCacheMS(); int64_t getRtpStamp(uint16_t seq); - mediakit::RtpPacket::Ptr *getRtp(uint16_t seq); + RtpPacket::Ptr *getRtp(uint16_t seq); private: uint32_t _cache_ms_check = 0; std::deque _nack_cache_seq; - std::unordered_map _nack_cache_pkt; + std::unordered_map _nack_cache_pkt; }; class NackContext { public: using Ptr = std::shared_ptr; - using onNack = std::function; + using onNack = std::function; //最大保留的rtp丢包状态个数 static constexpr auto kNackMaxSize = 2048; - //rtp丢包状态最长保留时间 + // rtp丢包状态最长保留时间 static constexpr auto kNackMaxMS = 3 * 1000; - //nack最多请求重传10次 + // nack最多请求重传10次 static constexpr auto kNackMaxCount = 10; - //nack重传频率,rtt的倍数 + // nack重传频率,rtt的倍数 static constexpr auto kNackIntervalRatio = 1.0f; NackContext() = default; @@ -57,8 +58,8 @@ public: private: void eraseFrontSeq(); - void doNack(const mediakit::FCI_NACK &nack, bool record_nack); - void recordNack(const mediakit::FCI_NACK &nack); + void doNack(const FCI_NACK &nack, bool record_nack); + void recordNack(const FCI_NACK &nack); void onRtx(uint16_t seq); private: @@ -67,12 +68,14 @@ private: std::set _seq; uint16_t _last_max_seq = 0; - struct NackStatus{ + struct NackStatus { uint64_t first_stamp; uint64_t update_stamp; int nack_count = 0; }; - std::map _nack_send_status; + std::map _nack_send_status; }; +} // namespace mediakit + #endif //ZLMEDIAKIT_NACK_H diff --git a/webrtc/RtpExt.cpp b/webrtc/RtpExt.cpp index b9568584..e1424ea7 100644 --- a/webrtc/RtpExt.cpp +++ b/webrtc/RtpExt.cpp @@ -17,7 +17,8 @@ using namespace std; using namespace toolkit; -using namespace mediakit; + +namespace mediakit { //https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 //https://tools.ietf.org/html/rfc5285 @@ -644,3 +645,4 @@ void RtpExtContext::onGetRtp(uint8_t pt, uint32_t ssrc, const string &rid){ } } +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/RtpExt.h b/webrtc/RtpExt.h index 89581c88..6345b394 100644 --- a/webrtc/RtpExt.h +++ b/webrtc/RtpExt.h @@ -17,6 +17,7 @@ #include "Common/macros.h" #include "Rtsp/Rtsp.h" +namespace mediakit { #define RTP_EXT_MAP(XX) \ XX(ssrc_audio_level, "urn:ietf:params:rtp-hdrext:ssrc-audio-level") \ @@ -53,7 +54,7 @@ public: friend class RtpExtContext; ~RtpExt() = default; - static std::map getExtValue(const mediakit::RtpHeader *header); + static std::map getExtValue(const RtpHeader *header); static RtpExtType getExtType(const std::string &url); static const std::string& getExtUrl(RtpExtType type); static const char *getExtName(RtpExtType type); @@ -122,7 +123,7 @@ public: void setOnGetRtp(OnGetRtp cb); std::string getRid(uint32_t ssrc) const; void setRid(uint32_t ssrc, const std::string &rid); - RtpExt changeRtpExtId(const mediakit::RtpHeader *header, bool is_recv, std::string *rid_ptr = nullptr, RtpExtType type = RtpExtType::padding); + RtpExt changeRtpExtId(const RtpHeader *header, bool is_recv, std::string *rid_ptr = nullptr, RtpExtType type = RtpExtType::padding); private: void onGetRtp(uint8_t pt, uint32_t ssrc, const std::string &rid); @@ -137,4 +138,5 @@ private: std::unordered_map _ssrc_to_rid; }; +} //namespace mediakit #endif //ZLMEDIAKIT_RTPEXT_H diff --git a/webrtc/Sdp.cpp b/webrtc/Sdp.cpp index b8eb3304..3a30ba67 100644 --- a/webrtc/Sdp.cpp +++ b/webrtc/Sdp.cpp @@ -14,15 +14,16 @@ using namespace std; using namespace toolkit; -using namespace mediakit; -namespace RTC { +namespace mediakit { + +namespace Rtc { #define RTC_FIELD "rtc." const string kPreferredCodecA = RTC_FIELD"preferredCodecA"; const string kPreferredCodecV = RTC_FIELD"preferredCodecV"; static onceToken token([]() { mINI::Instance()[kPreferredCodecA] = "PCMU,PCMA,opus,mpeg4-generic"; - mINI::Instance()[kPreferredCodecV] = "H264,H265,AV1X,VP9,VP8"; + mINI::Instance()[kPreferredCodecV] = "H264,H265,AV1,VP9,VP8"; }); } @@ -1203,7 +1204,9 @@ RtcSessionSdp::Ptr RtcSession::toRtcSessionSdp() const{ } for (auto &cand : m.candidate) { - sdp_media.addAttr(std::make_shared(cand)); + if (cand.port) { + sdp_media.addAttr(std::make_shared(cand)); + } } } return ret; @@ -1404,7 +1407,7 @@ void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type){ switch (type) { case TrackAudio: { //此处调整偏好的编码格式优先级 - GET_CONFIG_FUNC(vector, s_preferred_codec, RTC::kPreferredCodecA, toCodecArray); + GET_CONFIG_FUNC(vector, s_preferred_codec, Rtc::kPreferredCodecA, toCodecArray); CHECK(!s_preferred_codec.empty(), "rtc音频偏好codec不能为空"); preferred_codec = s_preferred_codec; @@ -1423,7 +1426,7 @@ void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type){ } case TrackVideo: { //此处调整偏好的编码格式优先级 - GET_CONFIG_FUNC(vector, s_preferred_codec, RTC::kPreferredCodecV, toCodecArray); + GET_CONFIG_FUNC(vector, s_preferred_codec, Rtc::kPreferredCodecV, toCodecArray); CHECK(!s_preferred_codec.empty(), "rtc视频偏好codec不能为空"); preferred_codec = s_preferred_codec; @@ -1811,3 +1814,5 @@ void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec) const { plan.fmtp[kMode] = mode.empty() ? "0" : mode; } } + +} // namespace mediakit \ No newline at end of file diff --git a/webrtc/Sdp.h b/webrtc/Sdp.h index 240a6a07..564362a4 100644 --- a/webrtc/Sdp.h +++ b/webrtc/Sdp.h @@ -18,6 +18,8 @@ #include "Extension/Frame.h" #include "Common/Parser.h" +namespace mediakit { + //https://datatracker.ietf.org/doc/rfc4566/?include_text=1 //https://blog.csdn.net/aggresss/article/details/109850434 //https://aggresss.blog.csdn.net/article/details/106436703 @@ -189,7 +191,7 @@ class SdpMedia : public SdpItem { public: // 5.14. Media Descriptions ("m=") // m= ... - mediakit::TrackType type; + TrackType type; uint16_t port; //RTP/AVP:应用场景为视频/音频的 RTP 协议。参考 RFC 3551 //RTP/SAVP:应用场景为视频/音频的 SRTP 协议。参考 RFC 3711 @@ -374,7 +376,7 @@ class SdpAttrFmtp : public SdpItem { public: //fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f uint8_t pt; - std::map fmtp; + std::map fmtp; void parse(const std::string &str) override; std::string toString() const override; const char* getKey() const override { return "fmtp";} @@ -600,7 +602,7 @@ public: uint32_t channel = 0; //rtcp反馈 std::set rtcp_fb; - std::map fmtp; + std::map fmtp; std::string getFmtp(const char *key) const; }; @@ -608,7 +610,7 @@ public: //rtc 媒体描述 class RtcMedia{ public: - mediakit::TrackType type{mediakit::TrackType::TrackInvalid}; + TrackType type{TrackType::TrackInvalid}; std::string mid; uint16_t port{0}; SdpConnection addr; @@ -675,8 +677,8 @@ public: void checkValid() const; std::string toString() const; std::string toRtspSdp() const; - const RtcMedia *getMedia(mediakit::TrackType type) const; - bool supportRtcpFb(const std::string &name, mediakit::TrackType type = mediakit::TrackType::TrackVideo) const; + const RtcMedia *getMedia(TrackType type) const; + bool supportRtcpFb(const std::string &name, TrackType type = TrackType::TrackVideo) const; bool supportSimulcast() const; bool isOnlyDatachannel() const; @@ -706,10 +708,10 @@ public: std::set rtcp_fb; std::map extmap; - std::vector preferred_codec; + std::vector preferred_codec; std::vector candidate; - void setDefaultSetting(mediakit::TrackType type); + void setDefaultSetting(TrackType type); void enableTWCC(bool enable = true); void enableREMB(bool enable = true); }; @@ -719,19 +721,19 @@ public: RtcTrackConfigure application; void setDefaultSetting(std::string ice_ufrag, std::string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint); - void addCandidate(const SdpAttrCandidate &candidate, mediakit::TrackType type = mediakit::TrackInvalid); + void addCandidate(const SdpAttrCandidate &candidate, TrackType type = TrackInvalid); std::shared_ptr createAnswer(const RtcSession &offer) const; void setPlayRtspInfo(const std::string &sdp); - void enableTWCC(bool enable = true, mediakit::TrackType type = mediakit::TrackInvalid); - void enableREMB(bool enable = true, mediakit::TrackType type = mediakit::TrackInvalid); + void enableTWCC(bool enable = true, TrackType type = TrackInvalid); + void enableREMB(bool enable = true, TrackType type = TrackInvalid); private: void matchMedia(const std::shared_ptr &ret, const RtcMedia &media) const; - bool onCheckCodecProfile(const RtcCodecPlan &plan, mediakit::CodecId codec) const; - void onSelectPlan(RtcCodecPlan &plan, mediakit::CodecId codec) const; + bool onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const; + void onSelectPlan(RtcCodecPlan &plan, CodecId codec) const; private: RtcCodecPlan::Ptr _rtsp_video_plan; @@ -748,5 +750,6 @@ private: ~SdpConst() = delete; }; +}// namespace mediakit #endif //ZLMEDIAKIT_SDP_H diff --git a/webrtc/TwccContext.cpp b/webrtc/TwccContext.cpp index ed565186..bbdba654 100644 --- a/webrtc/TwccContext.cpp +++ b/webrtc/TwccContext.cpp @@ -11,7 +11,7 @@ #include "TwccContext.h" #include "Rtcp/RtcpFCI.h" -using namespace mediakit; +namespace mediakit { enum class ExtSeqStatus : int { normal = 0, @@ -121,3 +121,5 @@ void TwccContext::clearStatus() { void TwccContext::setOnSendTwccCB(TwccContext::onSendTwccCB cb) { _cb = std::move(cb); } + +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/TwccContext.h b/webrtc/TwccContext.h index 26b10fcd..597d6d61 100644 --- a/webrtc/TwccContext.h +++ b/webrtc/TwccContext.h @@ -16,6 +16,8 @@ #include #include "Util/TimeTicker.h" +namespace mediakit { + class TwccContext { public: using onSendTwccCB = std::function; @@ -44,5 +46,5 @@ private: onSendTwccCB _cb; }; - +}// namespace mediakit #endif //ZLMEDIAKIT_TWCCCONTEXT_H diff --git a/webrtc/WebRtcEchoTest.cpp b/webrtc/WebRtcEchoTest.cpp index 1566ce53..f55ea198 100644 --- a/webrtc/WebRtcEchoTest.cpp +++ b/webrtc/WebRtcEchoTest.cpp @@ -10,6 +10,8 @@ #include "WebRtcEchoTest.h" +namespace mediakit { + WebRtcEchoTest::Ptr WebRtcEchoTest::create(const EventPoller::Ptr &poller) { WebRtcEchoTest::Ptr ret(new WebRtcEchoTest(poller), [](WebRtcEchoTest *ptr) { ptr->onDestory(); @@ -48,4 +50,6 @@ void WebRtcEchoTest::onCheckSdp(SdpType type, RtcSession &sdp) { } } } -} \ No newline at end of file +} + +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcEchoTest.h b/webrtc/WebRtcEchoTest.h index cdc5d92c..0ab74e7b 100644 --- a/webrtc/WebRtcEchoTest.h +++ b/webrtc/WebRtcEchoTest.h @@ -13,6 +13,8 @@ #include "WebRtcTransport.h" +namespace mediakit { + class WebRtcEchoTest : public WebRtcTransportImp { public: using Ptr = std::shared_ptr; @@ -26,7 +28,7 @@ protected: void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override; void onRtcp(const char *buf, size_t len) override; - void onRecvRtp(MediaTrack &track, const std::string &rid, mediakit::RtpPacket::Ptr rtp) override {}; + void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override {}; void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override {}; void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {}; @@ -34,4 +36,5 @@ private: WebRtcEchoTest(const EventPoller::Ptr &poller); }; +}// namespace mediakit #endif //ZLMEDIAKIT_WEBRTCECHOTEST_H diff --git a/webrtc/WebRtcPlayer.cpp b/webrtc/WebRtcPlayer.cpp index 520ca79d..c2882319 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -11,7 +11,8 @@ #include "WebRtcPlayer.h" using namespace std; -using namespace mediakit; + +namespace mediakit { WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, @@ -72,9 +73,7 @@ void WebRtcPlayer::onDestory() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_reader && getSession()) { WarnL << "RTC播放器(" - << _media_info._vhost << "/" - << _media_info._app << "/" - << _media_info._streamid + << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration; if (bytes_usage >= iFlowThreshold * 1024) { NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, @@ -90,3 +89,5 @@ void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const { configure.audio.direction = configure.video.direction = RtpDirection::sendonly; configure.setPlayRtspInfo(_play_src->getSdp()); } + +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcPlayer.h b/webrtc/WebRtcPlayer.h index e2487c67..c3a31b14 100644 --- a/webrtc/WebRtcPlayer.h +++ b/webrtc/WebRtcPlayer.h @@ -13,30 +13,32 @@ #include "WebRtcTransport.h" +namespace mediakit { + class WebRtcPlayer : public WebRtcTransportImp { public: using Ptr = std::shared_ptr; ~WebRtcPlayer() override = default; - static Ptr create(const EventPoller::Ptr &poller, const mediakit::RtspMediaSource::Ptr &src, const mediakit::MediaInfo &info); + static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); protected: ///////WebRtcTransportImp override/////// void onStartWebRTC() override; void onDestory() override; void onRtcConfigure(RtcConfigure &configure) const override; - void onRecvRtp(MediaTrack &track, const std::string &rid, mediakit::RtpPacket::Ptr rtp) override {}; + void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override {}; private: - WebRtcPlayer(const EventPoller::Ptr &poller, const mediakit::RtspMediaSource::Ptr &src, const mediakit::MediaInfo &info); + WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); private: //媒体相关元数据 - mediakit::MediaInfo _media_info; + MediaInfo _media_info; //播放的rtsp源 - mediakit::RtspMediaSource::Ptr _play_src; + RtspMediaSource::Ptr _play_src; //播放rtsp源的reader对象 - mediakit::RtspMediaSource::RingType::RingReader::Ptr _reader; + RtspMediaSource::RingType::RingReader::Ptr _reader; }; - -#endif //ZLMEDIAKIT_WEBRTCPLAYER_H +}// namespace mediakit +#endif // ZLMEDIAKIT_WEBRTCPLAYER_H diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index f73864b3..ca9115c8 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -11,13 +11,14 @@ #include "WebRtcPusher.h" using namespace std; -using namespace mediakit; + +namespace mediakit { WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller, const RtspMediaSourceImp::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, - const mediakit::ProtocolOption &option) { + const ProtocolOption &option) { WebRtcPusher::Ptr ret(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) { ptr->onDestory(); delete ptr; @@ -30,7 +31,7 @@ WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSourceImp::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, - const mediakit::ProtocolOption &option) : WebRtcTransportImp(poller) { + const ProtocolOption &option) : WebRtcTransportImp(poller) { _media_info = info; _push_src = src; _push_src_ownership = ownership; @@ -38,13 +39,9 @@ WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller, CHECK(_push_src); } -bool WebRtcPusher::close(MediaSource &sender, bool force) { +bool WebRtcPusher::close(MediaSource &sender) { //此回调在其他线程触发 - if (!force && totalReaderCount(sender)) { - return false; - } - string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" - << sender.getApp() << "/" << sender.getId() << " " << force; + string err = StrPrinter << "close media: " << sender.getUrl(); weak_ptr weak_self = static_pointer_cast(shared_from_this()); getPoller()->async([weak_self, err]() { auto strong_self = weak_self.lock(); @@ -77,6 +74,10 @@ std::shared_ptr WebRtcPusher::getOriginSock(MediaSource &sender) const return static_pointer_cast(getSession()); } +toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { if (!_simulcast) { assert(_push_src); @@ -123,9 +124,7 @@ void WebRtcPusher::onDestory() { if (getSession()) { WarnL << "RTC推流器(" - << _media_info._vhost << "/" - << _media_info._app << "/" - << _media_info._streamid + << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration; if (bytes_usage >= iFlowThreshold * 1024) { NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, @@ -148,6 +147,19 @@ void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const { configure.audio.direction = configure.video.direction = RtpDirection::recvonly; } -float WebRtcPusher::getLossRate(MediaSource &sender,mediakit::TrackType type){ +float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type){ return WebRtcTransportImp::getLossRate(type); -} \ No newline at end of file +} + +void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) { + //主动关闭推流,那么不等待重推 + _push_src = nullptr; + WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport); +} + +void WebRtcPusher::onRtcpBye(){ + _push_src = nullptr; + WebRtcTransportImp::onRtcpBye(); +} + +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcPusher.h b/webrtc/WebRtcPusher.h index a3e78e00..fee55e11 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -13,52 +13,60 @@ #include "WebRtcTransport.h" -class WebRtcPusher : public WebRtcTransportImp, public mediakit::MediaSourceEvent { +namespace mediakit { + +class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent { public: using Ptr = std::shared_ptr; ~WebRtcPusher() override = default; - static Ptr create(const EventPoller::Ptr &poller, const mediakit::RtspMediaSourceImp::Ptr &src, - const std::shared_ptr &ownership, const mediakit::MediaInfo &info, const mediakit::ProtocolOption &option); + static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSourceImp::Ptr &src, + const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option); protected: ///////WebRtcTransportImp override/////// void onStartWebRTC() override; void onDestory() override; void onRtcConfigure(RtcConfigure &configure) const override; - void onRecvRtp(MediaTrack &track, const std::string &rid, mediakit::RtpPacket::Ptr rtp) override; + void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; + void onRtcpBye() override; + //// dtls相关的回调 //// + void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; protected: ///////MediaSourceEvent override/////// // 关闭 - bool close(mediakit::MediaSource &sender, bool force) override; + bool close(MediaSource &sender) override; // 播放总人数 - int totalReaderCount(mediakit::MediaSource &sender) override; + int totalReaderCount(MediaSource &sender) override; // 获取媒体源类型 - mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override; + MediaOriginType getOriginType(MediaSource &sender) const override; // 获取媒体源url或者文件路径 - std::string getOriginUrl(mediakit::MediaSource &sender) const override; + std::string getOriginUrl(MediaSource &sender) const override; // 获取媒体源客户端相关信息 - std::shared_ptr getOriginSock(mediakit::MediaSource &sender) const override; + std::shared_ptr getOriginSock(MediaSource &sender) const override; + // 由于支持断连续推,存在OwnerPoller变更的可能 + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; // 获取丢包率 - float getLossRate(mediakit::MediaSource &sender,mediakit::TrackType type) override; + float getLossRate(MediaSource &sender,TrackType type) override; private: - WebRtcPusher(const EventPoller::Ptr &poller, const mediakit::RtspMediaSourceImp::Ptr &src, - const std::shared_ptr &ownership, const mediakit::MediaInfo &info, const mediakit::ProtocolOption &option); + WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSourceImp::Ptr &src, + const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option); private: bool _simulcast = false; //断连续推延时 uint32_t _continue_push_ms = 0; //媒体相关元数据 - mediakit::MediaInfo _media_info; + MediaInfo _media_info; //推流的rtsp源 - mediakit::RtspMediaSourceImp::Ptr _push_src; + RtspMediaSourceImp::Ptr _push_src; //推流所有权 std::shared_ptr _push_src_ownership; //推流的rtsp源,支持simulcast - std::unordered_map _push_src_sim; + std::unordered_map _push_src_sim; std::unordered_map > _push_src_sim_ownership; }; +}// namespace mediakit #endif //ZLMEDIAKIT_WEBRTCPUSHER_H diff --git a/webrtc/WebRtcSession.cpp b/webrtc/WebRtcSession.cpp index 1cb428f1..f72f620c 100644 --- a/webrtc/WebRtcSession.cpp +++ b/webrtc/WebRtcSession.cpp @@ -10,13 +10,13 @@ #include "WebRtcSession.h" #include "Util/util.h" +#include "Network/TcpServer.h" using namespace std; -using namespace mediakit; -static string getUserName(const Buffer::Ptr &buffer) { - auto buf = buffer->data(); - auto len = buffer->size(); +namespace mediakit { + +static string getUserName(const char *buf, size_t len) { if (!RTC::StunPacket::IsStun((const uint8_t *) buf, len)) { return ""; } @@ -34,7 +34,7 @@ static string getUserName(const Buffer::Ptr &buffer) { } EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) { - auto user_name = getUserName(buffer); + auto user_name = getUserName(buffer->data(), buffer->size()); if (user_name.empty()) { return nullptr; } @@ -44,34 +44,63 @@ EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) { //////////////////////////////////////////////////////////////////////////////// -WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : UdpSession(sock) { +WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock) { socklen_t addr_len = sizeof(_peer_addr); getpeername(sock->rawFD(), (struct sockaddr *)&_peer_addr, &addr_len); + _over_tcp = sock->sockType() == SockNum::Sock_TCP; } WebRtcSession::~WebRtcSession() { InfoP(this); } -void WebRtcSession::onRecv(const Buffer::Ptr &buffer) { +void WebRtcSession::attachServer(const Server &server) { + _server = std::dynamic_pointer_cast(const_cast(server).shared_from_this()); +} + +void WebRtcSession::onRecv_l(const char *data, size_t len) { if (_find_transport) { - //只允许寻找一次transport + // 只允许寻找一次transport _find_transport = false; - auto user_name = getUserName(buffer); - _identifier = to_string(getSock()->rawFD()) + '-' + user_name; + auto user_name = getUserName(data, len); auto transport = WebRtcTransportManager::Instance().getItem(user_name); - CHECK(transport && transport->getPoller()->isCurrentThread()); + CHECK(transport); + + //WebRtcTransport在其他poller线程上,需要切换poller线程并重新创建WebRtcSession对象 + if (!transport->getPoller()->isCurrentThread()) { + auto sock = Socket::createSocket(transport->getPoller()); + sock->cloneFromPeerSocket(*(getSock())); + auto server = _server; + std::string str(data, len); + sock->getPoller()->async([sock, server, str](){ + auto strong_server = server.lock(); + if (strong_server) { + auto session = static_pointer_cast(strong_server->createSession(sock)); + session->onRecv_l(str.data(), str.size()); + } + }); + throw std::runtime_error("webrtc over tcp change poller: " + getPoller()->getThreadName() + " -> " + sock->getPoller()->getThreadName()); + } + transport->setSession(shared_from_this()); _transport = std::move(transport); InfoP(this); } _ticker.resetTime(); CHECK(_transport); - _transport->inputSockData(buffer->data(), buffer->size(), (struct sockaddr *)&_peer_addr); + _transport->inputSockData((char *)data, len, (struct sockaddr *)&_peer_addr); +} + +void WebRtcSession::onRecv(const Buffer::Ptr &buffer) { + if (_over_tcp) { + input(buffer->data(), buffer->size()); + } else { + onRecv_l(buffer->data(), buffer->size()); + } } void WebRtcSession::onError(const SockException &err) { - //udp链接超时,但是rtc链接不一定超时,因为可能存在udp链接迁移的情况 + //udp链接超时,但是rtc链接不一定超时,因为可能存在链接迁移的情况 //在udp链接迁移时,新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 //本WebRtcSession对象将在超时后自动销毁 WarnP(this) << err.what(); @@ -86,7 +115,7 @@ void WebRtcSession::onError(const SockException &err) { } void WebRtcSession::onManager() { - GET_CONFIG(float, timeoutSec, RTC::kTimeOutSec); + GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); if (!_transport && _ticker.createdTime() > timeoutSec * 1000) { shutdown(SockException(Err_timeout, "illegal webrtc connection")); return; @@ -97,7 +126,25 @@ void WebRtcSession::onManager() { } } -std::string WebRtcSession::getIdentifier() const { - return _identifier; +ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) { + onRecv_l(data + 2, len - 2); + return 0; } +const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) { + if (len < 2) { + // 数据不够 + return nullptr; + } + uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1]; + if (len < (size_t)(length + 2)) { + // 数据不够 + return nullptr; + } + // 返回rtp包末尾 + return data + 2 + length; +} + +}// namespace mediakit + + diff --git a/webrtc/WebRtcSession.h b/webrtc/WebRtcSession.h index dcbd079c..9d48e814 100644 --- a/webrtc/WebRtcSession.h +++ b/webrtc/WebRtcSession.h @@ -15,26 +15,41 @@ #include "Network/Session.h" #include "IceServer.hpp" #include "WebRtcTransport.h" +#include "Http/HttpRequestSplitter.h" -class WebRtcSession : public UdpSession { +namespace toolkit { + class TcpServer; +} + +namespace mediakit { + +class WebRtcSession : public Session, public HttpRequestSplitter { public: WebRtcSession(const Socket::Ptr &sock); ~WebRtcSession() override; + void attachServer(const Server &server) override; void onRecv(const Buffer::Ptr &) override; void onError(const SockException &err) override; void onManager() override; - std::string getIdentifier() const override; - static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); private: - std::string _identifier; + //// HttpRequestSplitter override //// + ssize_t onRecvHeader(const char *data, size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + + void onRecv_l(const char *data, size_t len); + +private: + bool _over_tcp = false; bool _find_transport = true; Ticker _ticker; struct sockaddr_storage _peer_addr; + std::weak_ptr _server; std::shared_ptr _transport; }; +}// namespace mediakit #endif //ZLMEDIAKIT_WEBRTCSESSION_H diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 31bce08c..1d4642af 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -8,15 +8,18 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#include "WebRtcTransport.h" -#include "Rtcp/Rtcp.h" -#include "Rtcp/RtcpFCI.h" -#include "RtpExt.h" -#include "Rtsp/RtpReceiver.h" - +#include #include -#include +#include "RtpExt.h" +#include "Rtcp/Rtcp.h" +#include "Rtcp/RtcpFCI.h" +#include "Rtsp/RtpReceiver.h" +#include "WebRtcTransport.h" + +#include "WebRtcEchoTest.h" +#include "WebRtcPlayer.h" +#include "WebRtcPusher.h" #define RTP_SSRC_OFFSET 1 #define RTX_SSRC_OFFSET 2 @@ -26,10 +29,11 @@ #define RTP_MSID RTP_MSLABEL " " RTP_LABEL using namespace std; -using namespace mediakit; + +namespace mediakit { // RTC配置项目 -namespace RTC { +namespace Rtc { #define RTC_FIELD "rtc." // rtp和rtcp接受超时时间 const string kTimeOutSec = RTC_FIELD "timeoutSec"; @@ -40,11 +44,14 @@ const string kRembBitRate = RTC_FIELD "rembBitRate"; // webrtc单端口udp服务器 const string kPort = RTC_FIELD "port"; +const string kTcpPort = RTC_FIELD "tcpPort"; + static onceToken token([]() { mINI::Instance()[kTimeOutSec] = 15; mINI::Instance()[kExternIP] = ""; mINI::Instance()[kRembBitRate] = 0; mINI::Instance()[kPort] = 8000; + mINI::Instance()[kTcpPort] = 8000; }); } // namespace RTC @@ -246,7 +253,7 @@ void WebRtcTransport::setRemoteDtlsFingerprint(const RtcSession &remote) { void WebRtcTransport::onRtcConfigure(RtcConfigure &configure) const { // 开启remb后关闭twcc,因为开启twcc后remb无效 - GET_CONFIG(size_t, remb_bit_rate, RTC::kRembBitRate); + GET_CONFIG(size_t, remb_bit_rate, Rtc::kRembBitRate); configure.enableTWCC(!remb_bit_rate); } @@ -368,7 +375,7 @@ void WebRtcTransportImp::onCreate() { registerSelf(); weak_ptr weak_self = static_pointer_cast(shared_from_this()); - GET_CONFIG(float, timeoutSec, RTC::kTimeOutSec); + GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); _timer = std::make_shared( timeoutSec / 2, [weak_self]() { @@ -414,9 +421,21 @@ void WebRtcTransportImp::onSendSockData(Buffer::Ptr buf, bool flush, RTC::Transp WarnL << "send data failed:" << buf->size(); return; } + // 一次性发送一帧的rtp数据,提高网络io性能 - _selected_session->setSendFlushFlag(flush); + if (_selected_session->getSock()->sockType() == SockNum::Sock_TCP) { + // 增加tcp两字节头 + auto len = buf->size(); + char tcp_len[2] = { 0 }; + tcp_len[0] = (len >> 8) & 0xff; + tcp_len[1] = len & 0xff; + _selected_session->SockSender::send(tcp_len, 2); + } _selected_session->send(std::move(buf)); + + if (flush) { + _selected_session->flushAll(); + } } /////////////////////////////////////////////////////////////////// @@ -511,7 +530,7 @@ void WebRtcTransportImp::onStartWebRTC() { void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { // 修改answer sdp的ip、端口信息 - GET_CONFIG_FUNC(std::vector, extern_ips, RTC::kExternIP, [](string str) { + GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { std::vector ret; if (str.length()) { ret = split(str, ","); @@ -525,8 +544,9 @@ void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { m.rtcp_addr.reset(); m.rtcp_addr.address = m.addr.address; - GET_CONFIG(uint16_t, local_port, RTC::kPort); - m.rtcp_addr.port = local_port; + GET_CONFIG(uint16_t, udp_port, Rtc::kPort); + GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); + m.rtcp_addr.port = udp_port ? udp_port : tcp_port; m.port = m.rtcp_addr.port; sdp.origin.address = m.addr.address; } @@ -586,15 +606,19 @@ makeIceCandidate(std::string ip, uint16_t port, uint32_t priority = 100, std::st candidate->address = ip; candidate->port = port; candidate->type = "host"; + if (proto == "tcp") { + candidate->type += " tcptype passive"; + } return candidate; } void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const { WebRtcTransport::onRtcConfigure(configure); - GET_CONFIG(uint16_t, local_port, RTC::kPort); + GET_CONFIG(uint16_t, local_udp_port, Rtc::kPort); + GET_CONFIG(uint16_t, local_tcp_port, Rtc::kTcpPort); // 添加接收端口candidate信息 - GET_CONFIG_FUNC(std::vector, extern_ips, RTC::kExternIP, [](string str) { + GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { std::vector ret; if (str.length()) { ret = split(str, ","); @@ -603,13 +627,15 @@ void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const { return ret; }); if (extern_ips.empty()) { - std::string localIp = SockUtil::get_local_ip(); - configure.addCandidate(*makeIceCandidate(localIp, local_port, 120, "udp")); + std::string local_ip = SockUtil::get_local_ip(); + if (local_udp_port) { configure.addCandidate(*makeIceCandidate(local_ip, local_udp_port, 120, "udp")); } + if (local_tcp_port) { configure.addCandidate(*makeIceCandidate(local_ip, local_tcp_port, 110, "tcp")); } } else { const uint32_t delta = 10; uint32_t priority = 100 + delta * extern_ips.size(); for (auto ip : extern_ips) { - configure.addCandidate(*makeIceCandidate(ip, local_port, priority, "udp")); + if (local_udp_port) { configure.addCandidate(*makeIceCandidate(ip, local_udp_port, priority + 5, "udp")); } + if (local_tcp_port) { configure.addCandidate(*makeIceCandidate(ip, local_tcp_port, priority, "tcp")); } priority -= delta; } } @@ -698,7 +724,7 @@ std::shared_ptr MediaTrack::getRtpChannel(uint32_t ssrc) const { return it_chn->second; } -float WebRtcTransportImp::getLossRate(mediakit::TrackType type) { +float WebRtcTransportImp::getLossRate(TrackType type) { for (auto &pr : _ssrc_to_track) { auto ssrc = pr.first; auto &track = pr.second; @@ -718,6 +744,7 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) { for (auto rtcp : rtcps) { switch ((RtcpType)rtcp->pt) { case RtcpType::RTCP_SR: { + _alive_ticker.resetTime(); // 对方汇报rtp发送情况 RtcpSR *sr = (RtcpSR *)rtcp; auto it = _ssrc_to_track.find(sr->ssrc); @@ -766,6 +793,7 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) { } _ssrc_to_track.erase(it); } + onRtcpBye(); onShutdown(SockException(Err_eof, "rtcp bye message received")); break; } @@ -940,7 +968,7 @@ void WebRtcTransportImp::onSortedRtp(MediaTrack &track, const string &rid, RtpPa sendRtcpPli(rtp->getSSRC()); // 开启remb,则发送remb包调节比特率 - GET_CONFIG(size_t, remb_bit_rate, RTC::kRembBitRate); + GET_CONFIG(size_t, remb_bit_rate, Rtc::kRembBitRate); if (remb_bit_rate && _answer_sdp->supportRtcpFb(SdpConst::kRembRtcpFb)) { sendRtcpRemb(rtp->getSSRC(), remb_bit_rate); } @@ -1036,6 +1064,7 @@ void WebRtcTransportImp::setSession(Session::Ptr session) { << session->get_peer_port() << ", id:" << getIdentifier(); } _selected_session = std::move(session); + _selected_session->setSendFlushFlag(false); unrefSelf(); } @@ -1051,6 +1080,8 @@ uint64_t WebRtcTransportImp::getDuration() const { return _alive_ticker.createdTime() / 1000; } +void WebRtcTransportImp::onRtcpBye(){} + ///////////////////////////////////////////////////////////////////////////////////////////// void WebRtcTransportImp::registerSelf() { @@ -1106,31 +1137,23 @@ void WebRtcPluginManager::registerPlugin(const string &type, Plugin cb) { _map_creator[type] = std::move(cb); } -void WebRtcPluginManager::getAnswerSdp( - Session &sender, const string &type, const string &offer, const WebRtcArgs &args, const onCreateRtc &cb) { +void WebRtcPluginManager::getAnswerSdp(Session &sender, const string &type, const WebRtcArgs &args, const onCreateRtc &cb) { lock_guard lck(_mtx_creator); auto it = _map_creator.find(type); if (it == _map_creator.end()) { cb(WebRtcException(SockException(Err_other, "the type can not supported"))); return; } - it->second(sender, offer, args, cb); + it->second(sender, args, cb); } -#include "WebRtcEchoTest.h" -#include "WebRtcPlayer.h" -#include "WebRtcPusher.h" - -void echo_plugin( - Session &sender, const string &offer, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb) { +void echo_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb) { cb(*WebRtcEchoTest::create(EventPollerPool::Instance().getPoller())); } -void push_plugin( - Session &sender, const string &offer_sdp, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb) { +void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb) { MediaInfo info(args["url"]); - Broadcast::PublishAuthInvoker invoker = [cb, offer_sdp, - info](const string &err, const ProtocolOption &option) mutable { + Broadcast::PublishAuthInvoker invoker = [cb, info](const string &err, const ProtocolOption &option) mutable { if (!err.empty()) { cb(WebRtcException(SockException(Err_other, err))); return; @@ -1169,26 +1192,23 @@ void push_plugin( push_src_ownership = push_src->getOwnership(); push_src->setProtocolOption(option); } - auto rtc - = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option); + auto rtc = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option); push_src->setListener(rtc); cb(*rtc); }; // rtsp推流需要鉴权 - auto flag = NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, static_cast(sender)); + auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, static_cast(sender)); if (!flag) { // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); } } -void play_plugin( - Session &sender, const string &offer_sdp, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb) { +void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginManager::onCreateRtc &cb) { MediaInfo info(args["url"]); auto session_ptr = sender.shared_from_this(); - Broadcast::AuthInvoker invoker = [cb, offer_sdp, info, session_ptr](const string &err) mutable { + Broadcast::AuthInvoker invoker = [cb, info, session_ptr](const string &err) mutable { if (!err.empty()) { cb(WebRtcException(SockException(Err_other, err))); return; @@ -1210,8 +1230,7 @@ void play_plugin( }; // 广播通用播放url鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastMediaPlayed, info, invoker, static_cast(sender)); + auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, info, invoker, static_cast(sender)); if (!flag) { // 该事件无人监听,默认不鉴权 invoker(""); @@ -1223,3 +1242,5 @@ static onceToken s_rtc_auto_register([]() { WebRtcPluginManager::Instance().registerPlugin("push", push_plugin); WebRtcPluginManager::Instance().registerPlugin("play", play_plugin); }); + +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcTransport.h b/webrtc/WebRtcTransport.h index 01d842f6..14615146 100644 --- a/webrtc/WebRtcTransport.h +++ b/webrtc/WebRtcTransport.h @@ -27,9 +27,12 @@ #include "TwccContext.h" #include "SctpAssociation.hpp" +namespace mediakit { + //RTC配置项目 -namespace RTC { +namespace Rtc { extern const std::string kPort; +extern const std::string kTcpPort; extern const std::string kTimeOutSec; }//namespace RTC @@ -154,6 +157,7 @@ protected: virtual void onShutdown(const SockException &ex) = 0; virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0; virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0; + virtual void onRtcpBye() = 0; protected: RTC::TransportTuple* getSelectedTuple() const; @@ -176,7 +180,7 @@ private: std::shared_ptr _srtp_session_send; std::shared_ptr _srtp_session_recv; Ticker _ticker; - //循环池 + // 循环池 ResourcePool _packet_pool; #ifdef ENABLE_SCTP @@ -199,7 +203,7 @@ public: //for send rtp NackList nack_list; - mediakit::RtcpContext::Ptr rtcp_context_send; + RtcpContext::Ptr rtcp_context_send; //for recv rtp std::unordered_map > rtp_channel; @@ -210,13 +214,13 @@ struct WrappedMediaTrack { MediaTrack::Ptr track; explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {} virtual ~WrappedMediaTrack() {} - virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, mediakit::RtpHeader *rtp) = 0; + virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0; }; struct WrappedRtxTrack: public WrappedMediaTrack { explicit WrappedRtxTrack(MediaTrack::Ptr ptr) : WrappedMediaTrack(std::move(ptr)) {} - void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, mediakit::RtpHeader *rtp) override; + void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; }; class WebRtcTransportImp; @@ -228,7 +232,7 @@ struct WrappedRtpTrack : public WrappedMediaTrack { , _transport(t) {} TwccContext& _twcc_ctx; WebRtcTransportImp& _transport; - void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, mediakit::RtpHeader *rtp) override; + void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; }; class WebRtcTransportImp : public WebRtcTransport { @@ -242,7 +246,7 @@ public: uint64_t getDuration() const; bool canSendRtp() const; bool canRecvRtp() const; - void onSendRtp(const mediakit::RtpPacket::Ptr &rtp, bool flush, bool rtx = false); + void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false); void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track); @@ -261,13 +265,14 @@ protected: void onCreate() override; void onDestory() override; void onShutdown(const SockException &ex) override; - virtual void onRecvRtp(MediaTrack &track, const std::string &rid, mediakit::RtpPacket::Ptr rtp) = 0; + virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) = 0; void updateTicker(); - float getLossRate(mediakit::TrackType type); + float getLossRate(TrackType type); + void onRtcpBye() override; private: - void onSortedRtp(MediaTrack &track, const std::string &rid, mediakit::RtpPacket::Ptr rtp); - void onSendNack(MediaTrack &track, const mediakit::FCI_NACK &nack, uint32_t ssrc); + void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp); + void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc); void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci); void registerSelf(); @@ -328,12 +333,12 @@ public: class WebRtcPluginManager { public: using onCreateRtc = std::function; - using Plugin = std::function; + using Plugin = std::function; static WebRtcPluginManager &Instance(); void registerPlugin(const std::string &type, Plugin cb); - void getAnswerSdp(Session &sender, const std::string &type, const std::string &offer, const WebRtcArgs &args, const onCreateRtc &cb); + void getAnswerSdp(Session &sender, const std::string &type, const WebRtcArgs &args, const onCreateRtc &cb); private: WebRtcPluginManager() = default; @@ -341,4 +346,6 @@ private: private: mutable std::mutex _mtx_creator; std::unordered_map _map_creator; -}; \ No newline at end of file +}; + +}// namespace mediakit \ No newline at end of file diff --git a/webrtc/offer.sdp b/webrtc/offer.sdp index c3b09302..fa92a59f 100644 --- a/webrtc/offer.sdp +++ b/webrtc/offer.sdp @@ -128,7 +128,7 @@ a=rtcp-fb:108 nack pli a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f a=rtpmap:109 rtx/90000 a=fmtp:109 apt=108 -a=rtpmap:35 AV1X/90000 +a=rtpmap:35 AV1/90000 a=rtcp-fb:35 goog-remb a=rtcp-fb:35 transport-cc a=rtcp-fb:35 ccm fir diff --git a/webrtc/readme.md b/webrtc/readme.md index b94b2de4..3ac82dee 100644 --- a/webrtc/readme.md +++ b/webrtc/readme.md @@ -15,7 +15,10 @@ - srtp相关功能: - SrtpSession.cpp - SrtpSession.hpp - + +- datachannel相关功能: + - SctpAssociation.cpp + - SctpAssociation.hpp 以上源码有一定的修改和裁剪,感谢MediaSoup开源项目及作者, 用户在使用本项目的同时,应该同时遵循MediaSoup的开源协议。 diff --git a/webrtc_player/android/.gitignore b/webrtc_player/android/.gitignore new file mode 100644 index 00000000..d4c3a57e --- /dev/null +++ b/webrtc_player/android/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/ diff --git a/webrtc_player/android/app-debug.apk b/webrtc_player/android/app-debug.apk new file mode 100644 index 00000000..969d48b7 Binary files /dev/null and b/webrtc_player/android/app-debug.apk differ diff --git a/webrtc_player/android/app/.gitignore b/webrtc_player/android/app/.gitignore new file mode 100644 index 00000000..c591fdeb --- /dev/null +++ b/webrtc_player/android/app/.gitignore @@ -0,0 +1,2 @@ +/build +.cxx \ No newline at end of file diff --git a/webrtc_player/android/app/build.gradle b/webrtc_player/android/app/build.gradle new file mode 100644 index 00000000..cfd25638 --- /dev/null +++ b/webrtc_player/android/app/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-android-extensions' + id 'kotlin-kapt' + +} +apply plugin: 'kotlin-android' + +android { + compileSdk 32 + + defaultConfig { + applicationId "com.zlmediakit.webrtc" + minSdk 21 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'com.google.code.gson:gson:2.8.9' + + implementation("com.squareup.okhttp3:okhttp:4.10.0") + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation 'org.webrtc:google-webrtc:1.0.32006' + +} \ No newline at end of file diff --git a/webrtc_player/android/app/proguard-rules.pro b/webrtc_player/android/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/webrtc_player/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/webrtc_player/android/app/src/androidTest/java/com/zlmediakit/webrtc/ExampleInstrumentedTest.kt b/webrtc_player/android/app/src/androidTest/java/com/zlmediakit/webrtc/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..645a1102 --- /dev/null +++ b/webrtc_player/android/app/src/androidTest/java/com/zlmediakit/webrtc/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.zlmediakit.webrtc + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.zlmediakit.webrtc", appContext.packageName) + } +} \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/AndroidManifest.xml b/webrtc_player/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..81281350 --- /dev/null +++ b/webrtc_player/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/java/com/zlmediakit/webrtc/MainActivity.kt b/webrtc_player/android/app/src/main/java/com/zlmediakit/webrtc/MainActivity.kt new file mode 100644 index 00000000..15a8efc8 --- /dev/null +++ b/webrtc_player/android/app/src/main/java/com/zlmediakit/webrtc/MainActivity.kt @@ -0,0 +1,79 @@ +package com.zlmediakit.webrtc + +import android.annotation.SuppressLint +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.activity_main.view.* + + +class MainActivity : AppCompatActivity() { + + private var isSpeaker = true + + @SuppressLint("SetTextI18n") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + lifecycle.addObserver(web_rtc_sv) + + //http://124.223.98.45/index/api/webrtc?app=live&stream=test&type=play + url.setText("http://124.223.98.45/index/api/webrtc?app=live&stream=test&type=play") + + //http://192.168.1.17/index/api/webrtc?app=live&stream=test&type=play + btn_play.setOnClickListener { + web_rtc_sv?.setVideoPath(url.text.toString()) + web_rtc_sv.start() + } + + web_rtc_sv.setOnErrorListener { errorCode, errorMsg -> + runOnUiThread { + Toast.makeText(this, "errorCode:$errorCode,errorMsg:$errorMsg", Toast.LENGTH_SHORT) + .show() + } + } + + + btn_pause.setOnClickListener { + web_rtc_sv?.pause() + } + + btn_resume.setOnClickListener { + web_rtc_sv?.resume() + } + + btn_screenshot.setOnClickListener { + web_rtc_sv?.screenshot { + runOnUiThread { + iv_screen.setImageDrawable(BitmapDrawable(it)) + } + } + } + + btn_mute.setOnClickListener { + web_rtc_sv.mute(true) + } + + + selectAudio() + btn_speaker.setOnClickListener { + selectAudio() + } + + } + + fun selectAudio(){ + if (isSpeaker){ + btn_speaker.setText("扬声器") + web_rtc_sv.setSpeakerphoneOn(isSpeaker) + }else{ + btn_speaker.setText("话筒") + web_rtc_sv.setSpeakerphoneOn(isSpeaker) + } + isSpeaker=!isSpeaker + } +} \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/java/com/zlmediakit/webrtc/WebRTCSurfaceView.kt b/webrtc_player/android/app/src/main/java/com/zlmediakit/webrtc/WebRTCSurfaceView.kt new file mode 100644 index 00000000..3e94171c --- /dev/null +++ b/webrtc_player/android/app/src/main/java/com/zlmediakit/webrtc/WebRTCSurfaceView.kt @@ -0,0 +1,439 @@ +package com.zlmediakit.webrtc + +import android.content.Context +import android.graphics.Bitmap +import android.media.AudioManager +import android.util.AttributeSet +import android.util.Log +import android.view.LayoutInflater +import android.widget.RelativeLayout +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.google.gson.Gson +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import org.webrtc.* +import org.webrtc.RendererCommon.ScalingType +import org.webrtc.audio.AudioDeviceModule +import org.webrtc.audio.JavaAudioDeviceModule +import java.io.IOException +import java.util.* + +public class WebRTCSurfaceView(context: Context, attrs: AttributeSet?) : + RelativeLayout(context, attrs), DefaultLifecycleObserver, RendererCommon.RendererEvents { + + + private data class sdp(var sdp: String, var username: String, var password: String) + + private data class SdpResponse(var code: Int, var id: String, var sdp: String, var type: String) + + private enum class ErrorCode(val errorCode: Int) { + SUCCESS(0x00), + GET_REMOTE_SDP_ERROR(0x01); + } + + + companion object { + private val TAG = "WebRTCSurfaceView" + + } + + private var mContext: Context = context + + private val eglBase: EglBase = EglBase.create() + private var mEGLBaseContext: EglBase.Context = eglBase.eglBaseContext + + private lateinit var videoUrl: String; + + private var mPeerConnectionFactory: PeerConnectionFactory? = null + + private var mLocalMediaStream: MediaStream? = null + private var mLocalAudioTrack: AudioTrack? = null + private var mAudioSource: AudioSource? = null + + private var mLocalSessionDescription: SessionDescription? = null + private var mRemoteSessionDescription: SessionDescription? = null + + private var mLocalPeer: Peer? = null + + private var mSurfaceViewRenderer: SurfaceViewRenderer + + private lateinit var OnErrorListener: (errorCode: Int, errorMsg: String) -> Unit? + + fun setOnErrorListener(listener: (errorCode: Int, errorMsg: String) -> Unit) { + this.OnErrorListener = listener + } + + private lateinit var OnPreparedListener: () -> Unit? + + fun setOnPreparedListener(listener: () -> Unit) { + this.OnPreparedListener = listener + } + + private val audioManager: AudioManager + + + init { + + val view = LayoutInflater.from(mContext).inflate(R.layout.layout_videoview, this) + + mPeerConnectionFactory = createConnectionFactory() + + mSurfaceViewRenderer = view.findViewById(R.id.surface_view_renderer) + + mSurfaceViewRenderer.init(mEGLBaseContext, this) + mSurfaceViewRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL) + mSurfaceViewRenderer.setEnableHardwareScaler(true) + + + //创建媒体流 + mLocalMediaStream = mPeerConnectionFactory?.createLocalMediaStream("ARDAMS") + //采集音频 + mAudioSource = mPeerConnectionFactory?.createAudioSource(createAudioConstraints()) + mLocalAudioTrack = mPeerConnectionFactory?.createAudioTrack("ARDAMSa0", mAudioSource) + + //添加Tracks + mLocalMediaStream?.addTrack(mLocalAudioTrack) + + audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + audioManager.isSpeakerphoneOn = false + + + } + + + private fun set(width: Int, height: Int) { + layoutParams.width = width + layoutParams.height = height + } + + private fun createConnectionFactory(): PeerConnectionFactory? { + + val options = PeerConnectionFactory.InitializationOptions.builder(mContext) + .setEnableInternalTracer(false) + .createInitializationOptions() + + PeerConnectionFactory.initialize(options) + + val videoEncoderFactory = DefaultVideoEncoderFactory( + mEGLBaseContext, + true, + true + ) + + val videoDecoderFactory = DefaultVideoDecoderFactory(mEGLBaseContext) + + + val audioDevice = createJavaAudioDevice() + val peerConnectionFactory = PeerConnectionFactory.builder() + .setAudioDeviceModule(audioDevice) + .setVideoEncoderFactory(videoEncoderFactory) + .setVideoDecoderFactory(videoDecoderFactory) + .createPeerConnectionFactory() + audioDevice.release() + + return peerConnectionFactory + + } + + private fun createAudioConstraints(): MediaConstraints { + val audioConstraints = MediaConstraints() + audioConstraints.mandatory.add( + MediaConstraints.KeyValuePair( + "googEchoCancellation", + "true" + ) + ) + audioConstraints.mandatory.add( + MediaConstraints.KeyValuePair( + "googAutoGainControl", + "false" + ) + ) + audioConstraints.mandatory.add( + MediaConstraints.KeyValuePair( + "googHighpassFilter", + "true" + ) + ) + audioConstraints.mandatory.add( + MediaConstraints.KeyValuePair( + "googNoiseSuppression", + "true" + ) + ) + return audioConstraints + } + + private fun offerOrAnswerConstraint(): MediaConstraints { + val mediaConstraints = MediaConstraints() + val keyValuePairs = java.util.ArrayList() + keyValuePairs.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")) + keyValuePairs.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true")) + mediaConstraints.mandatory.addAll(keyValuePairs) + return mediaConstraints + } + + private fun createJavaAudioDevice(): AudioDeviceModule { + val audioTrackErrorCallback: JavaAudioDeviceModule.AudioTrackErrorCallback = object : + JavaAudioDeviceModule.AudioTrackErrorCallback { + override fun onWebRtcAudioTrackInitError(errorMessage: String) { + Log.i(TAG, "onWebRtcAudioTrackInitError ============> $errorMessage") + + } + + override fun onWebRtcAudioTrackStartError( + errorCode: JavaAudioDeviceModule.AudioTrackStartErrorCode, errorMessage: String + ) { + Log.i(TAG, "onWebRtcAudioTrackStartError ============> $errorCode:$errorMessage") + + } + + override fun onWebRtcAudioTrackError(errorMessage: String) { + Log.i(TAG, "onWebRtcAudioTrackError ============> $errorMessage") + + } + } + + + // Set audio track state callbacks. + val audioTrackStateCallback: JavaAudioDeviceModule.AudioTrackStateCallback = object : + JavaAudioDeviceModule.AudioTrackStateCallback { + override fun onWebRtcAudioTrackStart() { + Log.i(TAG, "onWebRtcAudioTrackStart ============>") + + } + + override fun onWebRtcAudioTrackStop() { + Log.i(TAG, "onWebRtcAudioTrackStop ============>") + + } + } + + return JavaAudioDeviceModule.builder(mContext) + .setUseHardwareAcousticEchoCanceler(true) + .setUseHardwareNoiseSuppressor(true) + .setAudioTrackErrorCallback(audioTrackErrorCallback) + .setAudioTrackStateCallback(audioTrackStateCallback) + .setUseStereoOutput(true) //立体声 + .createAudioDeviceModule() + } + + fun setVideoPath(url: String) { + videoUrl = url + } + + fun start() { + + mLocalPeer = Peer { + val okHttpClient = OkHttpClient.Builder().build() + + + val body = RequestBody.create("text/plain; charset=utf-8".toMediaType(), it!!) + + + val request: Request = Request.Builder() + .url(videoUrl) + .post(body) + .build() + + val call: Call = okHttpClient.newCall(request) + + call.enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + Log.i(TAG, "onFailure") + OnErrorListener?.invoke( + ErrorCode.GET_REMOTE_SDP_ERROR.errorCode, + e.message.toString() + ) + } + + override fun onResponse(call: Call, response: Response) { + val body = response.body?.string() + val sdpResponse = Gson().fromJson(body, SdpResponse::class.java) + + try { + mRemoteSessionDescription = SessionDescription( + SessionDescription.Type.fromCanonicalForm("answer"), + sdpResponse.sdp + ) + Log.i( + TAG, + "RemoteSdpObserver onCreateSuccess:[SessionDescription[type=${mRemoteSessionDescription?.type?.name},description=${mRemoteSessionDescription?.description}]]" + ) + mLocalPeer?.setRemoteDescription(mRemoteSessionDescription!!) + } catch (e: Exception) { + Log.i(TAG, e.toString()) + OnErrorListener.invoke( + ErrorCode.GET_REMOTE_SDP_ERROR.errorCode, + e.localizedMessage + ) + } + } + }) + } + } + + fun pause() { + mSurfaceViewRenderer.pauseVideo() + //mSurfaceViewRenderer.disableFpsReduction() + } + + fun resume() { + mSurfaceViewRenderer.setFpsReduction(15f) + } + + fun screenshot(listener: (bitmap: Bitmap) -> Unit) { + mSurfaceViewRenderer.addFrameListener({ + listener.invoke(it) + }, 1f) + } + + fun setSpeakerphoneOn(on: Boolean) { + audioManager.isSpeakerphoneOn = on + } + + fun mute(on:Boolean) { + audioManager.isMicrophoneMute=on + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + mSurfaceViewRenderer.release() + mLocalPeer?.mPeerConnection?.dispose() + mAudioSource?.dispose() + mPeerConnectionFactory?.dispose() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + } + + inner class Peer(var sdp: (String?) -> Unit = {}) : PeerConnection.Observer, SdpObserver { + + var mPeerConnection: PeerConnection? = null + + init { + mPeerConnection = createPeerConnection() + mPeerConnection?.createOffer(this, offerOrAnswerConstraint()) + } + + //初始化 RTCPeerConnection 连接管道 + private fun createPeerConnection(): PeerConnection? { + if (mPeerConnectionFactory == null) { + mPeerConnectionFactory = createConnectionFactory() + } + // 管道连接抽象类实现方法 + val ICEServers = LinkedList() + val rtcConfig = PeerConnection.RTCConfiguration(ICEServers) + //修改模式 PlanB无法使用仅接收音视频的配置 + //rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.PLAN_B + return mPeerConnectionFactory?.createPeerConnection(rtcConfig, this) + } + + fun setRemoteDescription(sdp: SessionDescription) { + mPeerConnection?.setRemoteDescription(this, sdp) + } + + override fun onCreateSuccess(sessionDescription: SessionDescription?) { + mPeerConnection?.setLocalDescription(this, sessionDescription) + mPeerConnection?.addStream(mLocalMediaStream) + sdp.invoke(sessionDescription?.description) + } + + override fun onSetSuccess() { + + } + + override fun onCreateFailure(p0: String?) { + + } + + override fun onSetFailure(p0: String?) { + + } + + override fun onSignalingChange(signalingState: PeerConnection.SignalingState?) { + Log.i(TAG, "onSignalingChange ============> " + signalingState.toString()) + } + + override fun onIceConnectionChange(iceConnectionState: PeerConnection.IceConnectionState?) { + Log.i(TAG, "onIceConnectionChange ============> " + iceConnectionState.toString()) + + } + + override fun onIceConnectionReceivingChange(p0: Boolean) { + Log.i(TAG, "onIceConnectionReceivingChange ============> $p0") + + } + + override fun onIceGatheringChange(iceGatheringState: PeerConnection.IceGatheringState?) { + Log.i(TAG, "onIceGatheringChange ============> ${iceGatheringState.toString()}") + } + + override fun onIceCandidate(iceCandidate: IceCandidate?) { + Log.i(TAG, "onIceCandidate ============> ${iceCandidate.toString()}") + + + } + + override fun onIceCandidatesRemoved(p0: Array?) { + Log.i(TAG, "onIceCandidatesRemoved ============> ${p0.toString()}") + } + + override fun onAddStream(mediaStream: MediaStream?) { + Log.i(TAG, "onAddStream ============> ${mediaStream?.toString()}") + + if (mediaStream?.videoTracks?.isEmpty() != true) { + val remoteVideoTrack = mediaStream?.videoTracks?.get(0) + remoteVideoTrack?.setEnabled(true) + remoteVideoTrack?.addSink(mSurfaceViewRenderer) + } + + if (mediaStream?.audioTracks?.isEmpty() != true) { + val remoteAudioTrack = mediaStream?.audioTracks?.get(0) + remoteAudioTrack?.setEnabled(true) + remoteAudioTrack?.setVolume(1.0) + } + + + } + + override fun onRemoveStream(mediaStream: MediaStream?) { + Log.i(TAG, "onRemoveStream ============> ${mediaStream.toString()}") + + } + + override fun onDataChannel(dataChannel: DataChannel?) { + Log.i(TAG, "onDataChannel ============> ${dataChannel.toString()}") + + } + + override fun onRenegotiationNeeded() { + Log.i(TAG, "onRenegotiationNeeded ============>") + + } + + override fun onAddTrack(rtpReceiver: RtpReceiver?, p1: Array?) { + Log.i(TAG, "onAddTrack ============>" + rtpReceiver?.track()) + Log.i(TAG, "onAddTrack ============>" + p1?.size) + + } + } + + override fun onFirstFrameRendered() { + Log.i(TAG, "onFirstFrameRendered ============>") + + } + + override fun onFrameResolutionChanged(frameWidth: Int, frameHeight: Int, rotation: Int) { + Log.i(TAG, "onFrameResolutionChanged ============> $frameWidth:$frameHeight:$rotation") + //set(frameWidth,frameHeight) + } + + + + +} \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/webrtc_player/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/webrtc_player/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/drawable/ic_launcher_background.xml b/webrtc_player/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/webrtc_player/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webrtc_player/android/app/src/main/res/layout/activity_main.xml b/webrtc_player/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..fdefb4e5 --- /dev/null +++ b/webrtc_player/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/layout/layout_videoview.xml b/webrtc_player/android/app/src/main/res/layout/layout_videoview.xml new file mode 100644 index 00000000..43c56e5e --- /dev/null +++ b/webrtc_player/android/app/src/main/res/layout/layout_videoview.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/webrtc_player/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/webrtc_player/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/webrtc_player/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/webrtc_player/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/webrtc_player/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/webrtc_player/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..b2dfe3d1 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/webrtc_player/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/webrtc_player/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..62b611da Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/webrtc_player/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/webrtc_player/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..1b9a6956 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/webrtc_player/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/webrtc_player/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9287f508 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/webrtc_player/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/webrtc_player/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/webrtc_player/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9126ae37 Binary files /dev/null and b/webrtc_player/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/webrtc_player/android/app/src/main/res/values-night/themes.xml b/webrtc_player/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..6a2a7eac --- /dev/null +++ b/webrtc_player/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/values/colors.xml b/webrtc_player/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/webrtc_player/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/values/strings.xml b/webrtc_player/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..a7484826 --- /dev/null +++ b/webrtc_player/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + AndroidWebRTC + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/values/themes.xml b/webrtc_player/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..119a9df9 --- /dev/null +++ b/webrtc_player/android/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/xml/backup_rules.xml b/webrtc_player/android/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 00000000..fa0f996d --- /dev/null +++ b/webrtc_player/android/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/main/res/xml/data_extraction_rules.xml b/webrtc_player/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..9ee9997b --- /dev/null +++ b/webrtc_player/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/webrtc_player/android/app/src/test/java/com/zlmediakit/webrtc/ExampleUnitTest.kt b/webrtc_player/android/app/src/test/java/com/zlmediakit/webrtc/ExampleUnitTest.kt new file mode 100644 index 00000000..5f47259f --- /dev/null +++ b/webrtc_player/android/app/src/test/java/com/zlmediakit/webrtc/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.zlmediakit.webrtc + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/webrtc_player/android/build.gradle b/webrtc_player/android/build.gradle new file mode 100644 index 00000000..c9fd0fc4 --- /dev/null +++ b/webrtc_player/android/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} +plugins { + id 'com.android.application' version '7.2.1' apply false + id 'com.android.library' version '7.2.1' apply false + id 'org.jetbrains.kotlin.android' version '1.7.10' apply false +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/webrtc_player/android/gradle.properties b/webrtc_player/android/gradle.properties new file mode 100644 index 00000000..cd0519bb --- /dev/null +++ b/webrtc_player/android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/webrtc_player/android/gradle/wrapper/gradle-wrapper.jar b/webrtc_player/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/webrtc_player/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/webrtc_player/android/gradle/wrapper/gradle-wrapper.properties b/webrtc_player/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..8a5b4f05 --- /dev/null +++ b/webrtc_player/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Sep 19 22:08:36 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/webrtc_player/android/gradlew b/webrtc_player/android/gradlew new file mode 100644 index 00000000..4f906e0c --- /dev/null +++ b/webrtc_player/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/webrtc_player/android/gradlew.bat b/webrtc_player/android/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/webrtc_player/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/webrtc_player/android/pic/Screenshot_2022-09-25-10-06-59-444_com.zlmediakit.webrtc.jpg b/webrtc_player/android/pic/Screenshot_2022-09-25-10-06-59-444_com.zlmediakit.webrtc.jpg new file mode 100644 index 00000000..96719cf3 Binary files /dev/null and b/webrtc_player/android/pic/Screenshot_2022-09-25-10-06-59-444_com.zlmediakit.webrtc.jpg differ diff --git a/webrtc_player/android/settings.gradle b/webrtc_player/android/settings.gradle new file mode 100644 index 00000000..4a669b32 --- /dev/null +++ b/webrtc_player/android/settings.gradle @@ -0,0 +1,24 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + jcenter() // Warning: this repository is going to shut down soon + + maven { url 'https://jitpack.io' } + + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() // Warning: this repository is going to shut down soon + + maven { url 'https://jitpack.io' } + + } +} +rootProject.name = "android" +include ':app' diff --git a/www/webrtc/index.html b/www/webrtc/index.html index a2ec0d4f..d4eb1776 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -46,14 +46,14 @@

- - echo - push - play + + echo + push + play

- -

@@ -98,7 +98,7 @@ url = "http://127.0.0.1"+"/index/api/webrtc?app=live&stream=test&type=play" } document.getElementById('streamUrl').value = url - document.getElementsByName("methond").forEach((el,idx)=>{ + document.getElementsByName("method").forEach((el,idx)=>{ el.onclick=function(e){ let url = new URL(document.getElementById('streamUrl').value); url.searchParams.set("type",el.value) @@ -118,14 +118,14 @@ opt = document.createElement('option'); opt.text = r.label +"("+r.width+"x"+r.height+")"; opt.value = r; - document.getElementById("resilution").add(opt,null) + document.getElementById("resolution").add(opt,null) //console.log(opt.text.match(/\d+/g)) }) function start_play(){ - let elr = document.getElementById("resilution"); + let elr = document.getElementById("resolution"); let res = elr.options[elr.selectedIndex].text.match(/\d+/g); let h = parseInt(res.pop()); let w = parseInt(res.pop()); @@ -204,7 +204,7 @@ function start() { stop(); - let elr = document.getElementById("resilution"); + let elr = document.getElementById("resolution"); let res = elr.options[elr.selectedIndex].text.match(/\d+/g); let h = parseInt(res.pop()); let w = parseInt(res.pop());