mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-06-10 17:47:50 +08:00
commit
68358155c9
57
.github/ISSUE_TEMPLATE/compile.md
vendored
Normal file
57
.github/ISSUE_TEMPLATE/compile.md
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
name: 编译问题反馈
|
||||
about: 反馈 ZLMediaKit 编译相关的问题
|
||||
title: "[编译问题]: "
|
||||
labels: 编译问题
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
请仔细阅读相关注释提示, 请务必根据提示填写相关信息.
|
||||
1. 信息不完整会影响问题的解决速度.
|
||||
1. 乱七八糟的渲染格式也会影响开发者心情, 同样会影响问题的解决. 提交前请务必点击 Preview/预览下反馈的显示效果.
|
||||
1. 不要删除模版内容, 模版的注释部分的内容不会显示,不需要删除,直接在各部分注释外面补充相关信息即可.
|
||||
-->
|
||||
|
||||
<!--
|
||||
markdown 语法参考:
|
||||
* https://docs.github.com/cn/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax
|
||||
* https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax
|
||||
-->
|
||||
|
||||
### 相关日志及环境信息
|
||||
|
||||
<!--
|
||||
由于编译日志通长较长, 建议将日志信息填写到下面 `````` block 内,或者上传日志文件
|
||||
-->
|
||||
|
||||
**清除编译缓存后,完整执行 cmake && make 命令的输出**
|
||||
|
||||
<details>
|
||||
<summary>展开查看详细编译日志</summary>
|
||||
<pre>
|
||||
|
||||
```
|
||||
详细日志粘在这里!
|
||||
```
|
||||
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
编译目录下的 `CMakeCache.txt` 文件内容,请直接上传为附件。
|
||||
|
||||
### 各种环境信息
|
||||
|
||||
<!--
|
||||
请填写相关环境信息, 详细的环境信息有助于快速复现定位问题.
|
||||
|
||||
* 代码提交记录, 可使用命令 `git rev-parse HEAD` 进行查看.
|
||||
* 操作系统及版本, 如: Windows 10, CentOS 7, ...
|
||||
* 硬件信息, 如: Intel, AMD, ARM, 飞腾, 龙芯, ...
|
||||
-->
|
||||
|
||||
* **代码提交记录/git commit hash**:
|
||||
* **操作系统及版本**:
|
||||
* **硬件信息**:
|
||||
* **其他需要补充的信息**:
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@ -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
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
@ -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
|
||||
|
||||
12
.travis.yml
12
.travis.yml
@ -1,12 +0,0 @@
|
||||
language: cpp
|
||||
sudo: required
|
||||
dist: trusty
|
||||
compiler:
|
||||
- clang
|
||||
os:
|
||||
- linux
|
||||
script:
|
||||
- ./build_for_linux.sh
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/jsoncpp/include"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>")
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/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
|
||||
"$<BUILD_INTERFACE:${MediaServer_MPEG_ROOT}/include>"
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 82f82e723eda127777aaf91ad0e4615e8d56e8d5
|
||||
Subproject commit 617b6b1db23f13e2592b29204b84b1b9dbbf81c0
|
||||
1
3rdpart/jsoncpp
Submodule
1
3rdpart/jsoncpp
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8190e061bc2d95da37479a638aa2c9e483e58ec6
|
||||
@ -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 <stdlib.h>
|
||||
#include <sstream>
|
||||
|
||||
#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
|
||||
@ -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 <cpptl/cpptl_autolink.h>
|
||||
#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
|
||||
@ -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 <cpptl/config.h>
|
||||
#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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@ -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<char>(cp);
|
||||
} else if (cp <= 0x7FF) {
|
||||
result.resize(2);
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
|
||||
} else if (cp <= 0xFFFF) {
|
||||
result.resize(3);
|
||||
result[2] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
|
||||
result[0] = static_cast<char>(0xE0 | (0xf & (cp >> 12)));
|
||||
} else if (cp <= 0x10FFFF) {
|
||||
result.resize(4);
|
||||
result[3] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
|
||||
result[0] = static_cast<char>(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<signed char>(value % 10U + static_cast<unsigned>('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
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 <deque>
|
||||
#include <iosfwd>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
|
||||
// Disable warning C4251: <data member>: <type> 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 <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a>
|
||||
* 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 <a HREF="http://www.json.org">JSON</a>
|
||||
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<StructuredError> 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<ErrorInfo> 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<Value*> 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 <a HREF="http://www.json.org">JSON</a>
|
||||
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
|
||||
@ -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 <string>
|
||||
#include <vector>
|
||||
#include <exception>
|
||||
|
||||
#ifndef JSON_USE_CPPTL_SMALLMAP
|
||||
#include <map>
|
||||
#else
|
||||
#include <cpptl/smallmap.h>
|
||||
#endif
|
||||
#ifdef JSON_USE_CPPTL
|
||||
#include <cpptl/forwards.h>
|
||||
#endif
|
||||
|
||||
// Disable warning C4251: <data member>: <type> 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<const char *> EnumMemberNames;
|
||||
// typedef CppTL::AnyEnumerator<const Value &> 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 <a HREF="http://www.json.org">JSON</a> 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<std::string> 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<CZString, Value> ObjectValues;
|
||||
#else
|
||||
typedef CppTL::SmallMap<CZString, Value> 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<const PathArgument*> InArgs;
|
||||
typedef std::vector<PathArgument> 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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 <vector>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
|
||||
// Disable warning C4251: <data member>: <type> 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<StreamWriter> 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<Json::StreamWriter> 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": "<anything>"
|
||||
- "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 <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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<std::string> ChildValues;
|
||||
|
||||
ChildValues childValues_;
|
||||
std::string document_;
|
||||
std::string indentString_;
|
||||
int rightMargin_;
|
||||
int indentSize_;
|
||||
bool addChildValues_;
|
||||
};
|
||||
|
||||
/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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<std::string> 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
|
||||
@ -1 +1 @@
|
||||
Subproject commit 8dc2fbf275628ab306a0976ac54533dd0e181f65
|
||||
Subproject commit 357ef885afa5112c8cb873eab21d94d26744c0ab
|
||||
@ -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
|
||||
|
||||
@ -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<FrameWriterInterfaceHelper>([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<FrameWriterInterfaceHelper>([globalWeakRef](const Frame::Ptr &frame) {
|
||||
audioTrack->addDelegate([globalWeakRef](const Frame::Ptr &frame) {
|
||||
emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame));
|
||||
return true;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
## 功能清单
|
||||
### 功能一览
|
||||
<img width="800" alt="功能一览" src="https://user-images.githubusercontent.com/11495632/114176523-d50fce80-996d-11eb-81f8-0a2e2715ba7b.png">
|
||||
<img width="800" alt="功能一览" src="https://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png">
|
||||
|
||||
- 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事件
|
||||
|
||||
@ -213,9 +213,9 @@ git submodule update --init
|
||||
WarnL << "none video Track!";
|
||||
return;
|
||||
}
|
||||
viedoTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([](const Frame::Ptr &frame) {
|
||||
viedoTrack->addDelegate([](const Frame::Ptr &frame) {
|
||||
//please decode video here
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
player->setOnShutdown([](const SockException &ex) {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-cayman
|
||||
@ -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
|
||||
|
||||
@ -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服务器
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 服务器
|
||||
|
||||
@ -37,7 +37,13 @@ static std::shared_ptr<RtpServer> rtpServer;
|
||||
|
||||
#ifdef ENABLE_WEBRTC
|
||||
#include "../webrtc/WebRtcSession.h"
|
||||
static std::shared_ptr<UdpServer> rtcServer;
|
||||
static std::shared_ptr<UdpServer> rtcServer_udp;
|
||||
static std::shared_ptr<TcpServer> rtcServer_tcp;
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_SRT)
|
||||
#include "../srt/SrtSession.hpp"
|
||||
static std::shared_ptr<UdpServer> 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<TcpServer>();
|
||||
if(ssl){
|
||||
http_server[ssl]->start<TcpSessionWithSSL<HttpSession> >(port);
|
||||
http_server[ssl]->start<SessionWithSSL<HttpSession> >(port);
|
||||
} else{
|
||||
http_server[ssl]->start<HttpSession>(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<TcpServer>();
|
||||
if(ssl){
|
||||
rtsp_server[ssl]->start<TcpSessionWithSSL<RtspSession> >(port);
|
||||
rtsp_server[ssl]->start<SessionWithSSL<RtspSession> >(port);
|
||||
}else{
|
||||
rtsp_server[ssl]->start<RtspSession>(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<TcpServer>();
|
||||
if(ssl){
|
||||
rtmp_server[ssl]->start<TcpSessionWithSSL<RtmpSession> >(port);
|
||||
rtmp_server[ssl]->start<SessionWithSSL<RtmpSession> >(port);
|
||||
}else{
|
||||
rtmp_server[ssl]->start<RtmpSession>(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<UdpServer>();
|
||||
rtcServer->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) {
|
||||
//创建rtc udp服务器
|
||||
rtcServer_udp = std::make_shared<UdpServer>();
|
||||
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<WebRtcSession>(port);
|
||||
return rtcServer->getPort();
|
||||
rtcServer_udp->start<WebRtcSession>(port);
|
||||
//创建rtc tcp服务器
|
||||
rtcServer_tcp = std::make_shared<TcpServer>();
|
||||
rtcServer_tcp->start<WebRtcSession>(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<HttpSession>(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<WebRtcInterface &>(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<UdpServer>();
|
||||
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<SRT::SrtSession>(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<ShellSession>(port);
|
||||
return shell_server->getPort();
|
||||
} catch (std::exception &ex) {
|
||||
shell_server.reset();
|
||||
shell_server = nullptr;;
|
||||
WarnL << ex.what();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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<Session *>(&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<WebRtcInterface &>(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<std::string>(*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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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后再编译";
|
||||
}
|
||||
|
||||
|
||||
@ -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<TcpSession> weak_session = ((TcpSessionForC *) ctx)->shared_from_this();
|
||||
std::weak_ptr<Session> 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<TcpSessionForC>(std::dynamic_pointer_cast<TcpSessionForC>(ref));
|
||||
auto ref = ((SessionForC *) ctx)->shared_from_this();
|
||||
return new std::shared_ptr<SessionForC>(std::dynamic_pointer_cast<SessionForC>(ref));
|
||||
}
|
||||
|
||||
API_EXPORT void mk_tcp_session_ref_release(const mk_tcp_session_ref ref) {
|
||||
delete (std::shared_ptr<TcpSessionForC> *) ref;
|
||||
delete (std::shared_ptr<SessionForC> *) ref;
|
||||
}
|
||||
|
||||
API_EXPORT mk_tcp_session mk_tcp_session_from_ref(const mk_tcp_session_ref ref) {
|
||||
return ((std::shared_ptr<TcpSessionForC> *) ref)->get();
|
||||
return ((std::shared_ptr<SessionForC> *) 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<TcpServer>();
|
||||
switch (type) {
|
||||
case mk_type_tcp:
|
||||
s_tcp_server[type]->start<TcpSessionForC>(port);
|
||||
s_tcp_server[type]->start<SessionForC>(port);
|
||||
break;
|
||||
case mk_type_ssl:
|
||||
s_tcp_server[type]->start<TcpSessionWithSSL<TcpSessionForC> >(port);
|
||||
s_tcp_server[type]->start<SessionWithSSL<SessionForC> >(port);
|
||||
break;
|
||||
case mk_type_ws:
|
||||
//此处你也可以修改WebSocketHeader::BINARY
|
||||
s_tcp_server[type]->start<WebSocketSession<TcpSessionForC, HttpSession, WebSocketHeader::TEXT> >(port);
|
||||
s_tcp_server[type]->start<WebSocketSession<SessionForC, HttpSession, WebSocketHeader::TEXT> >(port);
|
||||
break;
|
||||
case mk_type_wss:
|
||||
//此处你也可以修改WebSocketHeader::BINARY
|
||||
s_tcp_server[type]->start<WebSocketSession<TcpSessionForC, HttpsSession, WebSocketHeader::TEXT> >(port);
|
||||
s_tcp_server[type]->start<WebSocketSession<SessionForC, HttpsSession, WebSocketHeader::TEXT> >(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<TcpSessionWithSSL<TcpClientForC> >(new TcpSessionWithSSL<TcpClientForC>(events));
|
||||
return (TcpClientForC::Ptr *)new std::shared_ptr<SessionWithSSL<TcpClientForC> >(new SessionWithSSL<TcpClientForC>(events));
|
||||
case mk_type_ws:
|
||||
//此处你也可以修改WebSocketHeader::BINARY
|
||||
return (TcpClientForC::Ptr *)new std::shared_ptr<WebSocketClient<TcpClientForC, WebSocketHeader::TEXT, false> >(new WebSocketClient<TcpClientForC, WebSocketHeader::TEXT, false>(events));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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<FrameWriterInterfaceHelper>([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) {
|
||||
|
||||
@ -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 <functional>
|
||||
#include <string>
|
||||
|
||||
#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<std::string, std::string, mediakit::StrCaseCompare>;
|
||||
template <typename Args, typename First>
|
||||
std::string getValue(Args &args, const First &first) {
|
||||
return args[first];
|
||||
}
|
||||
|
||||
template <typename First>
|
||||
std::string getValue(Json::Value &args, const First &first) {
|
||||
return args[first].asString();
|
||||
}
|
||||
|
||||
template <typename First>
|
||||
std::string getValue(std::string &args, const First &first) {
|
||||
return "";
|
||||
}
|
||||
|
||||
template <typename First>
|
||||
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 <typename First>
|
||||
std::string getValue(mediakit::Parser &parser, const First &first) {
|
||||
return getValue((const mediakit::Parser &)parser, first);
|
||||
}
|
||||
|
||||
template <typename Args, typename First>
|
||||
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 <typename Args>
|
||||
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 <typename Key>
|
||||
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<void *()> _get_args;
|
||||
std::function<const mediakit::Parser &()> _get_parser;
|
||||
std::function<std::string(HttpAllArgs &that, const std::string &key)> _get_value;
|
||||
std::function<void(HttpAllArgs &that)> _clone;
|
||||
};
|
||||
|
||||
#define API_ARGS_MAP \
|
||||
toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const HttpAllArgs<ApiArgsType> &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<Json::Value> &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<std::string> &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<void(API_ARGS_STRING_ASYNC)> &func);
|
||||
|
||||
template <typename Args, typename First>
|
||||
bool checkArgs(Args &args, const First &first) {
|
||||
return !args[first].empty();
|
||||
}
|
||||
|
||||
template <typename Args, typename First, typename... KeyTypes>
|
||||
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<std::string> &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<std::string> _args;
|
||||
std::string _session_id;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // MK_WEBRTC_API_H
|
||||
#endif // ENABLE_WEBRTC
|
||||
@ -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 = "<html>"
|
||||
"<head>"
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
118
conf/config.ini
118
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
|
||||
|
||||
@ -86,4 +86,4 @@ M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
|
||||
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
|
||||
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
|
||||
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
|
||||
-----END CERTIFICATE-----
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@ -53,7 +53,7 @@ int main(int argc, char *argv[]) {
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
|
||||
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<FrameWriterInterfaceHelper>([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<FrameWriterInterfaceHelper>( [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;
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -273,14 +273,14 @@ void FFmpegSource::setOnClose(const function<void()> &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;
|
||||
|
||||
@ -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或者文件路径
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 <sys/wait.h>
|
||||
#endif // _WIN32
|
||||
|
||||
@ -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<recursive_mutex> 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<RtpServer>();
|
||||
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<recursive_mutex> 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<void(const SockException &ex)> &cb) {
|
||||
lock_guard<recursive_mutex> 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<recursive_mutex> lck(s_rtpServerMapMtx);
|
||||
auto it = s_rtpServerMap.find(stream_id);
|
||||
@ -772,9 +786,7 @@ void installWebApi() {
|
||||
[](std::shared_ptr<void> &&info) -> std::shared_ptr<void> {
|
||||
auto obj = std::make_shared<Value>();
|
||||
auto session = static_pointer_cast<Session>(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<bool>(), "::",
|
||||
allArgs["re_use_port"].as<bool>(), allArgs["ssrc"].as<uint32_t>());
|
||||
if(port == 0) {
|
||||
auto tcp_mode = allArgs["tcp_mode"].as<int>();
|
||||
if (allArgs["enable_tcp"].as<int>() && !tcp_mode) {
|
||||
//兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数
|
||||
tcp_mode = 1;
|
||||
}
|
||||
auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, "::", allArgs["re_use_port"].as<bool>(),
|
||||
allArgs["ssrc"].as<uint32_t>());
|
||||
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<Session *>(&sender)), type, offer, WebRtcArgsImp(allArgs, sender.getIdentifier()),
|
||||
[invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable {
|
||||
WebRtcPluginManager::Instance().getAnswerSdp(*(static_cast<Session *>(&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 <<allArgs.getArgs().toStyledString();
|
||||
});
|
||||
}
|
||||
|
||||
void unInstallWebApi(){
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#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<void(const toolkit::SockException &ex)> &cb);
|
||||
bool closeRtpServer(const std::string &stream_id);
|
||||
Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
|
||||
void getStatisticJson(const std::function<void(Json::Value &val)> &cb);
|
||||
|
||||
@ -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<string>& 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();
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "jsoncpp/json.h"
|
||||
#include "json/json.h"
|
||||
|
||||
//支持json或urlencoded方式传输参数
|
||||
#define JSON_ARGS
|
||||
|
||||
@ -165,6 +165,7 @@ public:
|
||||
[](const std::shared_ptr<ostream> &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<AsyncLogWriter>());
|
||||
|
||||
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<TcpServer>();
|
||||
//webrtc udp服务器
|
||||
auto rtcSrv = std::make_shared<UdpServer>();
|
||||
rtcSrv->setOnCreateSocket([](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *, int) {
|
||||
auto rtcSrv_udp = std::make_shared<UdpServer>();
|
||||
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<WebRtcSession>(rtcPort); }
|
||||
#endif//defined(ENABLE_WEBRTC)
|
||||
if (rtcPort) { rtcSrv_udp->start<WebRtcSession>(rtcPort);}
|
||||
|
||||
if (rtcTcpPort) { rtcSrv_tcp->start<WebRtcSession>(rtcTcpPort);}
|
||||
|
||||
#endif//defined(ENABLE_WEBRTC)
|
||||
|
||||
#if defined(ENABLE_SRT)
|
||||
// srt udp服务器
|
||||
if(srtPort){
|
||||
srtSrv->start<SRT::SrtSession>(srtPort);
|
||||
}
|
||||
if(srtPort) { srtSrv->start<SRT::SrtSession>(srtPort); }
|
||||
#endif//defined(ENABLE_SRT)
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ bool MediaSink::addTrack(const Track::Ptr &track_in) {
|
||||
};
|
||||
_ticker.resetTime();
|
||||
|
||||
track->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([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<AACTrack>(makeAacConfig(MUTE_ADTS_DATA, ADTS_HEADER_LEN));
|
||||
_track_map[audio->getTrackType()] = std::make_pair(audio, true);
|
||||
audio->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([this](const Frame::Ptr &frame) {
|
||||
audio->addDelegate([this](const Frame::Ptr &frame) {
|
||||
return onTrackFrame(frame);
|
||||
}));
|
||||
});
|
||||
_mute_audio_maker = std::make_shared<MuteAudioMaker>();
|
||||
_mute_audio_maker->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([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;
|
||||
|
||||
@ -61,7 +61,7 @@ public:
|
||||
bool inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
private:
|
||||
uint32_t _audio_idx = 0;
|
||||
uint64_t _audio_idx = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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<string/*strema_id*/, weak_ptr<MediaSource> >;
|
||||
using AppStreamMap = unordered_map<string/*app*/, StreamMap>;
|
||||
using VhostAppStreamMap = unordered_map<string/*vhost*/, AppStreamMap>;
|
||||
using SchemaVhostAppStreamMap = unordered_map<string/*schema*/, VhostAppStreamMap>;
|
||||
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<void> 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<MediaSourceEvent> MediaSource::getListener(bool next) const{
|
||||
if (!next) {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
auto listener = dynamic_pointer_cast<MediaSourceEventInterceptor>(_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<MediaSource &>(*this));
|
||||
if (!ret.empty()) {
|
||||
return ret;
|
||||
}
|
||||
return getOriginUrl_l(this);
|
||||
return getUrl();
|
||||
}
|
||||
|
||||
std::shared_ptr<SockInfo> 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<MediaSource> 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<void(const Ptr &src)> &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<MediaSource::Ptr &>(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<Session> &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<Session> &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) {
|
||||
|
||||
@ -19,19 +19,14 @@
|
||||
#include <unordered_map>
|
||||
#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<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
|
||||
|
||||
@ -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 <typename MAP>
|
||||
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 <typename MAP, typename KEY, typename TYPE>
|
||||
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<MediaSourceEvent> &listener);
|
||||
std::shared_ptr<MediaSourceEvent> 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<Media
|
||||
public:
|
||||
static MediaSource& NullMediaSource();
|
||||
using Ptr = std::shared_ptr<MediaSource>;
|
||||
using StreamMap = std::unordered_map<std::string/*stream_id*/, std::weak_ptr<MediaSource> >;
|
||||
using AppStreamMap = std::unordered_map<std::string/*app*/, StreamMap>;
|
||||
using VhostAppStreamMap = std::unordered_map<std::string/*vhost*/, AppStreamMap>;
|
||||
using SchemaVhostAppStreamMap = std::unordered_map<std::string/*schema*/, VhostAppStreamMap>;
|
||||
|
||||
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<void> 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<MediaSourceEvent> _listener;
|
||||
toolkit::EventPoller::Ptr _default_poller;
|
||||
//对象个数统计
|
||||
// 对象个数统计
|
||||
toolkit::ObjectStatistic<MediaSource> _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<typename packet, typename policy = FlushPolicy, typename packet_list = toolkit::List<std::shared_ptr<packet> > >
|
||||
class PacketCache {
|
||||
public:
|
||||
PacketCache(){
|
||||
_cache = std::make_shared<packet_list>();
|
||||
}
|
||||
PacketCache() { _cache = std::make_shared<packet_list>(); }
|
||||
|
||||
virtual ~PacketCache() = default;
|
||||
|
||||
void inputPacket(uint64_t stamp, bool is_video, std::shared_ptr<packet> 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<packet_list>();
|
||||
_key_pos = false;
|
||||
}
|
||||
|
||||
virtual void clearCache() {
|
||||
_cache->clear();
|
||||
}
|
||||
@ -375,22 +468,19 @@ public:
|
||||
virtual void onFlush(std::shared_ptr<packet_list>, bool key_pos) = 0;
|
||||
|
||||
private:
|
||||
void flushAll() {
|
||||
if (_cache->empty()) {
|
||||
return;
|
||||
}
|
||||
onFlush(std::move(_cache), _key_pos);
|
||||
_cache = std::make_shared<packet_list>();
|
||||
_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<packet, RtpPacket>::value && rtspLowLatency) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return std::is_same<packet, RtpPacket>::value ? false : (mergeWriteMS <= 0);
|
||||
}
|
||||
|
||||
|
||||
@ -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<MediaSinkInterface> makeRecorder(MediaSource &sender, const vector<Track::Ptr> &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<MediaSinkInterface> makeRecorder(MediaSource &sender, const vector<Track::Ptr> &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<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, option, std::make_shared<TitleMeta>(dur_sec));
|
||||
}
|
||||
if (option.enable_rtsp) {
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, option, std::make_shared<TitleSdp>(dur_sec));
|
||||
}
|
||||
if (option.enable_hls) {
|
||||
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream, option.hls_save_path));
|
||||
_hls = dynamic_pointer_cast<HlsRecorder>(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<TSMediaSourceMuxer>(vhost, app, stream);
|
||||
_ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream, option);
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (option.enable_fmp4) {
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream);
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(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<HlsRecorder>(makeRecorder(sender, getTracks(), type, custom_path, max_second));
|
||||
_option.hls_save_path = custom_path;
|
||||
auto hls = dynamic_pointer_cast<HlsRecorder>(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() {
|
||||
|
||||
@ -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<MultiMediaSourceMuxer>{
|
||||
public:
|
||||
typedef std::shared_ptr<MultiMediaSourceMuxer> 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<Listener> _track_listener;
|
||||
std::function<std::string()> _get_origin_url;
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
std::unordered_map<std::string, RtpSender::Ptr> _rtp_sender;
|
||||
#endif //ENABLE_RTPPROXY
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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::function<void(const std::string &err, const Prot
|
||||
|
||||
// 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
||||
extern const std::string kBroadcastMediaPublish;
|
||||
#define BroadcastMediaPublishArgs \
|
||||
const MediaOriginType &type, const MediaInfo &args, const Broadcast::PublishAuthInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastMediaPublishArgs const MediaOriginType &type, const MediaInfo &args, const Broadcast::PublishAuthInvoker &invoker, SockInfo &sender
|
||||
|
||||
// 播放鉴权结果回调对象
|
||||
// 如果err为空则代表鉴权成功
|
||||
@ -89,14 +83,11 @@ extern const std::string kBroadcastMediaPlayed;
|
||||
|
||||
// shell登录鉴权
|
||||
extern const std::string kBroadcastShellLogin;
|
||||
#define BroadcastShellLoginArgs \
|
||||
const std::string &user_name, const std::string &passwd, const Broadcast::AuthInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastShellLoginArgs const std::string &user_name, const std::string &passwd, const Broadcast::AuthInvoker &invoker, SockInfo &sender
|
||||
|
||||
// 停止rtsp/rtmp/http-flv会话后流量汇报事件广播
|
||||
extern const std::string kBroadcastFlowReport;
|
||||
#define BroadcastFlowReportArgs \
|
||||
const MediaInfo &args, const uint64_t &totalBytes, const uint64_t &totalDuration, const bool &isPlayer, \
|
||||
SockInfo &sender
|
||||
#define BroadcastFlowReportArgs const MediaInfo &args, const uint64_t &totalBytes, const uint64_t &totalDuration, const bool &isPlayer, SockInfo &sender
|
||||
|
||||
// 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了
|
||||
extern const std::string kBroadcastNotFoundStream;
|
||||
@ -114,6 +105,10 @@ extern const std::string kBroadcastSendRtpStopped;
|
||||
extern const std::string kBroadcastReloadConfig;
|
||||
#define BroadcastReloadConfigArgs void
|
||||
|
||||
// rtp server 超时
|
||||
extern const std::string KBroadcastRtpServerTimeout;
|
||||
#define BroadcastRtpServerTimeout uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
|
||||
|
||||
#define ReloadConfigTag ((void *)(0xFF))
|
||||
#define RELOAD_KEY(arg, key) \
|
||||
do { \
|
||||
@ -169,28 +164,12 @@ extern const std::string kStreamNoneReaderDelayMS;
|
||||
extern const std::string kMaxStreamWaitTimeMS;
|
||||
// 是否启动虚拟主机
|
||||
extern const std::string kEnableVhost;
|
||||
// 拉流代理时是否添加静音音频
|
||||
extern const std::string kAddMuteAudio;
|
||||
// 拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
// 如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
extern const std::string kResetWhenRePlay;
|
||||
// 是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const std::string kPublishToHls;
|
||||
// 是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const std::string kPublishToMP4;
|
||||
// 合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
|
||||
// 开启后会同时关闭TCP_NODELAY并开启MSG_MORE
|
||||
extern const std::string kMergeWriteMS;
|
||||
// 全局的时间戳覆盖开关,在转协议时,对frame进行时间戳覆盖
|
||||
extern const std::string kModifyStamp;
|
||||
// 按需转协议的开关
|
||||
extern const std::string kHlsDemand;
|
||||
extern const std::string kRtspDemand;
|
||||
extern const std::string kRtmpDemand;
|
||||
extern const std::string kTSDemand;
|
||||
extern const std::string kFMP4Demand;
|
||||
// 转协议是否全局开启或忽略音频
|
||||
extern const std::string kEnableAudio;
|
||||
// 在docker环境下,不能通过英伟达驱动是否存在来判断是否支持硬件转码
|
||||
extern const std::string kCheckNvidiaDev;
|
||||
// 是否开启ffmpeg日志
|
||||
@ -202,11 +181,49 @@ extern const std::string kWaitTrackReadyMS;
|
||||
extern const std::string kWaitAddTrackMS;
|
||||
// 如果track未就绪,我们先缓存帧数据,但是有最大个数限制(100帧时大约4秒),防止内存溢出
|
||||
extern const std::string kUnreadyFrameCache;
|
||||
// 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
|
||||
// 置0关闭此特性(推流断开会导致立即断开播放器)
|
||||
extern const std::string kContinuePushMS;
|
||||
} // namespace General
|
||||
|
||||
namespace Protocol {
|
||||
//时间戳修复这一路流标志位
|
||||
extern const std::string kModifyStamp;
|
||||
//转协议是否开启音频
|
||||
extern const std::string kEnableAudio;
|
||||
//添加静音音频,在关闭音频时,此开关无效
|
||||
extern const std::string kAddMuteAudio;
|
||||
//断连续推延时,单位毫秒,默认采用配置文件
|
||||
extern const std::string kContinuePushMS;
|
||||
|
||||
//是否开启转换为hls
|
||||
extern const std::string kEnableHls;
|
||||
//是否开启MP4录制
|
||||
extern const std::string kEnableMP4;
|
||||
//是否开启转换为rtsp/webrtc
|
||||
extern const std::string kEnableRtsp;
|
||||
//是否开启转换为rtmp/flv
|
||||
extern const std::string kEnableRtmp;
|
||||
//是否开启转换为http-ts/ws-ts
|
||||
extern const std::string kEnableTS;
|
||||
//是否开启转换为http-fmp4/ws-fmp4
|
||||
extern const std::string kEnableFMP4;
|
||||
|
||||
//是否将mp4录制当做观看者
|
||||
extern const std::string kMP4AsPlayer;
|
||||
//mp4切片大小,单位秒
|
||||
extern const std::string kMP4MaxSecond;
|
||||
//mp4录制保存路径
|
||||
extern const std::string kMP4SavePath;
|
||||
|
||||
//hls录制保存路径
|
||||
extern const std::string kHlsSavePath;
|
||||
|
||||
// 按需转协议的开关
|
||||
extern const std::string kHlsDemand;
|
||||
extern const std::string kRtspDemand;
|
||||
extern const std::string kRtmpDemand;
|
||||
extern const std::string kTSDemand;
|
||||
extern const std::string kFMP4Demand;
|
||||
} // !Protocol
|
||||
|
||||
////////////HTTP配置///////////
|
||||
namespace Http {
|
||||
// http 文件发送缓存大小
|
||||
@ -251,12 +268,13 @@ extern const std::string kKeepAliveSecond;
|
||||
// 假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
|
||||
// 默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
|
||||
extern const std::string kDirectProxy;
|
||||
|
||||
// rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
||||
extern const std::string kLowLatency;
|
||||
} // namespace Rtsp
|
||||
|
||||
////////////RTMP服务器配置///////////
|
||||
namespace Rtmp {
|
||||
// rtmp推流时间戳覆盖开关
|
||||
extern const std::string kModifyStamp;
|
||||
// 握手超时时间,默认15秒
|
||||
extern const std::string kHandshakeSecond;
|
||||
// 维持链接超时时间,默认15秒
|
||||
@ -271,6 +289,8 @@ extern const std::string kVideoMtuSize;
|
||||
extern const std::string kAudioMtuSize;
|
||||
// rtp包最大长度限制, 单位KB
|
||||
extern const std::string kRtpMaxSize;
|
||||
// rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||
extern const std::string kLowLatency;
|
||||
} // namespace Rtp
|
||||
|
||||
////////////组播配置///////////
|
||||
@ -289,18 +309,12 @@ namespace Record {
|
||||
extern const std::string kAppName;
|
||||
// 每次流化MP4文件的时长,单位毫秒
|
||||
extern const std::string kSampleMS;
|
||||
// MP4文件录制大小,默认一个小时
|
||||
extern const std::string kFileSecond;
|
||||
// 录制文件路径
|
||||
extern const std::string kFilePath;
|
||||
// mp4文件写缓存大小
|
||||
extern const std::string kFileBufSize;
|
||||
// mp4录制完成后是否进行二次关键帧索引写入头部
|
||||
extern const std::string kFastStart;
|
||||
// mp4文件是否重头循环读取
|
||||
extern const std::string kFileRepeat;
|
||||
// MP4录制是否当做播放器参与播放人数统计
|
||||
extern const std::string kMP4AsPlayer;
|
||||
} // namespace Record
|
||||
|
||||
////////////HLS相关配置///////////
|
||||
@ -315,8 +329,6 @@ extern const std::string kSegmentKeep;
|
||||
extern const std::string kSegmentRetain;
|
||||
// HLS文件写缓存大小
|
||||
extern const std::string kFileBufSize;
|
||||
// 录制文件路径
|
||||
extern const std::string kFilePath;
|
||||
// 是否广播 ts 切片完成通知
|
||||
extern const std::string kBroadcastRecordTs;
|
||||
// hls直播文件删除延时,单位秒
|
||||
@ -375,6 +387,9 @@ extern const std::string kBeatIntervalMS;
|
||||
extern const std::string kBenchmarkMode;
|
||||
// 播放器在触发播放成功事件时,是否等待所有track ready时再回调
|
||||
extern const std::string kWaitTrackReady;
|
||||
// rtsp播放指定track,可选项有0(不指定,默认)、1(视频)、2(音频)
|
||||
// 设置方法:player[Client::kPlayTrack] = 0/1/2;
|
||||
extern const std::string kPlayTrack;
|
||||
} // namespace Client
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
@ -35,9 +35,9 @@ namespace mediakit {
|
||||
|
||||
//请遵循MIT协议,勿修改服务器声明
|
||||
#if !defined(ENABLE_VERSION)
|
||||
const char kServerName[] = "ZLMediaKit-6.0(build in " __DATE__ " " __TIME__ ")";
|
||||
const char kServerName[] = "ZLMediaKit-7.0(build in " __DATE__ " " __TIME__ ")";
|
||||
#else
|
||||
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH ",branch:" BRANCH_NAME ",build time:" __DATE__ " " __TIME__ ")";
|
||||
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")";
|
||||
#endif
|
||||
|
||||
}//namespace mediakit
|
||||
@ -41,7 +41,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef CHECK
|
||||
#define CHECK(exp, ...) mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
#define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
#endif // CHECK
|
||||
|
||||
#ifndef MAX
|
||||
|
||||
@ -155,7 +155,7 @@ void FrameMerger::doMerge(BufferLikeString &merged, const Frame::Ptr &frame) con
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameMerger::inputFrame(const Frame::Ptr &frame, const onOutput &cb, BufferLikeString *buffer) {
|
||||
bool FrameMerger::inputFrame(const Frame::Ptr &frame, onOutput cb, BufferLikeString *buffer) {
|
||||
if (willFlush(frame)) {
|
||||
Frame::Ptr back = _frame_cache.back();
|
||||
Buffer::Ptr merged_frame = back;
|
||||
@ -190,6 +190,7 @@ bool FrameMerger::inputFrame(const Frame::Ptr &frame, const onOutput &cb, Buffer
|
||||
if (frame->decodeAble()) {
|
||||
_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<FrameWriterInterfaceHelper>;
|
||||
using onWriteFrame = std::function<bool(const Frame::Ptr &frame)>;
|
||||
|
||||
/**
|
||||
* 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<bool(const Frame::Ptr &frame)> cb) {
|
||||
return addDelegate(std::make_shared<FrameWriterInterfaceHelper>(std::move(cb)));
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@ -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<FrameWriterInterfaceHelper> Ptr;
|
||||
typedef std::function<bool(const Frame::Ptr &frame)> 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<std::mutex> lck(_mtx);
|
||||
_delegates.emplace(delegate.get(), delegate);
|
||||
return _delegates.emplace(delegate.get(), std::move(delegate)).first->second.get();
|
||||
}
|
||||
|
||||
FrameWriterInterface* addDelegate(std::function<bool(const Frame::Ptr &frame)> 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::Ptr> _frame_cache;
|
||||
};
|
||||
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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包
|
||||
*/
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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包
|
||||
*/
|
||||
|
||||
@ -31,7 +31,7 @@ public:
|
||||
};
|
||||
|
||||
//FMP4直播源
|
||||
class FMP4MediaSource : public MediaSource, public toolkit::RingDelegate<FMP4Packet::Ptr>, private PacketCache<FMP4Packet>{
|
||||
class FMP4MediaSource final : public MediaSource, public toolkit::RingDelegate<FMP4Packet::Ptr>, private PacketCache<FMP4Packet>{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<FMP4MediaSource>;
|
||||
using RingDataType = std::shared_ptr<toolkit::List<FMP4Packet::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(); }
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
|
||||
@ -18,18 +18,20 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class FMP4MediaSourceMuxer : public MP4MuxerMemory, public MediaSourceEventInterceptor,
|
||||
public std::enable_shared_from_this<FMP4MediaSourceMuxer> {
|
||||
class FMP4MediaSourceMuxer final : public MP4MuxerMemory, public MediaSourceEventInterceptor,
|
||||
public std::enable_shared_from_this<FMP4MediaSourceMuxer> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<FMP4MediaSourceMuxer>;
|
||||
|
||||
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<FMP4MediaSource>(vhost, app, stream_id);
|
||||
}
|
||||
|
||||
~FMP4MediaSourceMuxer() override = default;
|
||||
~FMP4MediaSourceMuxer() override { MP4MuxerMemory::flush(); };
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &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;
|
||||
};
|
||||
|
||||
|
||||
@ -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<HlsPlayerImp> weak_self = static_pointer_cast<HlsPlayerImp>(shared_from_this());
|
||||
if (_decoder) {
|
||||
_decoder->flush();
|
||||
}
|
||||
//等待所有frame flush输出后,再触发onShutdown事件
|
||||
static_pointer_cast<HlsDemuxer>(_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);
|
||||
}
|
||||
|
||||
@ -91,8 +91,8 @@ public:
|
||||
* 获取附加数据
|
||||
*/
|
||||
template <class T>
|
||||
const T& getAttach() const {
|
||||
return *static_cast<const T *>(_attach.get());
|
||||
T& getAttach() {
|
||||
return *static_cast<T *>(_attach.get());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -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<void(const string &err_msg, const HttpServerCookie::Ptr &cookie)> &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<string &>(media_info._streamid), kHlsSuffix, "");
|
||||
}
|
||||
|
||||
weak_ptr<TcpSession> weakSession = sender.shared_from_this();
|
||||
weak_ptr<Session> 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<HttpCookieAttachment>()._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);
|
||||
|
||||
@ -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值
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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<AsyncSenderData> 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<HttpSession>(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) {
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
#define SRC_HTTP_HTTPSESSION_H_
|
||||
|
||||
#include <functional>
|
||||
#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<bool (const char *data,size_t len) > _contentCallBack;
|
||||
};
|
||||
|
||||
using HttpsSession = toolkit::TcpSessionWithSSL<HttpSession>;
|
||||
using HttpsSession = toolkit::SessionWithSSL<HttpSession>;
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
|
||||
@ -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<TsPlayerImp> weak_self = static_pointer_cast<TsPlayerImp>(shared_from_this());
|
||||
if (_decoder) {
|
||||
_decoder->flush();
|
||||
}
|
||||
//等待所有frame flush输出后,再触发onShutdown事件
|
||||
static_pointer_cast<HlsDemuxer>(_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);
|
||||
}
|
||||
|
||||
@ -27,18 +27,18 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* 该类实现了TcpSession派生类发送数据的截取
|
||||
* 该类实现了Session派生类发送数据的截取
|
||||
* 目的是发送业务数据前进行websocket协议的打包
|
||||
*/
|
||||
template <typename TcpSessionType>
|
||||
class TcpSessionTypeImp : public TcpSessionType, public SendInterceptor{
|
||||
template <typename SessionType>
|
||||
class SessionTypeImp : public SessionType, public SendInterceptor{
|
||||
public:
|
||||
typedef std::shared_ptr<TcpSessionTypeImp> Ptr;
|
||||
using Ptr = std::shared_ptr<SessionTypeImp>;
|
||||
|
||||
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 <typename TcpSessionType>
|
||||
class TcpSessionCreator {
|
||||
template <typename SessionType>
|
||||
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<TcpSessionTypeImp<TcpSessionType> >(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<SessionTypeImp<SessionType> >(header,parent,pSock);
|
||||
}
|
||||
};
|
||||
|
||||
@ -228,15 +228,15 @@ private:
|
||||
std::string _payload_cache;
|
||||
std::string _payload_section;
|
||||
std::weak_ptr<toolkit::Server> _weak_server;
|
||||
toolkit::TcpSession::Ptr _session;
|
||||
toolkit::Session::Ptr _session;
|
||||
Creator _creator;
|
||||
};
|
||||
|
||||
|
||||
template<typename TcpSessionType,typename HttpSessionType = mediakit::HttpSession, mediakit::WebSocketHeader::Type DataType = mediakit::WebSocketHeader::TEXT>
|
||||
class WebSocketSession : public WebSocketSessionBase<TcpSessionCreator<TcpSessionType>,HttpSessionType,DataType>{
|
||||
template<typename SessionType,typename HttpSessionType = mediakit::HttpSession, mediakit::WebSocketHeader::Type DataType = mediakit::WebSocketHeader::TEXT>
|
||||
class WebSocketSession : public WebSocketSessionBase<SessionCreator<SessionType>,HttpSessionType,DataType>{
|
||||
public:
|
||||
WebSocketSession(const toolkit::Socket::Ptr &pSock) : WebSocketSessionBase<TcpSessionCreator<TcpSessionType>,HttpSessionType,DataType>(pSock){}
|
||||
WebSocketSession(const toolkit::Socket::Ptr &pSock) : WebSocketSessionBase<SessionCreator<SessionType>,HttpSessionType,DataType>(pSock){}
|
||||
virtual ~WebSocketSession(){}
|
||||
};
|
||||
|
||||
|
||||
@ -112,9 +112,9 @@ bool Demuxer::addTrack(const Track::Ptr &track) {
|
||||
}
|
||||
|
||||
if (_sink->addTrack(track)) {
|
||||
track->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([this](const Frame::Ptr &frame) {
|
||||
track->addDelegate([this](const Frame::Ptr &frame) {
|
||||
return _sink->inputFrame(frame);
|
||||
}));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -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<PlayerProxy> weakSelf = dynamic_pointer_cast<PlayerProxy>(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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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<HlsRecorder> {
|
||||
class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this<HlsRecorder> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<HlsRecorder>;
|
||||
|
||||
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<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
|
||||
// _hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
|
||||
_option = option;
|
||||
_hls = std::make_shared<HlsMakerImpSub>(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<HlsMakerImp> _hls;
|
||||
ProtocolOption _option;
|
||||
std::shared_ptr<HlsMakerImpSub> _hls;
|
||||
bool _isRecord = false;
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<int, track_info> _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<MP4Muxer> Ptr;
|
||||
|
||||
MP4Muxer();
|
||||
MP4Muxer() = default;
|
||||
~MP4Muxer() override;
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user