mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-21 09:07:49 +08:00
Compare commits
25 Commits
df9f3bd8a9
...
7534a70f34
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7534a70f34 | ||
|
|
788e34d848 | ||
|
|
3c85d6414b | ||
|
|
26ef13e959 | ||
|
|
2ce5e97efb | ||
|
|
b4a0e6bdd6 | ||
|
|
dd1e66da6f | ||
|
|
566761b47e | ||
|
|
c496dbf51e | ||
|
|
9420f25b73 | ||
|
|
a54a0b35c7 | ||
|
|
c53730f36c | ||
|
|
128d2a057c | ||
|
|
fb491f3e79 | ||
|
|
ae3d551c8a | ||
|
|
9cc4563fae | ||
|
|
cd8a14d1ca | ||
|
|
a59809047c | ||
|
|
48c37d4f46 | ||
|
|
d0eeba544a | ||
|
|
1191f15132 | ||
|
|
1e6a8964cc | ||
|
|
2cbe4b714b | ||
|
|
5f0edeed6a | ||
|
|
1da300cf3e |
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -10,3 +10,9 @@
|
||||
[submodule "www/webassist"]
|
||||
path = www/webassist
|
||||
url = https://gitee.com/victor1002/zlm_webassist
|
||||
[submodule "3rdpart/pybind11"]
|
||||
path = 3rdpart/pybind11
|
||||
url = https://gitee.com/mirrors/pybind11.git
|
||||
[submodule "python/StreamUI"]
|
||||
path = python/StreamUI
|
||||
url = https://gitee.com/xia-chu/StreamUI.git
|
||||
|
||||
@ -9,4 +9,10 @@
|
||||
url = https://github.com/open-source-parsers/jsoncpp.git
|
||||
[submodule "www/webassist"]
|
||||
path = www/webassist
|
||||
url = https://github.com/1002victor/zlm_webassist
|
||||
url = https://github.com/1002victor/zlm_webassist
|
||||
[submodule "3rdpart/pybind11"]
|
||||
path = 3rdpart/pybind11
|
||||
url = https://github.com/pybind/pybind11.git
|
||||
[submodule "python/StreamUI"]
|
||||
path = python/StreamUI
|
||||
url = https://github.com/xia-chu/StreamUI.git
|
||||
@ -120,4 +120,14 @@ add_subdirectory(ZLToolKit)
|
||||
# 添加库别名
|
||||
add_library(ZLMediaKit::ToolKit ALIAS ZLToolKit)
|
||||
# 添加依赖
|
||||
update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit)
|
||||
update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit)
|
||||
|
||||
##############################################################################
|
||||
|
||||
if (ENABLE_PYTHON)
|
||||
# ============ pybind11 lib ============
|
||||
add_subdirectory(pybind11)
|
||||
update_cached_list(MK_LINK_LIBRARIES pybind11::embed)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/pybind11/include)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_PYTHON)
|
||||
endif ()
|
||||
@ -1 +1 @@
|
||||
Subproject commit 493d14e1682cc0c79e72b3893dd98f865d45b8e9
|
||||
Subproject commit 7302286cf4be39d416b023fec3fd4ca9c54af762
|
||||
1
3rdpart/pybind11
Submodule
1
3rdpart/pybind11
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ed5057ded698e305210269dafa57574ecf964483
|
||||
@ -44,6 +44,7 @@ option(ENABLE_FFMPEG "Enable FFmpeg" OFF)
|
||||
option(ENABLE_HLS "Enable HLS" ON)
|
||||
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
|
||||
option(ENABLE_JEMALLOC_DUMP "Enable jemalloc to dump malloc statistics" OFF)
|
||||
option(ENABLE_TCMALLOC "Enable linking to the tcmalloc library" OFF)
|
||||
option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF)
|
||||
option(ENABLE_MP4 "Enable MP4" ON)
|
||||
option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON)
|
||||
@ -64,6 +65,7 @@ option(USE_SOLUTION_FOLDERS "Enable solution dir supported" ON)
|
||||
option(ENABLE_OBJCOPY "Enable use objcopy to generate debug info file" ON)
|
||||
# 编译静态库
|
||||
option(BUILD_SHARED_LIBS "Build shared instead of static" OFF)
|
||||
option(ENABLE_PYTHON "Enable python plugin" OFF)
|
||||
|
||||
##############################################################################
|
||||
# 设置socket默认缓冲区大小为256k.如果设置为0则不设置socket的默认缓冲区大小,使用系统内核默认值(设置为0仅对linux有效)
|
||||
@ -256,8 +258,8 @@ endif()
|
||||
# Multiple modules depend on ffmpeg related libraries, unified search
|
||||
if(ENABLE_FFMPEG)
|
||||
find_package(PkgConfig QUIET)
|
||||
# 查找 ffmpeg/libutil 是否安装
|
||||
# find ffmpeg/libutil installed
|
||||
# 查找 ffmpeg/libavutil 是否安装
|
||||
# find ffmpeg/libavutil installed
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(AVUTIL QUIET IMPORTED_TARGET libavutil)
|
||||
if(AVUTIL_FOUND)
|
||||
@ -296,8 +298,19 @@ if(ENABLE_FFMPEG)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 查找 ffmpeg/libutil 是否安装
|
||||
# find ffmpeg/libutil installed
|
||||
# 查找 ffmpeg/libavfilter 是否安装
|
||||
# find ffmpeg/libavfilter installed
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(AVFILTER QUIET IMPORTED_TARGET libavfilter)
|
||||
if(AVFILTER_FOUND)
|
||||
update_cached_list(MK_LINK_LIBRARIES PkgConfig::AVFILTER)
|
||||
message(STATUS "found library: ${AVFILTER_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# 查找 ffmpeg/libavutil 是否安装
|
||||
# find ffmpeg/libavutil installed
|
||||
if(NOT AVUTIL_FOUND)
|
||||
find_package(AVUTIL QUIET)
|
||||
if(AVUTIL_FOUND)
|
||||
@ -340,7 +353,16 @@ if(ENABLE_FFMPEG)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(AVUTIL_FOUND AND AVCODEC_FOUND AND SWSCALE_FOUND AND SWRESAMPLE_FOUND)
|
||||
if(NOT AVFILTER_FOUND)
|
||||
find_package(AVFILTER QUIET)
|
||||
if(AVFILTER_FOUND)
|
||||
include_directories(SYSTEM ${AVFILTER_INCLUDE_DIR})
|
||||
update_cached_list(MK_LINK_LIBRARIES ${AVFILTER_LIBRARIES})
|
||||
message(STATUS "found library: ${AVFILTER_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(AVUTIL_FOUND AND AVCODEC_FOUND AND SWSCALE_FOUND AND SWRESAMPLE_FOUND AND AVFILTER_FOUND)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_FFMPEG)
|
||||
update_cached_list(MK_LINK_LIBRARIES ${CMAKE_DL_LIBS})
|
||||
else()
|
||||
@ -401,6 +423,19 @@ if(JEMALLOC_FOUND)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# 查找 tcmalloc 是否安装
|
||||
# find tcmalloc installed
|
||||
if(ENABLE_TCMALLOC)
|
||||
find_package(TCMALLOC QUIET)
|
||||
if(TCMALLOC_FOUND)
|
||||
message(STATUS "Link with tcmalloc library: ${TCMALLOC_LIBRARIES}")
|
||||
update_cached_list(MK_LINK_LIBRARIES ${TCMALLOC_LIBRARIES})
|
||||
else()
|
||||
set(ENABLE_TCMALLOC OFF)
|
||||
message(WARNING "tcmalloc 相关功能未找到")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 查找 openssl 是否安装
|
||||
# find openssl installed
|
||||
find_package(OpenSSL QUIET)
|
||||
@ -569,7 +604,19 @@ endif ()
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/www" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/default.pem" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
if (ENABLE_PYTHON)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/python" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(GLOB FRONTEND_FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/python/StreamUI/frontend/*"
|
||||
)
|
||||
file(COPY ${FRONTEND_FILES}
|
||||
DESTINATION "${EXECUTABLE_OUTPUT_PATH}/www/StreamUI/"
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (ENABLE_FFMPEG)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/DejaVuSans.ttf" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
endif ()
|
||||
# 拷贝VideoStack 无视频流时默认填充的背景图片
|
||||
# Copy the default background image used by VideoStack when there is no video stream
|
||||
if (ENABLE_VIDEOSTACK AND ENABLE_FFMPEG AND ENABLE_X264)
|
||||
|
||||
BIN
DejaVuSans.ttf
Normal file
BIN
DejaVuSans.ttf
Normal file
Binary file not shown.
@ -327,6 +327,7 @@ API_EXPORT uint16_t API_CALL mk_ice_server_start(uint16_t port){
|
||||
iceServer_udp = std::make_shared<UdpServer>();
|
||||
iceServer_udp->start<IceSession>(port);
|
||||
iceServer_tcp->start<IceSession>(port);
|
||||
return 0;
|
||||
} catch (std::exception &ex) {
|
||||
iceServer_udp = nullptr;
|
||||
iceServer_tcp = nullptr;
|
||||
|
||||
16
cmake/FindAVFILTER.cmake
Normal file
16
cmake/FindAVFILTER.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
find_path(AVFILTER_INCLUDE_DIR
|
||||
NAMES libavfilter/avfilter.h
|
||||
HINTS ${FFMPEG_PATH_ROOT}
|
||||
PATH_SUFFIXES include)
|
||||
|
||||
find_library(AVFILTER_LIBRARY
|
||||
NAMES avfilter
|
||||
HINTS ${FFMPEG_PATH_ROOT}
|
||||
PATH_SUFFIXES bin lib)
|
||||
|
||||
set(AVFILTER_LIBRARIES ${AVFILTER_LIBRARY})
|
||||
set(AVFILTER_INCLUDE_DIRS ${AVFILTER_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(AVFILTER DEFAULT_MSG AVFILTER_LIBRARY AVFILTER_INCLUDE_DIR)
|
||||
16
cmake/FindTCMALLOC.cmake
Normal file
16
cmake/FindTCMALLOC.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
find_path(Tcmalloc_INCLUDE_DIR
|
||||
NAMES google/tcmalloc.h
|
||||
)
|
||||
|
||||
find_library(Tcmalloc_LIBRARY
|
||||
NAMES tcmalloc_minimal tcmalloc
|
||||
)
|
||||
|
||||
set(TCMALLOC_LIBRARIES ${Tcmalloc_LIBRARY})
|
||||
set(TCMALLOC_INCLUDE_DIRS ${Tcmalloc_INCLUDE_DIR})
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(TCMALLOC
|
||||
DEFAULT_MSG
|
||||
TCMALLOC_LIBRARIES TCMALLOC_INCLUDE_DIRS
|
||||
)
|
||||
@ -412,10 +412,14 @@ nackMaxMS=3000
|
||||
nackMaxCount=15
|
||||
#nack重传频率,rtt的倍数
|
||||
nackIntervalRatio=1.0
|
||||
#nack包中rtp个数,减小此值可以让nack包响应更灵敏
|
||||
#视频nack包中rtp个数,减小此值可以让nack包响应更灵敏
|
||||
nackRtpSize=8
|
||||
#音频nack包中rtp个数,减小此值可以让nack包响应更灵敏
|
||||
nackAudioRtpSize=4
|
||||
#是否尝试过滤 b帧
|
||||
bfilter=0
|
||||
# 是否优先采用webrtc over tcp模式
|
||||
preferred_tcp=0
|
||||
|
||||
[srt]
|
||||
#srt播放推流、播放超时时间,单位秒
|
||||
|
||||
@ -57,7 +57,7 @@ public:
|
||||
toolkit::Buffer::Ptr getExtraData() const override;
|
||||
void setExtraData(const uint8_t *data, size_t size) override;
|
||||
protected:
|
||||
aom_av1_t _context = {0};
|
||||
aom_av1_t _context {};
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
@ -20,7 +20,7 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
Buffer::Ptr G711Track::getExtraData() const {
|
||||
struct wave_format_t wav = {0};
|
||||
struct wave_format_t wav {};
|
||||
wav.wFormatTag = getCodecId() == CodecG711A ? WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW;
|
||||
wav.nChannels = getAudioChannel();
|
||||
wav.nSamplesPerSec = getAudioSampleRate();
|
||||
|
||||
@ -28,7 +28,7 @@ void OpusTrack::setExtraData(const uint8_t *data, size_t size) {
|
||||
}
|
||||
|
||||
Buffer::Ptr OpusTrack::getExtraData() const {
|
||||
struct opus_head_t opus = { 0 };
|
||||
struct opus_head_t opus {};
|
||||
opus.version = 1;
|
||||
opus.channels = getAudioChannel();
|
||||
opus.input_sample_rate = getAudioSampleRate();
|
||||
|
||||
@ -41,7 +41,7 @@ public:
|
||||
toolkit::Buffer::Ptr getExtraData() const override;
|
||||
void setExtraData(const uint8_t *data, size_t size) override;
|
||||
private:
|
||||
webm_vpx_t _vpx = {0};
|
||||
webm_vpx_t _vpx {};
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
@ -334,7 +334,7 @@ bool VP8RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
bool key = frame->keyFrame();
|
||||
bool mark = false;
|
||||
for (size_t pos = 0; pos < len; pos += pdu_size) {
|
||||
if (len - pos <= pdu_size) {
|
||||
if (static_cast<int>(len - pos) <= pdu_size) {
|
||||
pdu_size = len - pos;
|
||||
mark = true;
|
||||
}
|
||||
|
||||
@ -56,9 +56,6 @@ public:
|
||||
using Ptr = std::shared_ptr<VP8RtpEncoder>;
|
||||
|
||||
bool inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
private:
|
||||
uint16_t _pic_id = 0;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@ -41,7 +41,7 @@ public:
|
||||
toolkit::Buffer::Ptr getExtraData() const override;
|
||||
void setExtraData(const uint8_t *data, size_t size) override;
|
||||
private:
|
||||
webm_vpx_t _vpx = {0};
|
||||
webm_vpx_t _vpx {};
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
@ -297,7 +297,7 @@ bool VP9RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
int pdu_size = getRtpInfo().getMaxSize() - nheader;
|
||||
|
||||
bool mark = false;
|
||||
for (size_t pos = 0; pos < len; pos += pdu_size) {
|
||||
for (int pos = 0; pos < len; pos += pdu_size) {
|
||||
if (len - pos <= pdu_size) {
|
||||
pdu_size = len - pos;
|
||||
header[0] |= kEBit;
|
||||
|
||||
1
python/StreamUI
Submodule
1
python/StreamUI
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d055e925a5c061d3ce785f60f07b59ded08b4e55
|
||||
27
python/mk_logger.py
Normal file
27
python/mk_logger.py
Normal file
@ -0,0 +1,27 @@
|
||||
import inspect
|
||||
|
||||
try:
|
||||
import mk_loader
|
||||
USE_PLUGIN_LOGGER = True
|
||||
except ImportError:
|
||||
USE_PLUGIN_LOGGER = False
|
||||
|
||||
def _do_log(level: int, *args):
|
||||
frame_info = inspect.stack()[2]
|
||||
filename = frame_info.filename
|
||||
lineno = frame_info.lineno
|
||||
funcname = frame_info.function
|
||||
|
||||
# 把所有参数转成字符串后用空格拼接
|
||||
msg = " ".join(str(arg) for arg in args)
|
||||
|
||||
if USE_PLUGIN_LOGGER:
|
||||
mk_loader.log(level, filename, lineno, funcname, msg)
|
||||
else:
|
||||
print(f"[{filename}:{lineno}] {funcname} | {msg}")
|
||||
|
||||
def log_trace(*args): _do_log(0, *args)
|
||||
def log_debug(*args): _do_log(1, *args)
|
||||
def log_info(*args): _do_log(2, *args)
|
||||
def log_warn(*args): _do_log(3, *args)
|
||||
def log_error(*args): _do_log(4, *args)
|
||||
89
python/mk_plugin.py
Normal file
89
python/mk_plugin.py
Normal file
@ -0,0 +1,89 @@
|
||||
import mk_logger
|
||||
import mk_loader
|
||||
import asyncio
|
||||
import threading
|
||||
from StreamUI.backend.main import app
|
||||
from starlette.routing import Match
|
||||
|
||||
def start_background_loop(loop):
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_forever()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
threading.Thread(target=start_background_loop, args=(loop,), daemon=True).start()
|
||||
|
||||
def submit_coro(scope, body, send):
|
||||
async def run():
|
||||
# 包装 send 函数,确保它总是可等待的
|
||||
async def async_send(message):
|
||||
# 调用原始的 send 函数,它现在应该返回一个协程
|
||||
result = send(message)
|
||||
if result is not None:
|
||||
await result
|
||||
|
||||
async def receive():
|
||||
return {
|
||||
"type": "http.request",
|
||||
"body": body,
|
||||
"more_body": False,
|
||||
}
|
||||
|
||||
try:
|
||||
await app(scope, receive, async_send)
|
||||
except Exception as e:
|
||||
mk_logger.log_warn(f"FastAPI failed: {e}")
|
||||
# 发送错误响应
|
||||
await async_send({
|
||||
"type": "http.response.start",
|
||||
"status": 500,
|
||||
"headers": [(b"content-type", b"text/plain")],
|
||||
})
|
||||
await async_send({
|
||||
"type": "http.response.body",
|
||||
"body": b"Internal Server Error",
|
||||
"more_body": False,
|
||||
})
|
||||
return asyncio.run_coroutine_threadsafe(run(), loop)
|
||||
|
||||
def check_route(scope) -> bool:
|
||||
for route in app.routes:
|
||||
if hasattr(route, "matches"):
|
||||
match, _ = route.matches(scope)
|
||||
if match == Match.FULL:
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_start():
|
||||
mk_logger.log_info(f"on_start, secret: {mk_loader.get_config('api.secret')}")
|
||||
# mk_loader.set_config('api.secret', "new_secret_from_python")
|
||||
# mk_loader.update_config()
|
||||
mk_loader.set_fastapi(check_route, submit_coro)
|
||||
|
||||
def on_exit():
|
||||
mk_logger.log_info("on_exit")
|
||||
|
||||
def on_publish(type: str, args: dict, invoker, sender: dict) -> bool:
|
||||
mk_logger.log_info(f"args: {type}, args: {args}, sender: {sender}")
|
||||
# opt 控制转协议,请参考配置文件[protocol]下字段
|
||||
opt = {
|
||||
"enable_rtmp": "1"
|
||||
}
|
||||
# 响应推流鉴权结果
|
||||
mk_loader.publish_auth_invoker_do(invoker, "", opt);
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
|
||||
def on_play(args: dict, invoker, sender: dict) -> bool:
|
||||
mk_logger.log_info(f"args: {args}, sender: {sender}")
|
||||
# 响应播放鉴权结果
|
||||
mk_loader.auth_invoker_do(invoker, "");
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
|
||||
def on_flow_report(args: dict, totalBytes: int, totalDuration: int, isPlayer: bool, sender: dict) -> bool:
|
||||
mk_logger.log_info(f"args: {args}, totalBytes: {totalBytes}, totalDuration: {totalDuration}, isPlayer: {isPlayer}, sender: {sender}")
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
|
||||
def on_reload_config():
|
||||
mk_logger.log_info(f"on_reload_config")
|
||||
@ -50,6 +50,10 @@ target_compile_definitions(MediaServer
|
||||
target_compile_options(MediaServer
|
||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
|
||||
if(MINGW)
|
||||
update_cached_list(MK_LINK_LIBRARIES dbghelp)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
target_link_libraries(MediaServer -Wl,--start-group ${MK_LINK_LIBRARIES} -Wl,--end-group)
|
||||
else()
|
||||
|
||||
@ -115,8 +115,6 @@ static void responseApi(int code, const string &msg, const HttpSession::HttpResp
|
||||
responseApi(res, invoker);
|
||||
}
|
||||
|
||||
static ApiArgsType getAllArgs(const Parser &parser);
|
||||
|
||||
static HttpApi toApi(const function<void(API_ARGS_MAP_ASYNC)> &cb) {
|
||||
return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
@ -215,7 +213,7 @@ void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYN
|
||||
|
||||
// 获取HTTP请求中url参数、content参数 [AUTO-TRANSLATED:d161a1e1]
|
||||
// Get URL parameters and content parameters from the HTTP request
|
||||
static ApiArgsType getAllArgs(const Parser &parser) {
|
||||
ApiArgsType getAllArgs(const Parser &parser) {
|
||||
ApiArgsType allArgs;
|
||||
if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
|
||||
auto contentArgs = parser.parseArgs(parser.content());
|
||||
@ -345,7 +343,7 @@ 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) {
|
||||
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();
|
||||
@ -1079,6 +1077,7 @@ void installWebApi() {
|
||||
}
|
||||
fillSockInfo(jsession, session.get());
|
||||
jsession["id"] = id;
|
||||
jsession["type"] = session->getSock()->sockType() == SockNum::Sock_TCP ? "tcp" : "udp";
|
||||
jsession["typeid"] = toolkit::demangle(typeid(*session).name());
|
||||
val["data"].append(jsession);
|
||||
});
|
||||
|
||||
@ -238,6 +238,7 @@ uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, i
|
||||
#endif
|
||||
|
||||
Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
|
||||
ApiArgsType getAllArgs(const mediakit::Parser &parser);
|
||||
void getStatisticJson(const std::function<void(Json::Value &val)> &cb);
|
||||
void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count,
|
||||
const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args,
|
||||
|
||||
@ -18,9 +18,14 @@
|
||||
#include "Http/HttpRequester.h"
|
||||
#include "Network/Session.h"
|
||||
#include "Rtsp/RtspSession.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "WebHook.h"
|
||||
#include "WebApi.h"
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
#include "pyinvoker.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
@ -226,7 +231,7 @@ void do_http_hook(const string &url, const ArgsType &body, const function<void(c
|
||||
|
||||
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item);
|
||||
|
||||
static ArgsType make_json(const MediaInfo &args) {
|
||||
ArgsType make_json(const MediaInfo &args) {
|
||||
ArgsType body;
|
||||
body["schema"] = args.schema;
|
||||
if(!args.protocol.empty()){
|
||||
@ -358,6 +363,12 @@ void installWebHook() {
|
||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_publish(type, args, invoker, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
|
||||
if (!hook_enable || hook_publish.empty()) {
|
||||
invoker("", ProtocolOption());
|
||||
@ -387,6 +398,11 @@ void installWebHook() {
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_play(args, invoker, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
GET_CONFIG(string, hook_play, Hook::kOnPlay);
|
||||
if (!hook_enable || hook_play.empty()) {
|
||||
invoker("");
|
||||
@ -402,6 +418,11 @@ void installWebHook() {
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_flow_report(args, totalBytes, totalDuration, isPlayer, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
|
||||
if (!hook_enable || hook_flowreport.empty()) {
|
||||
return;
|
||||
@ -480,10 +501,6 @@ void installWebHook() {
|
||||
// 监听rtsp、rtmp源注册或注销事件 [AUTO-TRANSLATED:6396afa8]
|
||||
// Listen to rtsp, rtmp source registration or deregistration events
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
|
||||
GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged);
|
||||
if (!hook_enable || hook_stream_changed.empty()) {
|
||||
return;
|
||||
}
|
||||
GET_CONFIG_FUNC(std::set<std::string>, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) {
|
||||
std::set<std::string> ret;
|
||||
auto vec = split(str, "/");
|
||||
@ -500,6 +517,15 @@ void installWebHook() {
|
||||
// This protocol registration deregistration event is ignored
|
||||
return;
|
||||
}
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_media_changed(bRegist, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged);
|
||||
if (!hook_enable || hook_stream_changed.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArgsType body;
|
||||
if (bRegist) {
|
||||
@ -779,6 +805,14 @@ void installWebHook() {
|
||||
do_http_hook(rtp_server_timeout, body);
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastPlayerProxyFailed, [](BroadcastPlayerProxyFailedArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_player_proxy_failed(sender, ex)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
// 汇报服务器重新启动 [AUTO-TRANSLATED:bd7d83df]
|
||||
// Report server restart
|
||||
reportServerStarted();
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
#include "ZLMVersion.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
#include "pyinvoker.h"
|
||||
#endif
|
||||
|
||||
#include "System.h"
|
||||
|
||||
using namespace std;
|
||||
@ -107,6 +111,14 @@ onceToken token1([](){
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
namespace Python {
|
||||
#define Python_FIELD "python."
|
||||
const string kPlugin = Python_FIELD"plugin";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPlugin] = "mk_plugin";
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
@ -261,6 +273,16 @@ int start_main(int argc,char *argv[]) {
|
||||
}
|
||||
#endif //! defined(_WIN32)
|
||||
|
||||
// 设置poller线程数和cpu亲和性,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效 [AUTO-TRANSLATED:7f03a1e5]
|
||||
// Set the number of poller threads and CPU affinity. This function must be called before using ZLToolKit network related objects to take effect.
|
||||
// 如果需要调用getSnap和addFFmpegSource接口,可以关闭cpu亲和性 [AUTO-TRANSLATED:7629f7bc]
|
||||
// If you need to call the getSnap and addFFmpegSource interfaces, you can turn off CPU affinity
|
||||
|
||||
EventPollerPool::setPoolSize(threads);
|
||||
WorkThreadPool::setPoolSize(threads);
|
||||
EventPollerPool::enableCpuAffinity(affinity);
|
||||
WorkThreadPool::enableCpuAffinity(affinity);
|
||||
|
||||
// 开启崩溃捕获等 [AUTO-TRANSLATED:9c7c759c]
|
||||
// Enable crash capture, etc.
|
||||
System::systemSetup();
|
||||
@ -317,15 +339,6 @@ int start_main(int argc,char *argv[]) {
|
||||
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
|
||||
uint16_t rtpPort = mINI::Instance()[RtpProxy::kPort];
|
||||
|
||||
// 设置poller线程数和cpu亲和性,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效 [AUTO-TRANSLATED:7f03a1e5]
|
||||
// Set the number of poller threads and CPU affinity. This function must be called before using ZLToolKit network related objects to take effect.
|
||||
// 如果需要调用getSnap和addFFmpegSource接口,可以关闭cpu亲和性 [AUTO-TRANSLATED:7629f7bc]
|
||||
// If you need to call the getSnap and addFFmpegSource interfaces, you can turn off CPU affinity
|
||||
|
||||
EventPollerPool::setPoolSize(threads);
|
||||
WorkThreadPool::setPoolSize(threads);
|
||||
EventPollerPool::enableCpuAffinity(affinity);
|
||||
|
||||
// 简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象 [AUTO-TRANSLATED:f9324c6e]
|
||||
// Simple telnet server, can be used for server debugging, but cannot use port 23, otherwise telnet will have inexplicable phenomena
|
||||
// 测试方法:telnet 127.0.0.1 9000 [AUTO-TRANSLATED:de0ac883]
|
||||
@ -494,12 +507,25 @@ int start_main(int argc,char *argv[]) {
|
||||
g_reload_certificates();
|
||||
});
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
// 初始化python解释器
|
||||
auto &ref = PythonInvoker::Instance();
|
||||
auto py_plugin = mINI::Instance()[Python::kPlugin];
|
||||
if (!py_plugin.empty()) {
|
||||
ref.load(py_plugin);
|
||||
}
|
||||
#endif
|
||||
sem.wait();
|
||||
}
|
||||
unInstallWebApi();
|
||||
unInstallWebHook();
|
||||
onProcessExited();
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
PythonInvoker::release();
|
||||
#endif
|
||||
|
||||
// 休眠1秒再退出,防止资源释放顺序错误 [AUTO-TRANSLATED:1b11a74f]
|
||||
// sleep for 1 second before exiting, to prevent resource release order errors
|
||||
InfoL << "程序退出中,请等待...";
|
||||
|
||||
409
server/pyinvoker.cpp
Normal file
409
server/pyinvoker.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
#if defined(ENABLE_PYTHON)
|
||||
|
||||
#include "pyinvoker.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include "WebApi.h"
|
||||
#include "WebHook.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/File.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Http/HttpSession.h"
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
extern ArgsType make_json(const MediaInfo &args);
|
||||
extern void fillSockInfo(Json::Value & val, SockInfo* info);
|
||||
extern std::string g_ini_file;
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_copy_constructible<T>::value, py::capsule>::type to_python(const T &obj) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
auto p = new toolkit::Any(std::make_shared<T>(obj));
|
||||
return py::capsule(p, name_str.data(), [](PyObject *capsule) {
|
||||
auto p = reinterpret_cast<toolkit::Any *>(PyCapsule_GetPointer(capsule, name_str.data()));
|
||||
delete p;
|
||||
TraceL << "delete " << name_str << "(" << p << ")";
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_copy_constructible<T>::value, py::capsule>::type to_python(const T &obj) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
auto p = new toolkit::Any(std::shared_ptr<T>(const_cast<T *>(&obj), [](T *) {}));
|
||||
return py::capsule(p, name_str.data(), [](PyObject *capsule) {
|
||||
auto p = reinterpret_cast<toolkit::Any *>(PyCapsule_GetPointer(capsule, name_str.data()));
|
||||
delete p;
|
||||
TraceL << "unref " << name_str << "(" << p << ")";
|
||||
});
|
||||
}
|
||||
|
||||
static py::dict jsonToPython(const Json::Value &obj) {
|
||||
py::dict ret;
|
||||
if (obj.isObject()) {
|
||||
for (auto it = obj.begin(); it != obj.end(); ++it) {
|
||||
if (it->isNull()) {
|
||||
// 忽略null,修复wvp传null覆盖Protocol配置的问题
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto str = (*it).asString();
|
||||
ret[it.name().data()] = std::move(str);
|
||||
} catch (std::exception &) {
|
||||
WarnL << "Json is not convertible to string, key: " << it.name() << ", value: " << (*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
py::dict to_python(const MediaInfo &args) {
|
||||
auto json = make_json(args);
|
||||
return jsonToPython(json);
|
||||
}
|
||||
|
||||
py::dict to_python(const SockInfo &info) {
|
||||
Json::Value json;
|
||||
fillSockInfo(json, const_cast<SockInfo *>(&info));
|
||||
return jsonToPython(json);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> to_python2(const T &t) {
|
||||
return std::shared_ptr<T>(const_cast<T *>(&t), py::nodelete());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &to_native(const py::capsule &cap) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
if (std::string(cap.name()) != name_str) {
|
||||
throw std::runtime_error("Invalid capsule name!");
|
||||
}
|
||||
auto any = static_cast<toolkit::Any *>(cap.get_pointer());
|
||||
return any->get<T>();
|
||||
}
|
||||
|
||||
mINI to_native(const py::dict &opt) {
|
||||
mINI ret;
|
||||
for (auto &item : opt) {
|
||||
// 转换为字符串(允许 int/float/bool 等)
|
||||
ret.emplace(py::str(item.first).cast<std::string>(), py::str(item.second).cast<std::string>());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void handle_http_request(const py::object &check_route, const py::object &submit_coro, const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, toolkit::SockInfo &sender) {
|
||||
py::gil_scoped_acquire guard;
|
||||
|
||||
py::dict scope;
|
||||
scope["type"] = "http";
|
||||
scope["http_version"] = "1.1";
|
||||
scope["method"] = parser.method();
|
||||
scope["path"] = parser.url();
|
||||
scope["query_string"] = parser.params();
|
||||
py::list hdrs;
|
||||
for (auto &kv : parser.getHeader()) {
|
||||
hdrs.append(py::make_tuple(py::bytes(kv.first), py::bytes(kv.second)));
|
||||
}
|
||||
scope["headers"] = hdrs;
|
||||
|
||||
bool ok = check_route(scope).cast<bool>();
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
consumed = true;
|
||||
|
||||
// http api被python拦截了,再api统一鉴权
|
||||
try {
|
||||
auto args = getAllArgs(parser);
|
||||
auto allArgs = ArgsMap(parser, args);
|
||||
GET_CONFIG(std::string, api_secret, API::kSecret);
|
||||
CHECK_SECRET(); // 检测secret
|
||||
} catch (std::exception &ex) {
|
||||
Json::Value val;
|
||||
val["code"] = API::Exception;
|
||||
val["msg"] = ex.what();
|
||||
HttpSession::KeyValue headerOut;
|
||||
headerOut["Content-Type"] = "application/json";
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
return;
|
||||
}
|
||||
|
||||
StrCaseMap resp_headers;
|
||||
std::string resp_body;
|
||||
int status = 500;
|
||||
auto send = py::cpp_function([invoker, status, resp_body, resp_headers](const py::dict &msg) mutable {
|
||||
auto type = msg["type"].cast<std::string>();
|
||||
if (type == "http.response.start") {
|
||||
status = msg["status"].cast<int>();
|
||||
for (auto tup : msg["headers"].cast<py::list>()) {
|
||||
auto t = tup.cast<py::tuple>();
|
||||
resp_headers[t[0].cast<std::string>()] = t[1].cast<std::string>();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "http.response.body") {
|
||||
resp_body += msg["body"].cast<std::string>();
|
||||
// 💥 只在 more_body=False 时回调
|
||||
bool more = msg.contains("more_body") && msg["more_body"].cast<bool>();
|
||||
if (!more) {
|
||||
invoker(status, resp_headers, resp_body);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
submit_coro(scope, py::bytes(parser.content()), send);
|
||||
}
|
||||
|
||||
|
||||
PYBIND11_EMBEDDED_MODULE(mk_loader, m) {
|
||||
m.def("log", [](int lev, const char *file, int line, const char *func, const char *content) {
|
||||
py::gil_scoped_release release;
|
||||
LoggerWrapper::printLog(::toolkit::getLogger(), lev, file, func, line, content);
|
||||
});
|
||||
|
||||
m.def("get_config", [](const std::string &key) -> std::string {
|
||||
py::gil_scoped_release release;
|
||||
const auto it = mINI::Instance().find(key);
|
||||
if (it != mINI::Instance().end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
m.def("get_full_path", [](const std::string &path, const std::string ¤t_path) -> std::string {
|
||||
py::gil_scoped_release release;
|
||||
return File::absolutePath(path, current_path);
|
||||
});
|
||||
|
||||
m.def("set_config", [](const std::string &key, const std::string &value) -> bool {
|
||||
py::gil_scoped_release release;
|
||||
mINI::Instance()[key]= value;
|
||||
return true;
|
||||
});
|
||||
|
||||
m.def("update_config", []() {
|
||||
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||
mINI::Instance().dumpFile(g_ini_file);
|
||||
return true;
|
||||
});
|
||||
|
||||
m.def("publish_auth_invoker_do", [](const py::capsule &cap, const std::string &err, const py::dict &opt) {
|
||||
ProtocolOption option;
|
||||
option.load(to_native(opt));
|
||||
// 执行c++代码时释放gil锁
|
||||
py::gil_scoped_release release;
|
||||
auto &invoker = to_native<Broadcast::PublishAuthInvoker>(cap);
|
||||
invoker(err, option);
|
||||
});
|
||||
|
||||
m.def("auth_invoker_do", [](const py::capsule &cap, const std::string &err) {
|
||||
// 执行c++代码时释放gil锁
|
||||
py::gil_scoped_release release;
|
||||
auto &invoker = to_native<Broadcast::AuthInvoker>(cap);
|
||||
invoker(err);
|
||||
});
|
||||
|
||||
m.def("set_fastapi", [](const py::object &check_route, const py::object &submit_coro) {
|
||||
static void *fastapi_tag = nullptr;
|
||||
NoticeCenter::Instance().delListener(&fastapi_tag, Broadcast::kBroadcastHttpRequest);
|
||||
NoticeCenter::Instance().addListener(&fastapi_tag, Broadcast::kBroadcastHttpRequest, [check_route, submit_coro](BroadcastHttpRequestArgs) {
|
||||
handle_http_request(check_route, submit_coro, parser, invoker, consumed, sender);
|
||||
});
|
||||
});
|
||||
|
||||
py::enum_<TrackType>(m, "TrackType")
|
||||
.value("Invalid", TrackInvalid)
|
||||
.value("Video", TrackVideo)
|
||||
.value("Audio", TrackAudio)
|
||||
.value("Title", TrackTitle)
|
||||
.value("Application", TrackApplication)
|
||||
.export_values();
|
||||
|
||||
py::class_<MediaSource, MediaSource::Ptr>(m, "MediaSource")
|
||||
.def("getSchema", &MediaSource::getSchema)
|
||||
.def("getUrl", &MediaSource::getUrl)
|
||||
.def("getMediaTuple", &MediaSource::getMediaTuple)
|
||||
.def("getTimeStamp", &MediaSource::getTimeStamp)
|
||||
.def("setTimeStamp", &MediaSource::setTimeStamp)
|
||||
.def("getBytesSpeed", &MediaSource::getBytesSpeed)
|
||||
.def("getTotalBytes", &MediaSource::getTotalBytes)
|
||||
.def("getCreateStamp", &MediaSource::getCreateStamp)
|
||||
.def("getAliveSecond", &MediaSource::getAliveSecond)
|
||||
.def("readerCount", &MediaSource::readerCount)
|
||||
.def("totalReaderCount", &MediaSource::totalReaderCount)
|
||||
.def("getOriginType", &MediaSource::getOriginType)
|
||||
.def("getOriginUrl", &MediaSource::getOriginUrl)
|
||||
.def("getOriginSock", &MediaSource::getOriginSock)
|
||||
.def("seekTo", &MediaSource::seekTo)
|
||||
.def("pause", &MediaSource::pause)
|
||||
.def("speed", &MediaSource::speed)
|
||||
.def("close", &MediaSource::close)
|
||||
.def("setupRecord", &MediaSource::setupRecord)
|
||||
.def("isRecording", &MediaSource::isRecording)
|
||||
.def("stopSendRtp", &MediaSource::stopSendRtp)
|
||||
.def("getLossRate", &MediaSource::getLossRate);
|
||||
|
||||
py::class_<MediaTuple, std::shared_ptr<MediaTuple>>(m, "MediaTuple")
|
||||
.def_readwrite("vhost", &MediaTuple::vhost)
|
||||
.def_readwrite("app", &MediaTuple::app)
|
||||
.def_readwrite("stream", &MediaTuple::stream)
|
||||
.def_readwrite("params", &MediaTuple::params)
|
||||
.def("shortUrl", &MediaTuple::shortUrl);
|
||||
|
||||
py::class_<SockException, std::shared_ptr<SockException>>(m, "SockException").def("what", &SockException::what).def("code", &SockException::getErrCode);
|
||||
}
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
inline bool set_env(const char *name, const char *value) {
|
||||
#if defined(_WIN32)
|
||||
std::string env_str = std::string(name) + "=" + value;
|
||||
return _putenv(env_str.c_str()) == 0;
|
||||
#else
|
||||
return setenv(name, value, 1) == 0; // overwrite = 1
|
||||
#endif
|
||||
}
|
||||
|
||||
bool set_python_path() {
|
||||
const char *env_var = std::getenv("PYTHONPATH");
|
||||
if (env_var && *env_var) {
|
||||
PrintI("PYTHONPATH is already set to: %s", env_var);
|
||||
return false;
|
||||
}
|
||||
auto default_path = exeDir() + "/python";
|
||||
// 1 表示覆盖已存在的值
|
||||
if (!set_env("PYTHONPATH", default_path.data())) {
|
||||
PrintW("Failed to set PYTHONPATH");
|
||||
return false;
|
||||
}
|
||||
PrintI("PYTHONPATH was not set. Set to default: %s", default_path.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::shared_ptr<PythonInvoker> g_instance;
|
||||
|
||||
PythonInvoker &PythonInvoker::Instance() {
|
||||
static toolkit::onceToken s_token([]() {
|
||||
g_instance.reset(new PythonInvoker);
|
||||
});
|
||||
|
||||
return *g_instance;
|
||||
}
|
||||
|
||||
void PythonInvoker::release() {
|
||||
g_instance = nullptr;
|
||||
}
|
||||
|
||||
PythonInvoker::PythonInvoker() {
|
||||
// 确保日志一直可用
|
||||
_logger = Logger::Instance().shared_from_this();
|
||||
set_python_path(); // 确保 PYTHONPATH 在第一次调用时设置
|
||||
_interpreter = new py::scoped_interpreter;
|
||||
_rel = new py::gil_scoped_release;
|
||||
|
||||
NoticeCenter::Instance().addListener(this, Broadcast::kBroadcastReloadConfig, [this] (BroadcastReloadConfigArgs) {
|
||||
py::gil_scoped_acquire guard;
|
||||
if (_on_reload_config) {
|
||||
_on_reload_config();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PythonInvoker::~PythonInvoker() {
|
||||
NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastReloadConfig);
|
||||
{
|
||||
py::gil_scoped_acquire gil; // 加锁
|
||||
if (_on_exit) {
|
||||
_on_exit();
|
||||
}
|
||||
_on_exit = py::object();
|
||||
_on_publish = py::object();
|
||||
_on_play = py::object();
|
||||
_on_flow_report = py::object();
|
||||
_on_reload_config = py::object();
|
||||
_on_media_changed = py::object();
|
||||
_on_player_proxy_failed = py::object();
|
||||
_module = py::module();
|
||||
}
|
||||
delete _rel;
|
||||
delete _interpreter;
|
||||
}
|
||||
|
||||
#define GET_FUNC(instance, name) \
|
||||
if (hasattr(instance, #name)) { \
|
||||
_##name = instance.attr(#name); \
|
||||
}
|
||||
|
||||
void PythonInvoker::load(const std::string &module_name) {
|
||||
try {
|
||||
py::gil_scoped_acquire gil; // 加锁
|
||||
_module = py::module::import(module_name.c_str());
|
||||
GET_FUNC(_module, on_exit);
|
||||
GET_FUNC(_module, on_publish);
|
||||
GET_FUNC(_module, on_play);
|
||||
GET_FUNC(_module, on_flow_report);
|
||||
GET_FUNC(_module, on_reload_config);
|
||||
GET_FUNC(_module, on_media_changed);
|
||||
GET_FUNC(_module, on_player_proxy_failed);
|
||||
|
||||
if (hasattr(_module, "on_start")) {
|
||||
py::object on_start = _module.attr("on_start");
|
||||
if (on_start) {
|
||||
on_start();
|
||||
}
|
||||
}
|
||||
} catch (py::error_already_set &e) {
|
||||
PrintE("Python exception:%s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_publish(BroadcastMediaPublishArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_publish) {
|
||||
return false;
|
||||
}
|
||||
return _on_publish(getOriginTypeString(type), to_python(args), to_python(invoker), to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_play(BroadcastMediaPlayedArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_play) {
|
||||
return false;
|
||||
}
|
||||
return _on_play(to_python(args), to_python(invoker), to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_flow_report(BroadcastFlowReportArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_flow_report) {
|
||||
return false;
|
||||
}
|
||||
return _on_flow_report(to_python(args), totalBytes, totalDuration, isPlayer, to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_media_changed(BroadcastMediaChangedArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_media_changed) {
|
||||
return false;
|
||||
}
|
||||
return _on_media_changed(bRegist, to_python2(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_player_proxy_failed(BroadcastPlayerProxyFailedArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_player_proxy_failed) {
|
||||
return false;
|
||||
}
|
||||
return _on_player_proxy_failed(sender.getUrl(), to_python2(sender.getMediaTuple()), to_python2(ex)).cast<bool>();
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif
|
||||
62
server/pyinvoker.h
Normal file
62
server/pyinvoker.h
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
#ifndef PYINVOKER_H
|
||||
#define PYINVOKER_H
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/numpy.h>
|
||||
#include "Util/logger.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PythonInvoker : public std::enable_shared_from_this<PythonInvoker>{
|
||||
public:
|
||||
~PythonInvoker();
|
||||
|
||||
static PythonInvoker& Instance();
|
||||
static void release();
|
||||
|
||||
void load(const std::string &module_name);
|
||||
bool on_publish(BroadcastMediaPublishArgs) const;
|
||||
bool on_play(BroadcastMediaPlayedArgs) const;
|
||||
bool on_flow_report(BroadcastFlowReportArgs) const;
|
||||
bool on_media_changed(BroadcastMediaChangedArgs) const;
|
||||
bool on_player_proxy_failed(BroadcastPlayerProxyFailedArgs) const;
|
||||
|
||||
private:
|
||||
PythonInvoker();
|
||||
|
||||
private:
|
||||
py::gil_scoped_release *_rel;
|
||||
py::scoped_interpreter *_interpreter;
|
||||
std::shared_ptr<toolkit::Logger> _logger;
|
||||
py::module _module;
|
||||
|
||||
// 程序退出
|
||||
py::object _on_exit;
|
||||
// 推流鉴权
|
||||
py::object _on_publish;
|
||||
// 播放鉴权
|
||||
py::object _on_play;
|
||||
// 流量汇报接口
|
||||
py::object _on_flow_report;
|
||||
// 配置文件热更新回调
|
||||
py::object _on_reload_config;
|
||||
// 媒体注册注销
|
||||
py::object _on_media_changed;
|
||||
// 拉流代理失败
|
||||
py::object _on_player_proxy_failed;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif
|
||||
#endif // PYINVOKER_H
|
||||
@ -231,10 +231,6 @@ FFmpegFrame::FFmpegFrame(std::shared_ptr<AVFrame> frame) {
|
||||
}
|
||||
|
||||
FFmpegFrame::~FFmpegFrame() {
|
||||
if (_data) {
|
||||
delete[] _data;
|
||||
_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame *FFmpegFrame::get() const {
|
||||
@ -242,9 +238,9 @@ AVFrame *FFmpegFrame::get() const {
|
||||
}
|
||||
|
||||
void FFmpegFrame::fillPicture(AVPixelFormat target_format, int target_width, int target_height) {
|
||||
assert(_data == nullptr);
|
||||
_data = new char[av_image_get_buffer_size(target_format, target_width, target_height, 32)];
|
||||
av_image_fill_arrays(_frame->data, _frame->linesize, (uint8_t *) _data, target_format, target_width, target_height, 32);
|
||||
auto buffer_size = av_image_get_buffer_size(target_format, target_width, target_height, 32);
|
||||
_data = std::unique_ptr<char[]>(new char[buffer_size]);
|
||||
av_image_fill_arrays(_frame->data, _frame->linesize, (uint8_t *)_data.get(), target_format, target_width, target_height, 32);
|
||||
}
|
||||
|
||||
int FFmpegFrame::getChannels() const {
|
||||
@ -256,6 +252,14 @@ int FFmpegFrame::getChannels() const {
|
||||
#endif
|
||||
}
|
||||
|
||||
// 资源池复用前调用
|
||||
void FFmpegFrame::reset() {
|
||||
_data.reset();
|
||||
if (_frame) {
|
||||
av_frame_unref(_frame.get()); // 清理AVFrame数据引用
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<bool decoder = true>
|
||||
@ -734,6 +738,7 @@ FFmpegFrame::Ptr FFmpegSws::inputFrame(const FFmpegFrame::Ptr &frame, int &ret,
|
||||
}
|
||||
if (_ctx) {
|
||||
auto out = _sws_frame_pool.obtain2();
|
||||
out->reset(); // 清理旧数据和帧引用
|
||||
if (!out->get()->data[0]) {
|
||||
if (data) {
|
||||
av_image_fill_arrays(out->get()->data, out->get()->linesize, data, _target_format, target_width, target_height, 32);
|
||||
@ -756,45 +761,16 @@ FFmpegFrame::Ptr FFmpegSws::inputFrame(const FFmpegFrame::Ptr &frame, int &ret,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::tuple<bool, std::string> FFmpegUtils::saveFrame(const FFmpegFrame::Ptr &frame, const char *filename, AVPixelFormat fmt) {
|
||||
std::tuple<bool, std::string> FFmpegUtils::saveFrame(const FFmpegFrame::Ptr &frame, const char *filename, AVPixelFormat fmt, int w, int h, const char *font_path) {
|
||||
std::shared_ptr<AVFilterGraph> _filter_graph;
|
||||
AVFilterContext *buffersrc_ctx = nullptr;
|
||||
AVFilterContext *buffersink_ctx = nullptr;
|
||||
const AVFilter *buffersrc = nullptr;
|
||||
const AVFilter *buffersink = nullptr;
|
||||
// kServerName
|
||||
const string mark = "ZLMediaKit";
|
||||
char drawtext_args1[512];
|
||||
_StrPrinter ss;
|
||||
const AVCodec *jpeg_codec = avcodec_find_encoder(fmt == AV_PIX_FMT_YUVJ420P ? AV_CODEC_ID_MJPEG : AV_CODEC_ID_PNG);
|
||||
std::unique_ptr<AVCodecContext, void (*)(AVCodecContext *)> jpeg_codec_ctx(
|
||||
jpeg_codec ? avcodec_alloc_context3(jpeg_codec) : nullptr, [](AVCodecContext *ctx) { avcodec_free_context(&ctx); });
|
||||
|
||||
if (!jpeg_codec_ctx) {
|
||||
ss << "Could not allocate JPEG/PNG codec context";
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
jpeg_codec_ctx->width = frame->get()->width;
|
||||
jpeg_codec_ctx->height = frame->get()->height;
|
||||
jpeg_codec_ctx->pix_fmt = fmt;
|
||||
jpeg_codec_ctx->time_base = { 1, 1 };
|
||||
|
||||
auto ret = avcodec_open2(jpeg_codec_ctx.get(), jpeg_codec, NULL);
|
||||
if (ret < 0) {
|
||||
ss << "Could not open JPEG/PNG codec, " << ffmpeg_err(ret);
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
FFmpegSws sws(fmt, 0, 0);
|
||||
auto new_frame = sws.inputFrame(frame);
|
||||
if (!new_frame) {
|
||||
ss << "Could not scale the frame: " << ffmpeg_err(ret);
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
auto pkt = alloc_av_packet();
|
||||
ret = avcodec_send_frame(jpeg_codec_ctx.get(), new_frame->get());
|
||||
if (ret < 0) {
|
||||
ss << "Error sending a frame for encoding, " << ffmpeg_err(ret);
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
std::unique_ptr<FILE, void (*)(FILE *)> tmp_save_file_jpg(File::create_file(filename, "wb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
@ -808,10 +784,104 @@ std::tuple<bool, std::string> FFmpegUtils::saveFrame(const FFmpegFrame::Ptr &fra
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
while (avcodec_receive_packet(jpeg_codec_ctx.get(), pkt.get()) == 0) {
|
||||
fwrite(pkt.get()->data, pkt.get()->size, 1, tmp_save_file_jpg.get());
|
||||
std::string fontfile("");
|
||||
if (font_path && File::fileExist(font_path)) {
|
||||
fontfile = font_path;
|
||||
} else {
|
||||
// Fallback to common default
|
||||
fontfile = exeDir() + "/DejaVuSans.ttf";
|
||||
}
|
||||
|
||||
snprintf(drawtext_args1, sizeof(drawtext_args1), "text='%s':fontfile='%s':fontcolor=white@0.1:fontsize=h/50:x=w*0.02:y=h-th-h*0.02", mark.data(), fontfile.c_str());
|
||||
|
||||
const AVCodec *jpeg_codec = avcodec_find_encoder(fmt == AV_PIX_FMT_YUVJ420P ? AV_CODEC_ID_MJPEG : AV_CODEC_ID_PNG);
|
||||
std::unique_ptr<AVCodecContext, void (*)(AVCodecContext *)> jpeg_codec_ctx(
|
||||
jpeg_codec ? avcodec_alloc_context3(jpeg_codec) : nullptr, [](AVCodecContext *ctx) { avcodec_free_context(&ctx); });
|
||||
|
||||
if (!jpeg_codec_ctx) {
|
||||
ss << "Could not allocate JPEG/PNG codec context";
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
jpeg_codec_ctx->width = (w > 0 && w < 8192) ? w : frame->get()->width;
|
||||
jpeg_codec_ctx->height = (h > 0 && h < 4320) ? h : frame->get()->height;
|
||||
jpeg_codec_ctx->pix_fmt = fmt;
|
||||
jpeg_codec_ctx->time_base = { 1, 1 };
|
||||
|
||||
auto ret = avcodec_open2(jpeg_codec_ctx.get(), jpeg_codec, NULL);
|
||||
if (ret < 0) {
|
||||
ss << "Could not open JPEG/PNG codec, " << ffmpeg_err(ret);
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
FFmpegSws sws(fmt, jpeg_codec_ctx->width, jpeg_codec_ctx->height);
|
||||
auto new_frame = sws.inputFrame(frame);
|
||||
if (!new_frame) {
|
||||
ss << "Could not scale the frame";
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
_filter_graph.reset(avfilter_graph_alloc(), [](AVFilterGraph *ctx) { avfilter_graph_free(&ctx); });
|
||||
if (!_filter_graph) {
|
||||
ss << "avfilter_graph_alloc failed";
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
char args[512];
|
||||
snprintf(
|
||||
args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", jpeg_codec_ctx->width, jpeg_codec_ctx->height,
|
||||
jpeg_codec_ctx->pix_fmt, jpeg_codec_ctx->time_base.num, jpeg_codec_ctx->time_base.den, jpeg_codec_ctx->sample_aspect_ratio.num,
|
||||
jpeg_codec_ctx->sample_aspect_ratio.den);
|
||||
|
||||
buffersrc = avfilter_get_by_name("buffer");
|
||||
|
||||
if ((ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, _filter_graph.get())) < 0) {
|
||||
ss << "avfilter_graph_create_filter buffersrc failed: " << ret << " " << ffmpeg_err(ret);
|
||||
DebugL << ss;
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
buffersink = avfilter_get_by_name("buffersink");
|
||||
if ((ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, _filter_graph.get())) < 0) {
|
||||
ss << "avfilter_graph_create_filter buffersink failed: " << ret << " " << ffmpeg_err(ret);
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
AVFilterContext *drawtext_ctx1 = nullptr;
|
||||
|
||||
const AVFilter *drawtext_filter = avfilter_get_by_name("drawtext");
|
||||
if ((ret = avfilter_graph_create_filter(&drawtext_ctx1, drawtext_filter, "drawtext", drawtext_args1, NULL, _filter_graph.get())) < 0) {
|
||||
ss << "avfilter_graph_create_filter drawtext_filter failed: " << ret << " " << ffmpeg_err(ret);
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
if ((ret = avfilter_link(buffersrc_ctx, 0, drawtext_ctx1, 0) < 0 || avfilter_link(drawtext_ctx1, 0, buffersink_ctx, 0))< 0) {
|
||||
ss << "avfilter_link: " << ret << " " << ffmpeg_err(ret);
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
if ((ret = avfilter_graph_config(_filter_graph.get(), NULL)) < 0) {
|
||||
ss << "avfilter_graph_config failed: " << ret << " " << ffmpeg_err(ret);
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
if ((ret = av_buffersrc_add_frame_flags(buffersrc_ctx, new_frame->get(), 0)) < 0) {
|
||||
ss << "av_buffersink_get_frame failed: " << ret << " " << ffmpeg_err(ret);
|
||||
return make_tuple<bool, std::string>(false, ss.data());
|
||||
}
|
||||
|
||||
auto pkt = alloc_av_packet();
|
||||
while (av_buffersink_get_frame(buffersink_ctx, new_frame->get()) >= 0) {
|
||||
if (avcodec_send_frame(jpeg_codec_ctx.get(), new_frame->get()) == 0) {
|
||||
while (avcodec_receive_packet(jpeg_codec_ctx.get(), pkt.get()) == 0) {
|
||||
fwrite(pkt.get()->data, pkt.get()->size, 1, tmp_save_file_jpg.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
DebugL << "Screenshot successful: " << filename;
|
||||
return make_tuple<bool, std::string>(true, "");
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,9 @@ extern "C" {
|
||||
#include "libavutil/audio_fifo.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/frame.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavfilter/buffersink.h"
|
||||
#include "libavfilter/buffersrc.h"
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -45,9 +48,10 @@ public:
|
||||
AVFrame *get() const;
|
||||
void fillPicture(AVPixelFormat target_format, int target_width, int target_height);
|
||||
int getChannels() const;
|
||||
void reset();
|
||||
|
||||
private:
|
||||
char *_data = nullptr;
|
||||
std::unique_ptr<char[]> _data;
|
||||
std::shared_ptr<AVFrame> _frame;
|
||||
};
|
||||
|
||||
@ -170,9 +174,11 @@ public:
|
||||
* @param frame 解码后的帧
|
||||
* @param filename 保存文件路径
|
||||
* @param fmt jpg:AV_PIX_FMT_YUVJ420P,PNG:AV_PIX_FMT_RGB24
|
||||
* @param w h (可选)裁剪的图片大小,默认和输入源一致
|
||||
* @param font_path (可选), default DejaVuSans.ttf
|
||||
* @return
|
||||
*/
|
||||
static std::tuple<bool, std::string> saveFrame(const FFmpegFrame::Ptr &frame, const char *filename, AVPixelFormat fmt = AV_PIX_FMT_YUVJ420P);
|
||||
static std::tuple<bool, std::string> saveFrame(const FFmpegFrame::Ptr &frame, const char *filename, AVPixelFormat fmt = AV_PIX_FMT_YUVJ420P, int w = 0, int h = 0, const char *font_path = nullptr);
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@ -176,7 +176,9 @@ void MediaSink::checkTrackIfReady() {
|
||||
}
|
||||
|
||||
void MediaSink::addTrackCompleted() {
|
||||
setMaxTrackCount(_track_map.size());
|
||||
if (!_track_map.empty()) {
|
||||
setMaxTrackCount(_track_map.size());
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSink::setMaxTrackCount(size_t i) {
|
||||
|
||||
@ -287,6 +287,36 @@ public:
|
||||
// Maximum number of tracks
|
||||
size_t max_track = 2;
|
||||
|
||||
#define OPT_VALUE(XX) \
|
||||
XX(modify_stamp) \
|
||||
XX(enable_audio) \
|
||||
XX(add_mute_audio) \
|
||||
XX(auto_close) \
|
||||
XX(continue_push_ms) \
|
||||
XX(paced_sender_ms) \
|
||||
\
|
||||
XX(enable_hls) \
|
||||
XX(enable_hls_fmp4) \
|
||||
XX(enable_mp4) \
|
||||
XX(enable_rtsp) \
|
||||
XX(enable_rtmp) \
|
||||
XX(enable_ts) \
|
||||
XX(enable_fmp4) \
|
||||
\
|
||||
XX(hls_demand) \
|
||||
XX(rtsp_demand) \
|
||||
XX(rtmp_demand) \
|
||||
XX(ts_demand) \
|
||||
XX(fmp4_demand) \
|
||||
\
|
||||
XX(mp4_max_second) \
|
||||
XX(mp4_as_player) \
|
||||
XX(mp4_save_path) \
|
||||
\
|
||||
XX(hls_save_path) \
|
||||
XX(stream_replace) \
|
||||
XX(max_track)
|
||||
|
||||
template <typename MAP>
|
||||
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
|
||||
load(allArgs);
|
||||
@ -294,35 +324,18 @@ public:
|
||||
|
||||
template <typename MAP>
|
||||
void load(const MAP &allArgs) {
|
||||
#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(auto_close);
|
||||
GET_OPT_VALUE(continue_push_ms);
|
||||
GET_OPT_VALUE(paced_sender_ms);
|
||||
#define GET(key) getArgsValue(allArgs, #key, key);
|
||||
OPT_VALUE(GET)
|
||||
#undef GET
|
||||
}
|
||||
|
||||
GET_OPT_VALUE(enable_hls);
|
||||
GET_OPT_VALUE(enable_hls_fmp4);
|
||||
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);
|
||||
GET_OPT_VALUE(stream_replace);
|
||||
GET_OPT_VALUE(max_track);
|
||||
template <typename MAP>
|
||||
MAP as() {
|
||||
MAP ret;
|
||||
#define SET(key) ret[#key] = key;
|
||||
OPT_VALUE(SET)
|
||||
#undef SET
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -401,15 +401,20 @@ bool MultiMediaSourceMuxer::setupRecord(Recorder::type type, bool start, const s
|
||||
}
|
||||
}
|
||||
|
||||
std::string MultiMediaSourceMuxer::startRecord(const std::string &file_path, uint32_t back_time_ms, uint32_t forward_time_ms) {
|
||||
std::string MultiMediaSourceMuxer::startRecord(const std::string &file_path, int back_time_ms, int forward_time_ms) {
|
||||
#if !defined(ENABLE_MP4)
|
||||
throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试");
|
||||
#else
|
||||
if (!_ring) {
|
||||
throw std::runtime_error("frame gop cache disabled, start record event video failed");
|
||||
}
|
||||
auto path = Recorder::getRecordPath(Recorder::type_mp4, _tuple, _option.mp4_save_path);
|
||||
path += file_path;
|
||||
std::string path;
|
||||
if (!start_with(file_path, "/")) {
|
||||
path = Recorder::getRecordPath(Recorder::type_mp4, _tuple, _option.mp4_save_path);
|
||||
path += file_path;
|
||||
} else {
|
||||
path = file_path;
|
||||
}
|
||||
TraceL << "mp4 save path: " << path;
|
||||
|
||||
auto muxer = std::make_shared<MP4Muxer>();
|
||||
@ -419,68 +424,124 @@ std::string MultiMediaSourceMuxer::startRecord(const std::string &file_path, uin
|
||||
}
|
||||
muxer->addTrackCompleted();
|
||||
|
||||
std::list<Frame::Ptr> history;
|
||||
_ring->flushGop([&](const Frame::Ptr &frame) { history.emplace_back(frame); });
|
||||
if (!history.empty()) {
|
||||
auto now_dts = history.back()->dts();
|
||||
|
||||
decltype(history)::iterator pos = history.end();
|
||||
for (auto it = history.rbegin(); it != history.rend(); ++it) {
|
||||
auto &frame = *it;
|
||||
if (frame->getTrackType() != TrackVideo || (!frame->configFrame() && !frame->keyFrame())) {
|
||||
continue;
|
||||
}
|
||||
// 如果视频关键帧到末尾的时长超过一定的时间,那前面的数据应该全部删除
|
||||
if (frame->dts() + back_time_ms < now_dts) {
|
||||
pos = it.base();
|
||||
--pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos != history.end()) {
|
||||
// 移除前面过多的数据
|
||||
TraceL << "clear history video: " << history.front()->dts() << " -> " << (*pos)->dts();
|
||||
history.erase(history.begin(), pos);
|
||||
}
|
||||
bool have_history = false;
|
||||
if (back_time_ms > 0) {
|
||||
// 回溯录制
|
||||
std::list<Frame::Ptr> history;
|
||||
_ring->flushGop([&](const Frame::Ptr &frame) { history.emplace_back(frame); });
|
||||
if (!history.empty()) {
|
||||
auto &front = history.front();
|
||||
InfoL << "start record: " << path
|
||||
<< ", start_dts: " << front->dts() << ", key_frame: " << front->keyFrame() << ", config_frame: " << front->configFrame()
|
||||
<< ", now_dts: " << now_dts;
|
||||
}
|
||||
auto now_dts = history.back()->dts();
|
||||
|
||||
for (auto &frame : history) {
|
||||
muxer->inputFrame(frame);
|
||||
decltype(history)::iterator pos = history.end();
|
||||
for (auto it = history.rbegin(); it != history.rend(); ++it) {
|
||||
auto &frame = *it;
|
||||
if (frame->getTrackType() != TrackVideo || (!frame->configFrame() && !frame->keyFrame())) {
|
||||
continue;
|
||||
}
|
||||
// 如果视频关键帧到末尾的时长超过一定的时间,那前面的数据应该全部删除
|
||||
if (frame->dts() + back_time_ms < now_dts) {
|
||||
pos = it.base();
|
||||
--pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos != history.end()) {
|
||||
// 移除历史视频前面过多的数据
|
||||
DebugL << "clear history front video: " << history.front()->dts() << " -> " << (*pos)->dts();
|
||||
history.erase(history.begin(), pos);
|
||||
}
|
||||
|
||||
if (forward_time_ms < 0) {
|
||||
// 如果后向录制时长为负,说明回溯录制要截取一段尾部
|
||||
pos = history.end();
|
||||
for (auto it = history.rbegin(); it != history.rend(); ++it) {
|
||||
auto &frame = *it;
|
||||
if (frame->getTrackType() != TrackVideo) {
|
||||
continue;
|
||||
}
|
||||
if (frame->dts() < now_dts + forward_time_ms) {
|
||||
pos = it.base();
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos != history.end()) {
|
||||
// 移除历史视频后面过多的数据
|
||||
DebugL << "clear history tail video: " << (*pos)->dts() << " -> " << now_dts;
|
||||
history.erase(pos, history.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (!history.empty()) {
|
||||
auto &front = history.front();
|
||||
InfoL << "start record: " << path
|
||||
<< ", start_dts: " << front->dts() << ", key_frame: " << front->keyFrame() << ", config_frame: " << front->configFrame()
|
||||
<< ", now_dts: " << now_dts;
|
||||
have_history = true;
|
||||
}
|
||||
|
||||
for (auto &frame : history) {
|
||||
muxer->inputFrame(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto reader = _ring->attach(MultiMediaSourceMuxer::getOwnerPoller(MediaSource::NullMediaSource()), false);
|
||||
uint64_t now_dts = 0;
|
||||
int selected_index = -1;
|
||||
Ticker ticker;
|
||||
bool is_live_stream = _dur_sec < 0.01;
|
||||
reader->setReadCB([muxer, now_dts, selected_index, forward_time_ms, reader, path, ticker, is_live_stream](const Frame::Ptr &frame) mutable {
|
||||
// 循环引用自身
|
||||
if (!now_dts) {
|
||||
now_dts = frame->dts();
|
||||
selected_index = frame->getIndex();
|
||||
if (forward_time_ms > 0) {
|
||||
if (!have_history) {
|
||||
InfoL << "start record: " << path << ", back_time_ms: " << back_time_ms << ", forward_time_ms: " << forward_time_ms;
|
||||
}
|
||||
// 新增兜底机制,如果直播录制任务时长超过预期时间3秒,不管数据时间戳是否增长是否达到预期,都强制停止录制
|
||||
if ((frame->getIndex() == selected_index && now_dts + forward_time_ms < frame->dts()) || (is_live_stream && ticker.createdTime() > forward_time_ms + 3000)) {
|
||||
InfoL << "stop record: " << path << ", end dts: " << frame->dts();
|
||||
WorkThreadPool::Instance().getPoller()->async([muxer]() { muxer->closeMP4(); });
|
||||
reader = nullptr;
|
||||
return;
|
||||
|
||||
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
|
||||
auto lam = [weak_self, muxer, forward_time_ms, have_history, path]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
uint64_t now_dts = 0;
|
||||
int selected_index = -1;
|
||||
Ticker ticker;
|
||||
bool is_live_stream = strong_self->_dur_sec < 0.01;
|
||||
auto reader = strong_self->_ring->attach(strong_self->MultiMediaSourceMuxer::getOwnerPoller(MediaSource::NullMediaSource()), !have_history, 1);
|
||||
reader->setReadCB([muxer, now_dts, selected_index, forward_time_ms, reader, path, ticker, is_live_stream](const Frame::Ptr &frame) mutable {
|
||||
if (!reader) {
|
||||
// 已经关闭录制
|
||||
return;
|
||||
}
|
||||
// 循环引用自身
|
||||
if (!now_dts) {
|
||||
now_dts = frame->dts();
|
||||
selected_index = frame->getIndex();
|
||||
}
|
||||
// 新增兜底机制,如果直播录制任务时长超过预期时间3秒,不管数据时间戳是否增长是否达到预期,都强制停止录制
|
||||
if ((frame->getIndex() == selected_index && now_dts + forward_time_ms < frame->dts())
|
||||
|| (is_live_stream && ticker.createdTime() > forward_time_ms + 3000ULL)) {
|
||||
InfoL << "stop record: " << path << ", end dts: " << frame->dts();
|
||||
WorkThreadPool::Instance().getPoller()->async([muxer]() { muxer->closeMP4(); });
|
||||
reader = nullptr;
|
||||
return;
|
||||
}
|
||||
muxer->inputFrame(frame);
|
||||
});
|
||||
std::weak_ptr<RingType::RingReader> weak_reader = reader;
|
||||
reader->setDetachCB([weak_reader]() {
|
||||
if (auto strong_reader = weak_reader.lock()) {
|
||||
// 防止循环引用
|
||||
strong_reader->setReadCB(nullptr);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (back_time_ms >= 0) {
|
||||
// 立即前向录制
|
||||
lam();
|
||||
} else {
|
||||
// 延时启动录制
|
||||
MultiMediaSourceMuxer::getOwnerPoller(MediaSource::NullMediaSource())->doDelayTask(-back_time_ms, [lam]() {
|
||||
lam();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
muxer->inputFrame(frame);
|
||||
});
|
||||
std::weak_ptr<RingType::RingReader> weak_reader = reader;
|
||||
reader->setDetachCB([weak_reader]() {
|
||||
if (auto strong_reader = weak_reader.lock()) {
|
||||
// 防止循环引用
|
||||
strong_reader->setReadCB(nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return path;
|
||||
#endif
|
||||
|
||||
@ -129,7 +129,7 @@ public:
|
||||
* @param forward_time_ms 后续录制时长
|
||||
* @return 录制文件绝对路径
|
||||
*/
|
||||
std::string startRecord(const std::string &file_path, uint32_t back_time_ms, uint32_t forward_time_ms);
|
||||
std::string startRecord(const std::string &file_path, int back_time_ms, int forward_time_ms);
|
||||
|
||||
/**
|
||||
* 获取录制状态
|
||||
|
||||
@ -81,6 +81,7 @@ const string kBroadcastRtcSctpClosed = "kBroadcastRtcSctpClosed";
|
||||
const string kBroadcastRtcSctpSend = "kBroadcastRtcSctpSend";
|
||||
const string kBroadcastRtcSctpReceived = "kBroadcastRtcSctpReceived";
|
||||
const string kBroadcastPlayerCountChanged = "kBroadcastPlayerCountChanged";
|
||||
const string kBroadcastPlayerProxyFailed = "kBroadcastPlayerProxyFailed";
|
||||
|
||||
} // namespace Broadcast
|
||||
|
||||
|
||||
@ -55,31 +55,31 @@ extern const std::string kBroadcastRecordTs;
|
||||
// 收到http api请求广播 [AUTO-TRANSLATED:c72e7c3f]
|
||||
// Broadcast for receiving http api request
|
||||
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, toolkit::SockInfo &sender
|
||||
|
||||
// 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 [AUTO-TRANSLATED:2de426b4]
|
||||
// In the http file server, broadcast for receiving http access to files or directories. Control access permissions to the http directory through this event.
|
||||
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, toolkit::SockInfo &sender
|
||||
|
||||
// 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 [AUTO-TRANSLATED:0294d0c5]
|
||||
// In the http file server, broadcast before receiving http access to files or directories. Control the mapping from http url to file path through this event.
|
||||
// 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 [AUTO-TRANSLATED:1bea3efb]
|
||||
// By overriding the path parameter in this event, you can achieve the purpose of selecting different http root directories based on virtual hosts or apps.
|
||||
extern const std::string kBroadcastHttpBeforeAccess;
|
||||
#define BroadcastHttpBeforeAccessArgs const Parser &parser, std::string &path, SockInfo &sender
|
||||
#define BroadcastHttpBeforeAccessArgs const Parser &parser, std::string &path, toolkit::SockInfo &sender
|
||||
|
||||
// 该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 [AUTO-TRANSLATED:5f436d8f]
|
||||
// Does this stream need authentication? If yes, call invoker and pass in realm, otherwise pass in an empty realm. If this event is not listened to, no authentication will be performed.
|
||||
extern const std::string kBroadcastOnGetRtspRealm;
|
||||
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args, const RtspSession::onGetRealm &invoker, SockInfo &sender
|
||||
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args, const RtspSession::onGetRealm &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 [AUTO-TRANSLATED:22b6dfcc]
|
||||
// Request authentication user password event, user_name is the username, must_no_encrypt if true, then the plaintext password must be provided (because it is base64 authentication method at this time), otherwise it will lead to authentication failure.
|
||||
// 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码 [AUTO-TRANSLATED:8c57fd43]
|
||||
// After getting the password, please call invoker and input the corresponding type of password and password type. The invoker will match the password when executing.
|
||||
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, toolkit::SockInfo &sender
|
||||
|
||||
// 推流鉴权结果回调对象 [AUTO-TRANSLATED:7e508ed1]
|
||||
// Push stream authentication result callback object
|
||||
@ -90,7 +90,7 @@ using PublishAuthInvoker = std::function<void(const std::string &err, const Prot
|
||||
// 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权 [AUTO-TRANSLATED:72417373]
|
||||
// Broadcast for receiving rtsp/rtmp push stream event. Control push stream authentication through this event.
|
||||
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, toolkit::SockInfo &sender
|
||||
|
||||
// 播放鉴权结果回调对象 [AUTO-TRANSLATED:c980162b]
|
||||
// Playback authentication result callback object
|
||||
@ -101,22 +101,22 @@ using AuthInvoker = std::function<void(const std::string &err)>;
|
||||
// 播放rtsp/rtmp/http-flv事件广播,通过该事件控制播放鉴权 [AUTO-TRANSLATED:eddd7014]
|
||||
// Broadcast for playing rtsp/rtmp/http-flv events. Control playback authentication through this event.
|
||||
extern const std::string kBroadcastMediaPlayed;
|
||||
#define BroadcastMediaPlayedArgs const MediaInfo &args, const Broadcast::AuthInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastMediaPlayedArgs const MediaInfo &args, const Broadcast::AuthInvoker &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// shell登录鉴权 [AUTO-TRANSLATED:26b135d4]
|
||||
// Shell login authentication
|
||||
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, toolkit::SockInfo &sender
|
||||
|
||||
// 停止rtsp/rtmp/http-flv会话后流量汇报事件广播 [AUTO-TRANSLATED:69df61d8]
|
||||
// Broadcast for traffic reporting event after stopping rtsp/rtmp/http-flv session
|
||||
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, toolkit::SockInfo &sender
|
||||
|
||||
// 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 [AUTO-TRANSLATED:0c00171d]
|
||||
// This event will be broadcast after the stream is not found. Please pull the stream or other methods to generate the stream after listening to this event, so that you can pull the stream on demand.
|
||||
extern const std::string kBroadcastNotFoundStream;
|
||||
#define BroadcastNotFoundStreamArgs const MediaInfo &args, SockInfo &sender, const std::function<void()> &closePlayer
|
||||
#define BroadcastNotFoundStreamArgs const MediaInfo &args, toolkit::SockInfo &sender, const std::function<void()> &closePlayer
|
||||
|
||||
// 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑 [AUTO-TRANSLATED:3c45f002]
|
||||
// Triggered when a stream is not consumed by anyone. The purpose is to achieve business logic such as actively disconnecting the pull stream when no one is watching.
|
||||
@ -161,6 +161,9 @@ extern const std::string kBroadcastRtcSctpReceived;
|
||||
extern const std::string kBroadcastPlayerCountChanged;
|
||||
#define BroadcastPlayerCountChangedArgs const MediaTuple& args, const int& count
|
||||
|
||||
extern const std::string kBroadcastPlayerProxyFailed;
|
||||
#define BroadcastPlayerProxyFailedArgs const PlayerProxy& sender, const toolkit::SockException &ex
|
||||
|
||||
#define ReloadConfigTag ((void *)(0xFF))
|
||||
#define RELOAD_KEY(arg, key) \
|
||||
do { \
|
||||
|
||||
@ -61,6 +61,7 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||
static onceToken token([]() {
|
||||
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
|
||||
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
|
||||
s_func_map.emplace("PUT", &HttpSession::onHttpRequest_POST);
|
||||
// DELETE命令用于whip/whep用,只用于触发http api [AUTO-TRANSLATED:f3b7aaea]
|
||||
// DELETE command is used for whip/whep, only used to trigger http api
|
||||
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
|
||||
|
||||
@ -110,7 +110,9 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
NOTICE_EMIT(BroadcastPlayerProxyFailedArgs, Broadcast::kBroadcastPlayerProxyFailed, *strongSelf, err);
|
||||
}
|
||||
if (strongSelf->_on_play) {
|
||||
strongSelf->_on_play(err);
|
||||
strongSelf->_on_play = nullptr;
|
||||
@ -146,6 +148,9 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
NOTICE_EMIT(BroadcastPlayerProxyFailedArgs, Broadcast::kBroadcastPlayerProxyFailed, *strongSelf, err);
|
||||
}
|
||||
|
||||
// 注销直接拉流代理产生的流:#532 [AUTO-TRANSLATED:c6343a3b]
|
||||
// Unregister the stream generated by the direct stream proxy: #532
|
||||
|
||||
@ -18,8 +18,7 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
struct StreamInfo
|
||||
{
|
||||
struct StreamInfo {
|
||||
TrackType codec_type;
|
||||
std::string codec_name;
|
||||
int bitrate;
|
||||
@ -30,8 +29,7 @@ struct StreamInfo
|
||||
int video_height;
|
||||
float video_fps;
|
||||
|
||||
StreamInfo()
|
||||
{
|
||||
StreamInfo() {
|
||||
codec_type = TrackInvalid;
|
||||
codec_name = "none";
|
||||
bitrate = -1;
|
||||
@ -44,14 +42,12 @@ struct StreamInfo
|
||||
}
|
||||
};
|
||||
|
||||
struct TranslationInfo
|
||||
{
|
||||
struct TranslationInfo {
|
||||
std::vector<StreamInfo> stream_info;
|
||||
int byte_speed;
|
||||
uint64_t start_time_stamp;
|
||||
|
||||
TranslationInfo()
|
||||
{
|
||||
TranslationInfo() {
|
||||
byte_speed = -1;
|
||||
start_time_stamp = 0;
|
||||
}
|
||||
|
||||
@ -115,7 +115,13 @@ void PusherProxy::rePublish(const string &dst_url, int failed_cnt) {
|
||||
return false;
|
||||
}
|
||||
WarnL << "推流重试[" << failed_cnt << "]:" << dst_url;
|
||||
strong_self->MediaPusher::publish(dst_url);
|
||||
try {
|
||||
strong_self->MediaPusher::publish(dst_url);
|
||||
} catch (std::exception &e) {
|
||||
WarnL << e.what();
|
||||
// 回调推流失败,一般是媒体注销了
|
||||
strong_self->_on_close(SockException(Err_other, e.what()));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getPoller());
|
||||
|
||||
@ -19,7 +19,11 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
MP4Muxer::~MP4Muxer() {
|
||||
closeMP4();
|
||||
try {
|
||||
closeMP4();
|
||||
} catch (std::exception &e) {
|
||||
WarnL << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Muxer::openMP4(const string &file) {
|
||||
|
||||
@ -56,30 +56,33 @@ AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) {
|
||||
}
|
||||
|
||||
uint8_t getCodecFlags(CodecId cid) {
|
||||
switch(cid) {
|
||||
#define XX(a, b, c) case a: return static_cast<uint8_t>(b);
|
||||
RTMP_CODEC_MAP(XX)
|
||||
switch (cid) {
|
||||
#define XX(a, b, c) \
|
||||
case a: return static_cast<uint8_t>(b);
|
||||
RTMP_CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t getCodecFourCC(CodecId cid) {
|
||||
switch(cid) {
|
||||
#define XX(a, b, c) case a: return static_cast<uint32_t>(c);
|
||||
RTMP_CODEC_MAP(XX)
|
||||
switch (cid) {
|
||||
#define XX(a, b, c) \
|
||||
case a: return static_cast<uint32_t>(c);
|
||||
RTMP_CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
CodecId getFourccCodec(uint32_t id) {
|
||||
switch(id) {
|
||||
#define XX(a, b, c) case (uint32_t)c: return a;
|
||||
RTMP_CODEC_MAP(XX)
|
||||
switch (id) {
|
||||
#define XX(a, b, c) \
|
||||
case (uint32_t)c: return a;
|
||||
RTMP_CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default: return CodecInvalid;
|
||||
}
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
uint8_t getAudioRtmpFlags(const Track::Ptr &track) {
|
||||
@ -195,12 +198,10 @@ bool RtmpPacket::isConfigFrame() const {
|
||||
switch (type_id) {
|
||||
case MSG_AUDIO: {
|
||||
switch ((RtmpAudioCodec)getRtmpCodecId()) {
|
||||
case RtmpAudioCodec::aac:
|
||||
return (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header;
|
||||
case RtmpAudioCodec::ex_header:
|
||||
return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart;
|
||||
case RtmpAudioCodec::aac: return (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header;
|
||||
case RtmpAudioCodec::ex_header: return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart;
|
||||
default: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MSG_VIDEO: {
|
||||
if (!isVideoKeyFrame()) {
|
||||
|
||||
@ -26,11 +26,35 @@ using namespace toolkit;
|
||||
#define S2_FMS_KEY_SIZE 68
|
||||
#define C1_OFFSET_SIZE 4
|
||||
|
||||
|
||||
#ifdef ENABLE_OPENSSL
|
||||
#include "Util/SSLBox.h"
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/opensslv.h>
|
||||
|
||||
static uint8_t FMSKey[] = {
|
||||
0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,
|
||||
0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
|
||||
0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,
|
||||
0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
|
||||
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
|
||||
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
|
||||
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
|
||||
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
|
||||
}; // 68
|
||||
|
||||
static uint8_t FPKey[] = {
|
||||
0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
|
||||
0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
|
||||
0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
|
||||
0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,
|
||||
0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,
|
||||
0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
|
||||
}; // 62
|
||||
|
||||
static string openssl_HMACsha256(const void *key, size_t key_len, const void *data, size_t data_len){
|
||||
std::shared_ptr<char> out(new char[32], [](char *ptr) { delete[] ptr; });
|
||||
unsigned int out_len;
|
||||
@ -329,8 +353,16 @@ const char* RtmpProtocol::handle_S0S1S2(const char *data, size_t len, const func
|
||||
}
|
||||
// 发送 C2 [AUTO-TRANSLATED:e51c339e]
|
||||
// Send C2
|
||||
const char *pcC2 = data + 1;
|
||||
onSendRawData(obtainBuffer(pcC2, C1_HANDSHARK_SIZE));
|
||||
uint8_t *pS1 = (uint8_t*)data + 1;
|
||||
RtmpHandshake c2(0);
|
||||
memcpy(&c2, pS1, sizeof(c2));
|
||||
#ifdef ENABLE_OPENSSL
|
||||
if(pS1[4] >=3){ // 复杂握手计算c2
|
||||
handle_S1_complex((char*)pS1, c2);
|
||||
}
|
||||
#endif
|
||||
|
||||
onSendRawData(obtainBuffer(&c2, C1_HANDSHARK_SIZE));
|
||||
// 握手结束 [AUTO-TRANSLATED:9df763ff]
|
||||
// Handshake finished
|
||||
_next_step_func = [this](const char *data, size_t len) {
|
||||
@ -408,7 +440,7 @@ void RtmpProtocol::handle_C1_complex(const char *data){
|
||||
check_C1_Digest(digest, c1_joined);
|
||||
|
||||
send_complex_S0S1S2(0, digest);
|
||||
// InfoL << "schema0";
|
||||
// InfoL << "schema0";
|
||||
} catch (std::exception &) {
|
||||
// 貌似flash从来都不用schema1 [AUTO-TRANSLATED:2c6d140f]
|
||||
// It seems that flash never uses schema1
|
||||
@ -426,40 +458,70 @@ void RtmpProtocol::handle_C1_complex(const char *data){
|
||||
check_C1_Digest(digest, c1_joined);
|
||||
|
||||
send_complex_S0S1S2(1, digest);
|
||||
// InfoL << "schema1";
|
||||
// InfoL << "schema1";
|
||||
} catch (std::exception &) {
|
||||
// WarnL << "try rtmp complex schema1 failed:" << ex.what();
|
||||
//WarnL << "try rtmp complex schema1 failed:" << ex.what();
|
||||
handle_C1_simple(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(u_int8_t)
|
||||
#define u_int8_t unsigned char
|
||||
#endif // !defined(u_int8_t)
|
||||
void RtmpProtocol::check_S1_Digest(const std::string &digest,const std::string &data){
|
||||
auto sha256 = openssl_HMACsha256(FMSKey, S1_FMS_KEY_SIZE, data.data(), data.size());
|
||||
if (sha256 != digest) {
|
||||
throw std::runtime_error("digest mismatched");
|
||||
} else {
|
||||
InfoL << "check rtmp complex handshark success!";
|
||||
}
|
||||
}
|
||||
|
||||
static u_int8_t FMSKey[] = {
|
||||
0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,
|
||||
0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
|
||||
0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,
|
||||
0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
|
||||
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
|
||||
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
|
||||
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
|
||||
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
|
||||
}; // 68
|
||||
void RtmpProtocol::handle_S1_complex(const char *data,RtmpHandshake &c2){
|
||||
|
||||
static u_int8_t FPKey[] = {
|
||||
0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
|
||||
0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
|
||||
0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
|
||||
0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,
|
||||
0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,
|
||||
0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
|
||||
}; // 62
|
||||
const char *s1_start = data;
|
||||
const char *schema_start = s1_start + 8;
|
||||
char *digest_start;
|
||||
std::string digest;
|
||||
try {
|
||||
/* c1s1 schema0
|
||||
time: 4bytes
|
||||
version: 4bytes
|
||||
key: 764bytes
|
||||
digest: 764bytes
|
||||
*/
|
||||
digest = get_C1_digest((uint8_t *) schema_start + C1_SCHEMA_SIZE, &digest_start);
|
||||
string s1_joined(s1_start, C1_HANDSHARK_SIZE);
|
||||
s1_joined.erase(digest_start - s1_start, C1_DIGEST_SIZE);
|
||||
check_S1_Digest(digest, s1_joined);
|
||||
//InfoL << "schema0";
|
||||
} catch (std::exception &ex) {
|
||||
// 貌似flash从来都不用schema1 [AUTO-TRANSLATED:2c6d140f]
|
||||
// It seems that flash never uses schema1
|
||||
//WarnL << "try rtmp complex schema0 failed:" << ex.what();
|
||||
try {
|
||||
/* c1s1 schema1
|
||||
time: 4bytes
|
||||
version: 4bytes
|
||||
digest: 764bytes
|
||||
key: 764bytes
|
||||
*/
|
||||
digest = get_C1_digest((uint8_t *) schema_start, &digest_start);
|
||||
string s1_joined(s1_start, C1_HANDSHARK_SIZE);
|
||||
s1_joined.erase(digest_start - s1_start, C1_DIGEST_SIZE);
|
||||
check_S1_Digest(digest, s1_joined);
|
||||
//send_complex_S0S1S2(1, digest);
|
||||
//InfoL << "schema1";
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "try rtmp complex schema1 failed:" << ex.what();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//InfoL << "send complex C2";
|
||||
auto c2_key = openssl_HMACsha256(FPKey, sizeof(FPKey), digest.data(), digest.size());
|
||||
std::string c2_str((char*)(&c2), sizeof(c2)- C1_DIGEST_SIZE);
|
||||
auto c2_digest = openssl_HMACsha256(c2_key.data(), c2_key.size(), c2_str.data(), c2_str.size());
|
||||
memcpy(c2.random + RANDOM_LEN - C1_DIGEST_SIZE, c2_digest.data(), C1_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
void RtmpProtocol::check_C1_Digest(const string &digest,const string &data){
|
||||
auto sha256 = openssl_HMACsha256(FPKey, C1_FPKEY_SIZE, data.data(), data.size());
|
||||
|
||||
@ -63,14 +63,16 @@ protected:
|
||||
void sendRtmp(uint8_t type, uint32_t stream_index, const std::string &buffer, uint32_t stamp, int chunk_id);
|
||||
void sendRtmp(uint8_t type, uint32_t stream_index, const toolkit::Buffer::Ptr &buffer, uint32_t stamp, int chunk_id);
|
||||
toolkit::BufferRaw::Ptr obtainBuffer(const void *data = nullptr, size_t len = 0);
|
||||
|
||||
|
||||
private:
|
||||
void handle_C1_simple(const char *data);
|
||||
#ifdef ENABLE_OPENSSL
|
||||
void handle_S1_complex(const char *data, RtmpHandshake &c2);
|
||||
void handle_C1_complex(const char *data);
|
||||
std::string get_C1_digest(const uint8_t *ptr,char **digestPos);
|
||||
std::string get_C1_key(const uint8_t *ptr);
|
||||
void check_C1_Digest(const std::string &digest,const std::string &data);
|
||||
void check_S1_Digest(const std::string &digest,const std::string &data);
|
||||
void send_complex_S0S1S2(int schemeType,const std::string &digest);
|
||||
#endif //ENABLE_OPENSSL
|
||||
|
||||
|
||||
@ -446,7 +446,11 @@ void RtspPlayer::sendOptions() {
|
||||
}
|
||||
|
||||
void RtspPlayer::sendKeepAlive() {
|
||||
_on_response = [](const Parser &parser) {};
|
||||
if (_play_check_timer)
|
||||
{
|
||||
WarnL << "receive RTP packet before handleResPAUSE";
|
||||
}
|
||||
_on_keepalive_reponse = [](const Parser &parser) {};
|
||||
if (_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()) {
|
||||
// 支持GET_PARAMETER,用此命令保活 [AUTO-TRANSLATED:b45cd737]
|
||||
// Support GET_PARAMETER, use this command to keep alive
|
||||
@ -532,6 +536,10 @@ void RtspPlayer::onWholeRtspPacket(Parser &parser) {
|
||||
try {
|
||||
decltype(_on_response) func;
|
||||
_on_response.swap(func);
|
||||
if (!func)
|
||||
{
|
||||
_on_keepalive_reponse.swap(func);
|
||||
}
|
||||
if (func) {
|
||||
func(parser);
|
||||
}
|
||||
|
||||
@ -162,6 +162,7 @@ private:
|
||||
float _speed = 0.0f;
|
||||
std::vector<SdpTrack::Ptr> _sdp_track;
|
||||
std::function<void(const Parser&)> _on_response;
|
||||
std::function<void(const Parser&)> _on_keepalive_reponse;
|
||||
protected:
|
||||
// RTP端口,trackid idx 为数组下标 [AUTO-TRANSLATED:77c186bb]
|
||||
// RTP port, trackid idx is the array subscript
|
||||
|
||||
@ -64,7 +64,6 @@ public:
|
||||
|
||||
private:
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
uint64_t timeStamp = 0;
|
||||
uint64_t timeStamp_last = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -83,6 +83,10 @@ struct sniff_tcp {
|
||||
#define TH_URG 0x20
|
||||
#define TH_ECE 0x40
|
||||
#define TH_CWR 0x80
|
||||
|
||||
#if defined(TH_FLAGS)
|
||||
#undef TH_FLAGS
|
||||
#endif
|
||||
#define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
|
||||
u_short th_win; /* TCP滑动窗口 */
|
||||
u_short th_sum; /* 头部校验和 */
|
||||
@ -154,7 +158,7 @@ static bool loadFile(const char *path, const EventPoller::Ptr &poller) {
|
||||
return false;
|
||||
}
|
||||
auto total_size = std::make_shared<size_t>(0);
|
||||
struct pcap_pkthdr header = {0};
|
||||
struct pcap_pkthdr header {};
|
||||
while (true) {
|
||||
const u_char *pkt_buff = pcap_next(handle.get(), &header);
|
||||
if (!pkt_buff) {
|
||||
|
||||
@ -1175,7 +1175,7 @@ void IceAgent::connectivityCheck(const Pair::Ptr &pair, CandidateTuple& candidat
|
||||
}
|
||||
|
||||
void IceAgent::tryTriggerredCheck(const Pair::Ptr& pair) {
|
||||
DebugL;
|
||||
// DebugL;
|
||||
//FIXME 暂不实现,因为当前实现基本收到candidate就会发起check
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ const string kNackIntervalRatio = RTC_FIELD "nackIntervalRatio";
|
||||
// nack包中rtp个数,减小此值可以让nack包响应更灵敏 [AUTO-TRANSLATED:12393868]
|
||||
// Number of rtp in nack packet, reducing this value can make nack packet response more sensitive
|
||||
const string kNackRtpSize = RTC_FIELD "nackRtpSize";
|
||||
const string kNackAudioRtpSize = RTC_FIELD "nackAudioRtpSize";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kMaxRtpCacheMS] = 5 * 1000;
|
||||
@ -55,6 +56,7 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kNackMaxCount] = 15;
|
||||
mINI::Instance()[kNackIntervalRatio] = 1.0f;
|
||||
mINI::Instance()[kNackRtpSize] = 8;
|
||||
mINI::Instance()[kNackAudioRtpSize] = 4;
|
||||
});
|
||||
|
||||
} // namespace Rtc
|
||||
@ -155,7 +157,8 @@ int64_t NackList::getNtpStamp(uint16_t seq) {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NackContext::NackContext() {
|
||||
NackContext::NackContext(TrackType type) {
|
||||
_type = type;
|
||||
setOnNack(nullptr);
|
||||
}
|
||||
|
||||
@ -217,7 +220,9 @@ void NackContext::makeNack(uint16_t max_seq, bool flush) {
|
||||
// 最多生成5个nack包,防止seq大幅跳跃导致一直循环 [AUTO-TRANSLATED:9cc5da25]
|
||||
// Generate at most 5 nack packets to prevent seq from jumping significantly and causing continuous loops
|
||||
auto max_nack = 5u;
|
||||
GET_CONFIG(uint32_t, nack_rtpsize, Rtc::kNackRtpSize);
|
||||
GET_CONFIG(uint32_t, nack_video_rtpsize, Rtc::kNackRtpSize);
|
||||
GET_CONFIG(uint32_t, nack_audio_rtpsize, Rtc::kNackAudioRtpSize);
|
||||
auto nack_rtpsize = _type == TrackVideo ? nack_video_rtpsize : nack_audio_rtpsize;
|
||||
// kNackRtpSize must between 0 and 16
|
||||
nack_rtpsize = std::min<uint32_t>(nack_rtpsize, FCI_NACK::kBitSize);
|
||||
while (_nack_seq != max_seq && max_nack--) {
|
||||
|
||||
@ -55,7 +55,7 @@ public:
|
||||
using Ptr = std::shared_ptr<NackContext>;
|
||||
using onNack = std::function<void(const FCI_NACK &nack)>;
|
||||
|
||||
NackContext();
|
||||
NackContext(TrackType type = TrackVideo);
|
||||
|
||||
void received(uint16_t seq, bool is_rtx = false);
|
||||
void setOnNack(onNack cb);
|
||||
@ -71,6 +71,7 @@ private:
|
||||
private:
|
||||
bool _started = false;
|
||||
int _rtt = 50;
|
||||
TrackType _type;
|
||||
onNack _cb;
|
||||
std::set<uint16_t> _seq;
|
||||
// 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a]
|
||||
|
||||
@ -204,11 +204,16 @@ SrtpSession::SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_
|
||||
policy.key = key;
|
||||
// Required for sending RTP retransmission without RTX.
|
||||
policy.allow_repeat_tx = 1;
|
||||
#if 0
|
||||
if (type == Type::OUTBOUND) {
|
||||
policy.window_size = 0x8000 - 1;
|
||||
} else {
|
||||
policy.window_size = 1024;
|
||||
}
|
||||
#else
|
||||
// TODO 关闭防重放攻击
|
||||
policy.window_size = 0x8000 - 1;
|
||||
#endif
|
||||
policy.next = nullptr;
|
||||
|
||||
// Set the SRTP session.
|
||||
|
||||
@ -69,10 +69,11 @@ void WebRtcSession::onRecv_l(const char *data, size_t len) {
|
||||
auto sock = Socket::createSocket(transport->getPoller(), false);
|
||||
// 1、克隆socket(fd不变),切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab]
|
||||
// 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located
|
||||
sock->cloneSocket(*(getSock()));
|
||||
auto on_complete = sock->cloneSocket(*(getSock()));
|
||||
auto server = _server;
|
||||
std::string str(data, len);
|
||||
sock->getPoller()->async([sock, server, str](){
|
||||
// on_complete在创建WebRtcSession后才析构(才开始网络事件监听)
|
||||
sock->getPoller()->async([sock, server, str, on_complete](){
|
||||
auto strong_server = server.lock();
|
||||
if (strong_server) {
|
||||
auto session = static_pointer_cast<WebRtcSession>(strong_server->createSession(sock));
|
||||
|
||||
@ -82,6 +82,7 @@ const string kMinBitrate = RTC_FIELD "min_bitrate";
|
||||
// 数据通道设置 [AUTO-TRANSLATED:2dc48bc3]
|
||||
// Data channel setting
|
||||
const string kDataChannelEcho = RTC_FIELD "datachannel_echo";
|
||||
const string kPreferredTcp = RTC_FIELD "preferred_tcp";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kTimeOutSec] = 15;
|
||||
@ -105,6 +106,7 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kIceTransportPolicy] = 0; // 默认值:不限制(kAll)
|
||||
mINI::Instance()[kIceUfrag] = "ZLMediaKit";
|
||||
mINI::Instance()[kIcePwd] = "ZLMediaKit";
|
||||
mINI::Instance()[kPreferredTcp] = 0;
|
||||
});
|
||||
|
||||
} // namespace Rtc
|
||||
@ -1102,7 +1104,7 @@ void WebRtcTransportImp::setIceCandidate(vector<SdpAttrCandidate> cands) {
|
||||
|
||||
class RtpChannel : public RtpTrackImp, public std::enable_shared_from_this<RtpChannel> {
|
||||
public:
|
||||
RtpChannel(EventPoller::Ptr poller, RtpTrackImp::OnSorted cb, function<void(const FCI_NACK &nack)> on_nack) {
|
||||
RtpChannel(TrackType type, EventPoller::Ptr poller, RtpTrackImp::OnSorted cb, function<void(const FCI_NACK &nack)> on_nack) : _nack_ctx(type){
|
||||
_poller = std::move(poller);
|
||||
_on_nack = std::move(on_nack);
|
||||
setOnSorted(std::move(cb));
|
||||
@ -1314,7 +1316,7 @@ void WebRtcTransportImp::createRtpChannel(const string &rid, uint32_t ssrc, Medi
|
||||
// rid --> RtpReceiverImp
|
||||
auto &ref = track.rtp_channel[rid];
|
||||
weak_ptr<WebRtcTransportImp> weak_self = static_pointer_cast<WebRtcTransportImp>(shared_from_this());
|
||||
ref = std::make_shared<RtpChannel>(
|
||||
ref = std::make_shared<RtpChannel>(track.media->type,
|
||||
getPoller(), [&track, this, rid](RtpPacket::Ptr rtp) mutable { onSortedRtp(track, rid, std::move(rtp)); },
|
||||
[&track, weak_self, ssrc](const FCI_NACK &nack) mutable {
|
||||
// nack发送可能由定时器异步触发 [AUTO-TRANSLATED:186d6723]
|
||||
@ -1631,6 +1633,7 @@ WebRtcPluginManager &WebRtcPluginManager::Instance() {
|
||||
}
|
||||
|
||||
void WebRtcPluginManager::registerPlugin(const string &type, Plugin cb) {
|
||||
InfoL << "Load webrtc plugin:" << type;
|
||||
lock_guard<mutex> lck(_mtx_creator);
|
||||
_map_creator[type] = std::move(cb);
|
||||
}
|
||||
@ -1667,6 +1670,7 @@ void echo_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWeb
|
||||
cb(*WebRtcEchoTest::create(EventPollerPool::Instance().getPoller()));
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
void push_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
|
||||
MediaInfo info(args["url"]);
|
||||
Broadcast::PublishAuthInvoker invoker = [cb, info](const string &err, const ProtocolOption &option) mutable {
|
||||
@ -1711,7 +1715,7 @@ void push_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWeb
|
||||
push_src_ownership = push_src->getOwnership();
|
||||
push_src->setProtocolOption(option);
|
||||
}
|
||||
auto rtc = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option,
|
||||
auto rtc = Type::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option,
|
||||
WebRtcTransport::Role::PEER, WebRtcTransport::SignalingProtocols::WHEP_WHIP);
|
||||
push_src->setListener(rtc);
|
||||
cb(*rtc);
|
||||
@ -1782,9 +1786,12 @@ static void setWebRtcArgs(const WebRtcArgs &args, WebRtcInterface &rtc) {
|
||||
}
|
||||
}
|
||||
|
||||
bool preferred_tcp = args["preferred_tcp"];
|
||||
{
|
||||
auto preferred_tcp = args["preferred_tcp"];
|
||||
if (!preferred_tcp.empty()) {
|
||||
rtc.setPreferredTcp(preferred_tcp);
|
||||
} else {
|
||||
GET_CONFIG(bool, s_preferred_tcp, Rtc::kPreferredTcp);
|
||||
rtc.setPreferredTcp(s_preferred_tcp);
|
||||
}
|
||||
|
||||
{
|
||||
@ -1832,7 +1839,7 @@ static onceToken s_rtc_auto_register([]() {
|
||||
// Enable echo plugin only in debug mode
|
||||
WebRtcPluginManager::Instance().registerPlugin("echo", echo_plugin);
|
||||
#endif
|
||||
WebRtcPluginManager::Instance().registerPlugin("push", push_plugin);
|
||||
WebRtcPluginManager::Instance().registerPlugin("push", push_plugin<WebRtcPusher>);
|
||||
WebRtcPluginManager::Instance().registerPlugin("play", play_plugin<WebRtcPlayer>);
|
||||
WebRtcPluginManager::Instance().registerPlugin("talk", play_plugin<WebRtcTalk>);
|
||||
|
||||
|
||||
@ -286,7 +286,7 @@ public:
|
||||
|
||||
struct WrappedMediaTrack {
|
||||
MediaTrack::Ptr track;
|
||||
explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {}
|
||||
explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(std::move(ptr)) {}
|
||||
virtual ~WrappedMediaTrack() {}
|
||||
virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user