From a554fab5fbde28c08e84cfe334ebbfbcba469c23 Mon Sep 17 00:00:00 2001 From: wdl1697454803 <50978870+wdl1697454803@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:26:25 +0800 Subject: [PATCH 01/20] add cmake minimum required 3.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pkg_check_modules在cmake的3.6.0及以上版本才支持参数IMPORTED_TARGET --- player/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/player/CMakeLists.txt b/player/CMakeLists.txt index 5b332bb9..9c2d74f6 100644 --- a/player/CMakeLists.txt +++ b/player/CMakeLists.txt @@ -21,6 +21,8 @@ # SOFTWARE. # +cmake_minimum_required(VERSION 3.6.0) + set(LINK_LIBRARIES ${MK_LINK_LIBRARIES}) find_package(PkgConfig QUIET) From 210894ed8305fdb322bb2c86518067d643848e4c Mon Sep 17 00:00:00 2001 From: wdl1697454803 <50978870+wdl1697454803@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:27:44 +0800 Subject: [PATCH 02/20] Use find_package when pkg_check_modules fails Fixed the issue that when the cmake version was earlier than 3.6.0, the pkg_check_modules did not support IMPORTED_TARGET parameters, resulting in the SDL2 library not being found --- player/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/player/CMakeLists.txt b/player/CMakeLists.txt index 9c2d74f6..e36b2a46 100644 --- a/player/CMakeLists.txt +++ b/player/CMakeLists.txt @@ -21,8 +21,6 @@ # SOFTWARE. # -cmake_minimum_required(VERSION 3.6.0) - set(LINK_LIBRARIES ${MK_LINK_LIBRARIES}) find_package(PkgConfig QUIET) @@ -33,7 +31,9 @@ if(PKG_CONFIG_FOUND) list(APPEND LINK_LIBRARIES PkgConfig::SDL2) message(STATUS "found library: ${SDL2_LIBRARIES}") endif() -else() +endif() + +if(NOT SDL2_FOUND) find_package(SDL2 QUIET) if(SDL2_FOUND) include_directories(SYSTEM ${SDL2_INCLUDE_DIR}) From ffdc13bfb9f25dff1277bdd70a20f0b4aee3b309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E4=BC=A0=E5=B3=B0?= <59160162+zhang-chuanfeng@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:42:22 +0800 Subject: [PATCH 03/20] =?UTF-8?q?RTP=20proxy=E9=80=9A=E8=BF=87UDP=E6=94=B6?= =?UTF-8?q?=E6=B5=81=EF=BC=8C=E8=B0=83=E6=95=B4udp=20recv=20socket=20buffe?= =?UTF-8?q?r=20size=E6=88=90=E9=85=8D=E7=BD=AE=20(#3336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 国标推流有些情况需要UDP方式接收流,端口复用同一个UDP端口可能需要根据服务器性能 --- conf/config.ini | 3 +++ src/Common/config.cpp | 2 ++ src/Common/config.h | 2 ++ src/Rtp/RtpServer.cpp | 4 +++- src/Rtp/RtpSession.cpp | 11 +++++++---- src/Rtp/RtpSession.h | 1 + 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index a66de19f..21e268fd 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -332,6 +332,9 @@ gop_cache=1 #国标发送g711 rtp 打包时,每个包的语音时长是多少,默认是100 ms,范围为20~180ms (gb28181-2016,c.2.4规定), #最好为20 的倍数,程序自动向20的倍数取整 rtp_g711_dur_ms = 100 +#udp接收数据socket buffer大小配置 +#4*1024*1024=4196304 +udp_recv_socket_buffer=4194304 [rtc] #rtc播放推流、播放超时时间 diff --git a/src/Common/config.cpp b/src/Common/config.cpp index f31546fe..a72ea8da 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -345,6 +345,7 @@ const string kPSPT = RTP_PROXY_FIELD "ps_pt"; const string kOpusPT = RTP_PROXY_FIELD "opus_pt"; const string kGopCache = RTP_PROXY_FIELD "gop_cache"; const string kRtpG711DurMs = RTP_PROXY_FIELD "rtp_g711_dur_ms"; +const string kUdpRecvSocketBuffer = RTP_PROXY_FIELD "udp_recv_socket_buffer"; static onceToken token([]() { mINI::Instance()[kDumpDir] = ""; @@ -356,6 +357,7 @@ static onceToken token([]() { mINI::Instance()[kOpusPT] = 100; mINI::Instance()[kGopCache] = 1; mINI::Instance()[kRtpG711DurMs] = 100; + mINI::Instance()[kUdpRecvSocketBuffer] = 4 * 1024 * 1024; }); } // namespace RtpProxy diff --git a/src/Common/config.h b/src/Common/config.h index 3196654b..16ef6303 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -400,6 +400,8 @@ extern const std::string kGopCache; //国标发送g711 rtp 打包时,每个包的语音时长是多少,默认是100 ms,范围为20~180ms (gb28181-2016,c.2.4规定), //最好为20 的倍数,程序自动向20的倍数取整 extern const std::string kRtpG711DurMs; +// udp recv socket buffer size +extern const std::string kUdpRecvSocketBuffer; } // namespace RtpProxy /** diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index 51bb5555..560792b3 100644 --- a/src/Rtp/RtpServer.cpp +++ b/src/Rtp/RtpServer.cpp @@ -174,7 +174,8 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_ } //设置udp socket读缓存 - SockUtil::setRecvBuf(rtp_socket->rawFD(), 4 * 1024 * 1024); + GET_CONFIG(int, udpRecvSocketBuffer, RtpProxy::kUdpRecvSocketBuffer); + SockUtil::setRecvBuf(rtp_socket->rawFD(), udpRecvSocketBuffer); TcpServer::Ptr tcp_server; _tcp_mode = tcp_mode; @@ -223,6 +224,7 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_ //单端口多线程接收多个流,根据ssrc区分流 udp_server = std::make_shared(rtp_socket->getPoller()); (*udp_server)[RtpSession::kOnlyAudio] = only_audio; + (*udp_server)[RtpSession::kUdpRecvBuffer] = udpRecvSocketBuffer; udp_server->start(local_port, local_ip); rtp_socket = nullptr; } diff --git a/src/Rtp/RtpSession.cpp b/src/Rtp/RtpSession.cpp index 006b1c82..41c76287 100644 --- a/src/Rtp/RtpSession.cpp +++ b/src/Rtp/RtpSession.cpp @@ -24,6 +24,7 @@ namespace mediakit{ const string RtpSession::kStreamID = "stream_id"; const string RtpSession::kSSRC = "ssrc"; const string RtpSession::kOnlyAudio = "only_audio"; +const string RtpSession::kUdpRecvBuffer = "udp_recv_socket_buffer"; void RtpSession::attachServer(const Server &server) { setParams(const_cast(server)); @@ -33,6 +34,12 @@ void RtpSession::setParams(mINI &ini) { _stream_id = ini[kStreamID]; _ssrc = ini[kSSRC]; _only_audio = ini[kOnlyAudio]; + int udp_socket_buffer = ini[kUdpRecvBuffer]; + if (_is_udp) { + // 设置udp socket读缓存 + SockUtil::setRecvBuf(getSock()->rawFD(), + (udp_socket_buffer > 0) ? udp_socket_buffer : (4 * 1024 * 1024)); + } } RtpSession::RtpSession(const Socket::Ptr &sock) @@ -40,10 +47,6 @@ RtpSession::RtpSession(const Socket::Ptr &sock) socklen_t addr_len = sizeof(_addr); getpeername(sock->rawFD(), (struct sockaddr *)&_addr, &addr_len); _is_udp = sock->sockType() == SockNum::Sock_UDP; - if (_is_udp) { - // 设置udp socket读缓存 - SockUtil::setRecvBuf(getSock()->rawFD(), 4 * 1024 * 1024); - } } RtpSession::~RtpSession() = default; diff --git a/src/Rtp/RtpSession.h b/src/Rtp/RtpSession.h index 5b8d7e1f..de2fa75f 100644 --- a/src/Rtp/RtpSession.h +++ b/src/Rtp/RtpSession.h @@ -25,6 +25,7 @@ public: static const std::string kStreamID; static const std::string kSSRC; static const std::string kOnlyAudio; + static const std::string kUdpRecvBuffer; RtpSession(const toolkit::Socket::Ptr &sock); ~RtpSession() override; From 79b2aa6adc6e0ada78535b8c2fa93c26ebfd7d7c Mon Sep 17 00:00:00 2001 From: waken <33921191+mc373906408@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:06:31 +0800 Subject: [PATCH 04/20] =?UTF-8?q?openRtpServer=E6=8E=A5=E5=8F=A3=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=8D=95=E8=A7=86=E9=A2=91=E5=8F=82=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E5=8A=A0=E5=BF=AB=E5=8D=95=E8=A7=86=E9=A2=91=E6=B5=81=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E9=80=9F=E5=BA=A6=20(#3342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit only_audio -> only_track --- postman/ZLMediaKit.postman_collection.json | 12 ++++++------ server/WebApi.cpp | 18 ++++++++++++++---- server/WebApi.h | 2 +- src/Common/MediaSink.cpp | 2 +- src/Rtp/RtpProcess.cpp | 10 ++++++---- src/Rtp/RtpProcess.h | 7 ++++--- src/Rtp/RtpServer.cpp | 16 ++++++++-------- src/Rtp/RtpServer.h | 4 ++-- src/Rtp/RtpSession.cpp | 6 +++--- src/Rtp/RtpSession.h | 4 ++-- 10 files changed, 47 insertions(+), 34 deletions(-) diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index a6dc237f..e0d4c80d 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -1,11 +1,11 @@ { "info": { - "_postman_id": "08e3bc35-5318-4949-81bb-90d854706194", + "_postman_id": "08c77fc3-7670-428c-bde4-80c8cc9f389f", "name": "ZLMediaKit", "description": "媒体服务器", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "29185956", - "_collection_link": "https://lively-station-598157.postman.co/workspace/%E6%B5%81%E5%AA%92%E4%BD%93%E6%9C%8D%E5%8A%A1~1e119172-45b0-4ed6-b1fc-8a15d0e2d5f8/collection/29185956-08e3bc35-5318-4949-81bb-90d854706194?action=share&source=collection_link&creator=29185956" + "_collection_link": "https://lively-station-598157.postman.co/workspace/%E6%B5%81%E5%AA%92%E4%BD%93%E6%9C%8D%E5%8A%A1~1e119172-45b0-4ed6-b1fc-8a15d0e2d5f8/collection/29185956-08c77fc3-7670-428c-bde4-80c8cc9f389f?action=share&source=collection_link&creator=29185956" }, "item": [ { @@ -1470,9 +1470,9 @@ "disabled": true }, { - "key": "only_audio", + "key": "only_track", "value": "1", - "description": "是否为单音频track,用于语音对讲", + "description": "是否为单音频/单视频track,0:不设置,1:单音频,2:单视频", "disabled": true }, { @@ -1523,9 +1523,9 @@ "description": "该端口绑定的流id\n" }, { - "key": "only_audio", + "key": "only_track", "value": "0", - "description": "是否为单音频track,用于语音对讲", + "description": "是否为单音频/单视频track,0:不设置,1:单音频,2:单视频", "disabled": true }, { diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 8b53e948..4ca72759 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -415,7 +415,7 @@ Value makeMediaSourceJson(MediaSource &media){ } #if defined(ENABLE_RTPPROXY) -uint16_t openRtpServer(uint16_t local_port, const string &stream_id, int tcp_mode, const string &local_ip, bool re_use_port, uint32_t ssrc, bool only_audio, bool multiplex) { +uint16_t openRtpServer(uint16_t local_port, const string &stream_id, int tcp_mode, const string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) { lock_guard lck(s_rtpServerMapMtx); if (s_rtpServerMap.find(stream_id) != s_rtpServerMap.end()) { //为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的stream_id @@ -423,7 +423,7 @@ uint16_t openRtpServer(uint16_t local_port, const string &stream_id, int tcp_mod } RtpServer::Ptr server = std::make_shared(); - server->start(local_port, stream_id, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc, only_audio, multiplex); + server->start(local_port, stream_id, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc, only_track, multiplex); server->setOnDetach([stream_id]() { //设置rtp超时移除事件 lock_guard lck(s_rtpServerMapMtx); @@ -1198,12 +1198,17 @@ void installWebApi() { //兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数 tcp_mode = 1; } + auto only_track = allArgs["only_track"].as(); + if (allArgs["only_audio"].as()) { + // 兼容老版本请求,新版本去除only_audio参数并新增only_track参数 + only_track = 1; + } std::string local_ip = "::"; if (!allArgs["local_ip"].empty()) { local_ip = allArgs["local_ip"]; } auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, local_ip, allArgs["re_use_port"].as(), - allArgs["ssrc"].as(), allArgs["only_audio"].as()); + allArgs["ssrc"].as(), only_track); if (port == 0) { throw InvalidArgsException("该stream_id已存在"); } @@ -1220,11 +1225,16 @@ void installWebApi() { // 兼容老版本请求,新版本去除enable_tcp参数并新增tcp_mode参数 tcp_mode = 1; } + auto only_track = allArgs["only_track"].as(); + if (allArgs["only_audio"].as()) { + // 兼容老版本请求,新版本去除only_audio参数并新增only_track参数 + only_track = 1; + } std::string local_ip = "::"; if (!allArgs["local_ip"].empty()) { local_ip = allArgs["local_ip"]; } - auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, local_ip, true, 0, allArgs["only_audio"].as(),true); + auto port = openRtpServer(allArgs["port"], stream_id, tcp_mode, local_ip, true, 0, only_track,true); if (port == 0) { throw InvalidArgsException("该stream_id已存在"); } diff --git a/server/WebApi.h b/server/WebApi.h index 43516463..eff4c93f 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -233,7 +233,7 @@ void installWebApi(); void unInstallWebApi(); #if defined(ENABLE_RTPPROXY) -uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, bool only_audio, bool multiplex=false); +uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false); void connectRtpServer(const std::string &stream_id, const std::string &dst_url, uint16_t dst_port, const std::function &cb); bool closeRtpServer(const std::string &stream_id); #endif diff --git a/src/Common/MediaSink.cpp b/src/Common/MediaSink.cpp index 837024af..41b4f415 100644 --- a/src/Common/MediaSink.cpp +++ b/src/Common/MediaSink.cpp @@ -133,7 +133,7 @@ void MediaSink::checkTrackIfReady() { } GET_CONFIG(uint32_t, kMaxAddTrackMS, General::kWaitAddTrackMS); - if (_track_map.size() == 1 && _ticker.elapsedTime() > kMaxAddTrackMS) { + if (_track_map.size() == 1 && (_ticker.elapsedTime() > kMaxAddTrackMS || !_enable_audio)) { // 如果只有一个Track,那么在该Track添加后,我们最多还等待若干时间(可能后面还会添加Track) emitAllTrackReady(); return; diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp index 1d95f6b0..b0161d91 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -199,8 +199,8 @@ void RtpProcess::setStopCheckRtp(bool is_check){ } } -void RtpProcess::setOnlyAudio(bool only_audio){ - _only_audio = only_audio; +void RtpProcess::setOnlyTrack(OnlyTrack only_track) { + _only_track = only_track; } void RtpProcess::onDetach() { @@ -259,8 +259,10 @@ void RtpProcess::emitOnPublish() { if (!option.stream_replace.empty()) { RtpSelector::Instance().addStreamReplace(strong_self->_media_info.stream, option.stream_replace); } - if (strong_self->_only_audio) { - strong_self->_muxer->setOnlyAudio(); + switch (strong_self->_only_track) { + case kOnlyAudio: strong_self->_muxer->setOnlyAudio(); break; + case kOnlyVideo: strong_self->_muxer->enableAudio(false); break; + default: break; } strong_self->_muxer->setMediaListener(strong_self); strong_self->doCachedFunc(); diff --git a/src/Rtp/RtpProcess.h b/src/Rtp/RtpProcess.h index b9d5009b..b680936c 100644 --- a/src/Rtp/RtpProcess.h +++ b/src/Rtp/RtpProcess.h @@ -24,6 +24,7 @@ public: friend class RtpProcessHelper; RtpProcess(const std::string &stream_id); ~RtpProcess(); + enum OnlyTrack { kAll = 0, kOnlyAudio = 1, kOnlyVideo = 2 }; /** * 输入rtp @@ -58,10 +59,10 @@ public: void setStopCheckRtp(bool is_check=false); /** - * 设置为单track,单音频时可以加快媒体注册速度 + * 设置为单track,单音频/单视频时可以加快媒体注册速度 * 请在inputRtp前调用此方法,否则可能会是空操作 */ - void setOnlyAudio(bool only_audio); + void setOnlyTrack(OnlyTrack only_track); /** * flush输出缓存 @@ -93,7 +94,7 @@ private: void doCachedFunc(); private: - bool _only_audio = false; + OnlyTrack _only_track = kAll; std::string _auth_err; uint64_t _dts = 0; uint64_t _total_bytes = 0; diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index 560792b3..84809488 100644 --- a/src/Rtp/RtpServer.cpp +++ b/src/Rtp/RtpServer.cpp @@ -42,12 +42,12 @@ public: } } - void setRtpServerInfo(uint16_t local_port,RtpServer::TcpMode mode,bool re_use_port,uint32_t ssrc, bool only_audio) { + void setRtpServerInfo(uint16_t local_port, RtpServer::TcpMode mode, bool re_use_port, uint32_t ssrc, int only_track) { _local_port = local_port; _tcp_mode = mode; _re_use_port = re_use_port; _ssrc = ssrc; - _only_audio = only_audio; + _only_track = only_track; } void setOnDetach(function cb) { @@ -61,7 +61,7 @@ public: void onRecvRtp(const Socket::Ptr &sock, const Buffer::Ptr &buf, struct sockaddr *addr) { if (!_process) { _process = RtpSelector::Instance().getProcess(_stream_id, true); - _process->setOnlyAudio(_only_audio); + _process->setOnlyTrack((RtpProcess::OnlyTrack)_only_track); _process->setOnDetach(std::move(_on_detach)); cancelDelayTask(); } @@ -142,7 +142,7 @@ private: private: bool _re_use_port = false; - bool _only_audio = false; + int _only_track = 0; uint16_t _local_port = 0; uint32_t _ssrc = 0; RtpServer::TcpMode _tcp_mode = RtpServer::NONE; @@ -156,7 +156,7 @@ private: EventPoller::DelayTask::Ptr _delay_task; }; -void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_mode, const char *local_ip, bool re_use_port, uint32_t ssrc, bool only_audio, bool multiplex) { +void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_mode, const char *local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) { //创建udp服务器 Socket::Ptr rtp_socket = Socket::createSocket(nullptr, true); Socket::Ptr rtcp_socket = Socket::createSocket(nullptr, true); @@ -184,7 +184,7 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_ tcp_server = std::make_shared(rtp_socket->getPoller()); (*tcp_server)[RtpSession::kStreamID] = stream_id; (*tcp_server)[RtpSession::kSSRC] = ssrc; - (*tcp_server)[RtpSession::kOnlyAudio] = only_audio; + (*tcp_server)[RtpSession::kOnlyTrack] = only_track; if (tcp_mode == PASSIVE) { tcp_server->start(local_port, local_ip); } else if (stream_id.empty()) { @@ -201,7 +201,7 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_ //指定了流id,那么一个端口一个流(不管是否包含多个ssrc的多个流,绑定rtp源后,会筛选掉ip端口不匹配的流) helper = std::make_shared(std::move(rtcp_socket), stream_id); helper->startRtcp(); - helper->setRtpServerInfo(local_port, tcp_mode, re_use_port, ssrc, only_audio); + helper->setRtpServerInfo(local_port, tcp_mode, re_use_port, ssrc, only_track); bool bind_peer_addr = false; auto ssrc_ptr = std::make_shared(ssrc); _ssrc = ssrc_ptr; @@ -223,7 +223,7 @@ void RtpServer::start(uint16_t local_port, const string &stream_id, TcpMode tcp_ } else { //单端口多线程接收多个流,根据ssrc区分流 udp_server = std::make_shared(rtp_socket->getPoller()); - (*udp_server)[RtpSession::kOnlyAudio] = only_audio; + (*udp_server)[RtpSession::kOnlyTrack] = only_track; (*udp_server)[RtpSession::kUdpRecvBuffer] = udpRecvSocketBuffer; udp_server->start(local_port, local_ip); rtp_socket = nullptr; diff --git a/src/Rtp/RtpServer.h b/src/Rtp/RtpServer.h index 74250d6f..3654828e 100644 --- a/src/Rtp/RtpServer.h +++ b/src/Rtp/RtpServer.h @@ -44,7 +44,7 @@ public: * @param multiplex 多路复用 */ void start(uint16_t local_port, const std::string &stream_id = "", TcpMode tcp_mode = PASSIVE, - const char *local_ip = "::", bool re_use_port = true, uint32_t ssrc = 0, bool only_audio = false, bool multiplex = false); + const char *local_ip = "::", bool re_use_port = true, uint32_t ssrc = 0, int only_track = 0, bool multiplex = false); /** * 连接到tcp服务(tcp主动模式) @@ -81,7 +81,7 @@ protected: std::shared_ptr _rtcp_helper; std::function _on_cleanup; - bool _only_audio = false; + int _only_track = 0; //用于tcp主动模式 TcpMode _tcp_mode = NONE; }; diff --git a/src/Rtp/RtpSession.cpp b/src/Rtp/RtpSession.cpp index 41c76287..95807637 100644 --- a/src/Rtp/RtpSession.cpp +++ b/src/Rtp/RtpSession.cpp @@ -23,7 +23,7 @@ namespace mediakit{ const string RtpSession::kStreamID = "stream_id"; const string RtpSession::kSSRC = "ssrc"; -const string RtpSession::kOnlyAudio = "only_audio"; +const string RtpSession::kOnlyTrack = "only_track"; const string RtpSession::kUdpRecvBuffer = "udp_recv_socket_buffer"; void RtpSession::attachServer(const Server &server) { @@ -33,7 +33,7 @@ void RtpSession::attachServer(const Server &server) { void RtpSession::setParams(mINI &ini) { _stream_id = ini[kStreamID]; _ssrc = ini[kSSRC]; - _only_audio = ini[kOnlyAudio]; + _only_track = ini[kOnlyTrack]; int udp_socket_buffer = ini[kUdpRecvBuffer]; if (_is_udp) { // 设置udp socket读缓存 @@ -125,7 +125,7 @@ void RtpSession::onRtpPacket(const char *data, size_t len) { _delay_close = true; return; } - _process->setOnlyAudio(_only_audio); + _process->setOnlyTrack((RtpProcess::OnlyTrack)_only_track); _process->setDelegate(static_pointer_cast(shared_from_this())); } try { diff --git a/src/Rtp/RtpSession.h b/src/Rtp/RtpSession.h index de2fa75f..2bff4f5f 100644 --- a/src/Rtp/RtpSession.h +++ b/src/Rtp/RtpSession.h @@ -24,7 +24,7 @@ class RtpSession : public toolkit::Session, public RtpSplitter, public MediaSour public: static const std::string kStreamID; static const std::string kSSRC; - static const std::string kOnlyAudio; + static const std::string kOnlyTrack; static const std::string kUdpRecvBuffer; RtpSession(const toolkit::Socket::Ptr &sock); @@ -52,7 +52,7 @@ private: bool _is_udp = false; bool _search_rtp = false; bool _search_rtp_finished = false; - bool _only_audio = false; + int _only_track = 0; uint32_t _ssrc = 0; toolkit::Ticker _ticker; std::string _stream_id; From fe61b572e0c95fff4261902d76cb3b8bcbcc2114 Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Fri, 8 Mar 2024 11:04:59 +0800 Subject: [PATCH 05/20] Fix hls fmp4 clear cache bug (#3355) Fix hls fmp4 clear cache delete init.mp4 result in play failed for #3348 --- src/Record/HlsMakerImp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index e987f820..d62c4398 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -72,7 +72,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) { std::list lst; lst.emplace_back(_path_hls); lst.emplace_back(_path_hls_delay); - if (!_path_init.empty()) { + if (!_path_init.empty() && eof) { lst.emplace_back(_path_init); } for (auto &pr : _segment_file_paths) { From 78a6f041a817dbef94cdede0376a209e93fe6911 Mon Sep 17 00:00:00 2001 From: huangcaichun <34204760+huangcaichun@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:17:29 +0800 Subject: [PATCH 06/20] Fixed issue that set use_ps in startSendRtp api does not take effect (#3353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复使用startSendRtp接口转发ps流,设置use_ps为1后,还发送es流的问题 --------- Co-authored-by: huangcaichun --- server/WebApi.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 4ca72759..6956c5a2 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -1299,9 +1299,10 @@ void installWebApi() { if (!src) { throw ApiRetException("can not find the source stream", API::NotFound); } + auto type = allArgs["type"].as(); if (!allArgs["use_ps"].empty()) { // 兼容之前的use_ps参数 - allArgs["type"] = allArgs["use_ps"].as(); + type = allArgs["use_ps"].as(); } MediaSourceEvent::SendRtpArgs args; args.passive = false; @@ -1312,11 +1313,11 @@ void installWebApi() { args.is_udp = allArgs["is_udp"]; args.src_port = allArgs["src_port"]; args.pt = allArgs["pt"].empty() ? 96 : allArgs["pt"].as(); - args.type = (MediaSourceEvent::SendRtpArgs::Type)(allArgs["type"].as()); + args.type = (MediaSourceEvent::SendRtpArgs::Type)type; args.only_audio = allArgs["only_audio"].as(); args.udp_rtcp_timeout = allArgs["udp_rtcp_timeout"]; args.recv_stream_id = allArgs["recv_stream_id"]; - TraceL << "startSendRtp, pt " << int(args.pt) << " rtp type " << args.type << " audio " << args.only_audio; + TraceL << "startSendRtp, pt " << int(args.pt) << " rtp type " << type << " audio " << args.only_audio; src->getOwnerPoller()->async([=]() mutable { src->startSendRtp(args, [val, headerOut, invoker](uint16_t local_port, const SockException &ex) mutable { @@ -1338,10 +1339,10 @@ void installWebApi() { if (!src) { throw ApiRetException("can not find the source stream", API::NotFound); } - + auto type = allArgs["type"].as(); if (!allArgs["use_ps"].empty()) { // 兼容之前的use_ps参数 - allArgs["type"] = allArgs["use_ps"].as(); + type = allArgs["use_ps"].as(); } MediaSourceEvent::SendRtpArgs args; @@ -1350,12 +1351,12 @@ void installWebApi() { args.is_udp = false; args.src_port = allArgs["src_port"]; args.pt = allArgs["pt"].empty() ? 96 : allArgs["pt"].as(); - args.type = (MediaSourceEvent::SendRtpArgs::Type)(allArgs["type"].as()); + args.type = (MediaSourceEvent::SendRtpArgs::Type)type; args.only_audio = allArgs["only_audio"].as(); args.recv_stream_id = allArgs["recv_stream_id"]; //tcp被动服务器等待链接超时时间 args.tcp_passive_close_delay_ms = allArgs["close_delay_ms"]; - TraceL << "startSendRtpPassive, pt " << int(args.pt) << " rtp type " << args.type << " audio " << args.only_audio; + TraceL << "startSendRtpPassive, pt " << int(args.pt) << " rtp type " << type << " audio " << args.only_audio; src->getOwnerPoller()->async([=]() mutable { src->startSendRtp(args, [val, headerOut, invoker](uint16_t local_port, const SockException &ex) mutable { From 03c93d0b23f2d4dbfa5f88de8824d65a5053fd02 Mon Sep 17 00:00:00 2001 From: jamesZHANG500 Date: Sun, 10 Mar 2024 16:19:02 +0800 Subject: [PATCH 07/20] Add config for save fmp4 record files (#3356) --- conf/config.ini | 2 ++ src/Common/config.cpp | 2 ++ src/Common/config.h | 2 ++ src/Record/MP4Muxer.cpp | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/conf/config.ini b/conf/config.ini index 21e268fd..8f566338 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -277,6 +277,8 @@ sampleMS=500 fastStart=0 #MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件 fileRepeat=0 +#MP4录制写文件格式是否采用fmp4,启用的话,断电未完成录制的文件也能正常打开 +enableFmp4=0 [rtmp] #rtmp必须在此时间内完成握手,否则服务器会断开链接,单位秒 diff --git a/src/Common/config.cpp b/src/Common/config.cpp index a72ea8da..a34ee84f 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -297,6 +297,7 @@ const string kSampleMS = RECORD_FIELD "sampleMS"; const string kFileBufSize = RECORD_FIELD "fileBufSize"; const string kFastStart = RECORD_FIELD "fastStart"; const string kFileRepeat = RECORD_FIELD "fileRepeat"; +const string kEnableFmp4 = RECORD_FIELD "enableFmp4"; static onceToken token([]() { mINI::Instance()[kAppName] = "record"; @@ -304,6 +305,7 @@ static onceToken token([]() { mINI::Instance()[kFileBufSize] = 64 * 1024; mINI::Instance()[kFastStart] = false; mINI::Instance()[kFileRepeat] = false; + mINI::Instance()[kEnableFmp4] = false; }); } // namespace Record diff --git a/src/Common/config.h b/src/Common/config.h index 16ef6303..98ab289c 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -354,6 +354,8 @@ extern const std::string kFileBufSize; extern const std::string kFastStart; // mp4文件是否重头循环读取 extern const std::string kFileRepeat; +// mp4录制文件是否采用fmp4格式 +extern const std::string kEnableFmp4; } // namespace Record ////////////HLS相关配置/////////// diff --git a/src/Record/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp index 5d40be1d..46fda66f 100644 --- a/src/Record/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -31,7 +31,8 @@ void MP4Muxer::openMP4(const string &file) { MP4FileIO::Writer MP4Muxer::createWriter() { GET_CONFIG(bool, mp4FastStart, Record::kFastStart); - return _mp4_file->createWriter(mp4FastStart ? MOV_FLAG_FASTSTART : 0, false); + GET_CONFIG(bool, recordEnableFmp4, Record::kEnableFmp4); + return _mp4_file->createWriter(mp4FastStart ? MOV_FLAG_FASTSTART : 0, recordEnableFmp4); } void MP4Muxer::closeMP4() { From 2f50344e7bf56a8ef359a2eefaf221a0c89aa52a Mon Sep 17 00:00:00 2001 From: johzzy Date: Sun, 10 Mar 2024 05:31:20 -0300 Subject: [PATCH 08/20] Add ServiceController to manage PlayerProxy/PusherProxy/FFmpegSource/RtpServer services (#3337) --- api/source/mk_common.cpp | 3 +- ext-codec/G711Rtp.cpp | 12 +- server/WebApi.cpp | 323 +++++++++++++++++++-------------------- server/main.cpp | 4 +- 4 files changed, 160 insertions(+), 182 deletions(-) diff --git a/api/source/mk_common.cpp b/api/source/mk_common.cpp index 1f94cf28..c4a36ea1 100644 --- a/api/source/mk_common.cpp +++ b/api/source/mk_common.cpp @@ -33,12 +33,11 @@ static TcpServer::Ptr shell_server; #ifdef ENABLE_RTPPROXY #include "Rtp/RtpServer.h" -static std::shared_ptr rtpServer; +static RtpServer::Ptr rtpServer; #endif #ifdef ENABLE_WEBRTC #include "../webrtc/WebRtcSession.h" -#include "../webrtc/WebRtcTransport.h" static UdpServer::Ptr rtcServer_udp; static TcpServer::Ptr rtcServer_tcp; #endif diff --git a/ext-codec/G711Rtp.cpp b/ext-codec/G711Rtp.cpp index ea07b767..16a9c5c2 100644 --- a/ext-codec/G711Rtp.cpp +++ b/ext-codec/G711Rtp.cpp @@ -38,16 +38,12 @@ bool G711RtpEncoder::inputFrame(const Frame::Ptr &frame) { auto ptr = _cache_frame->data() + _cache_frame->prefixSize(); auto len = _cache_frame->size() - _cache_frame->prefixSize(); auto remain_size = len; - auto max_size = 160 * _channels * _pkt_dur_ms / 20; // 20 ms per 160 byte - uint32_t n = 0; + size_t max_size = 160 * _channels * _pkt_dur_ms / 20; // 20 ms per 160 byte + size_t n = 0; bool mark = true; while (remain_size >= max_size) { - size_t rtp_size; - if (remain_size >= max_size) { - rtp_size = max_size; - } else { - break; - } + assert(remain_size >= max_size); + const size_t rtp_size = max_size; n++; stamp += _pkt_dur_ms; RtpCodec::inputRtp(getRtpInfo().makeRtp(TrackAudio, ptr, rtp_size, mark, stamp), true); diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 6956c5a2..669caa06 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -297,22 +297,71 @@ static inline void addHttpListener(){ }); } +template +class ServiceController { +public: + using Pointer = std::shared_ptr; + std::unordered_map _map; + mutable std::recursive_mutex _mtx; + + void clear() { + decltype(_map) copy; + { + std::lock_guard lck(_mtx); + copy.swap(_map); + } + } + + size_t erase(const std::string &key) { + std::lock_guard lck(_mtx); + return _map.erase(key); + } + + Pointer find(const std::string &key) const { + std::lock_guard lck(_mtx); + auto it = _map.find(key); + if (it == _map.end()) { + return nullptr; + } + return it->second; + } + + template + Pointer make(const std::string &key, _Args&& ...__args) { + // assert(!find(key)); + + auto server = std::make_shared(std::forward<_Args>(__args)...); + std::lock_guard lck(_mtx); + auto it = _map.emplace(key, server); + assert(it.second); + return server; + } + + template + Pointer makeWithAction(const std::string &key, function action, _Args&& ...__args) { + // assert(!find(key)); + + auto server = std::make_shared(std::forward<_Args>(__args)...); + action(server); + std::lock_guard lck(_mtx); + auto it = _map.emplace(key, server); + assert(it.second); + return server; + } +}; + //拉流代理器列表 -static unordered_map s_proxyMap; -static recursive_mutex s_proxyMapMtx; +static ServiceController s_player_proxy; //推流代理器列表 -static unordered_map s_proxyPusherMap; -static recursive_mutex s_proxyPusherMapMtx; +static ServiceController s_pusher_proxy; //FFmpeg拉流代理器列表 -static unordered_map s_ffmpegMap; -static recursive_mutex s_ffmpegMapMtx; +static ServiceController s_ffmpeg_src; #if defined(ENABLE_RTPPROXY) //rtp服务器列表 -static unordered_map s_rtpServerMap; -static recursive_mutex s_rtpServerMapMtx; +static ServiceController s_rtp_server; #endif static inline string getProxyKey(const string &vhost, const string &app, const string &stream) { @@ -416,46 +465,23 @@ Value makeMediaSourceJson(MediaSource &media){ #if defined(ENABLE_RTPPROXY) uint16_t openRtpServer(uint16_t local_port, const string &stream_id, int tcp_mode, const string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex) { - lock_guard lck(s_rtpServerMapMtx); - if (s_rtpServerMap.find(stream_id) != s_rtpServerMap.end()) { + if (s_rtp_server.find(stream_id)) { //为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的stream_id return 0; } - RtpServer::Ptr server = std::make_shared(); - server->start(local_port, stream_id, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc, only_track, multiplex); + auto server = s_rtp_server.makeWithAction(stream_id, [&](RtpServer::Ptr server) { + server->start(local_port, stream_id, (RtpServer::TcpMode)tcp_mode, local_ip.c_str(), re_use_port, ssrc, only_track, multiplex); + }); server->setOnDetach([stream_id]() { //设置rtp超时移除事件 - lock_guard lck(s_rtpServerMapMtx); - s_rtpServerMap.erase(stream_id); + s_rtp_server.erase(stream_id); }); - //保存对象 - s_rtpServerMap.emplace(stream_id, server); //回复json return server->getPort(); } -void connectRtpServer(const string &stream_id, const string &dst_url, uint16_t dst_port, const function &cb) { - lock_guard lck(s_rtpServerMapMtx); - auto it = s_rtpServerMap.find(stream_id); - if (it == s_rtpServerMap.end()) { - cb(SockException(Err_other, "未找到rtp服务")); - return; - } - it->second->connectToServer(dst_url, dst_port, cb); -} - -bool closeRtpServer(const string &stream_id) { - lock_guard lck(s_rtpServerMapMtx); - auto it = s_rtpServerMap.find(stream_id); - if (it == s_rtpServerMap.end()) { - return false; - } - auto server = it->second; - s_rtpServerMap.erase(it); - return true; -} #endif void getStatisticJson(const function &cb) { @@ -546,15 +572,13 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream const ProtocolOption &option, int rtp_type, float timeout_sec, const mINI &args, const function &cb) { auto key = getProxyKey(vhost, app, stream); - lock_guard lck(s_proxyMapMtx); - if (s_proxyMap.find(key) != s_proxyMap.end()) { + if (s_player_proxy.find(key)) { //已经在拉流了 cb(SockException(Err_other, "This stream already exists"), key); return; } //添加拉流代理 - auto player = std::make_shared(vhost, app, stream, option, retry_count); - s_proxyMap[key] = player; + auto player = s_player_proxy.make(key, vhost, app, stream, option, retry_count); // 先透传参数 player->mINI::operator=(args); @@ -562,7 +586,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream //指定RTP over TCP(播放rtsp时有效) (*player)[Client::kRtpType] = rtp_type; - if (timeout_sec > 0.1) { + if (timeout_sec > 0.1f) { //播放握手超时时间 (*player)[Client::kTimeoutMS] = timeout_sec * 1000; } @@ -570,20 +594,68 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream //开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试 player->setPlayCallbackOnce([cb, key](const SockException &ex) { if (ex) { - lock_guard lck(s_proxyMapMtx); - s_proxyMap.erase(key); + s_player_proxy.erase(key); } cb(ex, key); }); //被主动关闭拉流 player->setOnClose([key](const SockException &ex) { - lock_guard lck(s_proxyMapMtx); - s_proxyMap.erase(key); + s_player_proxy.erase(key); }); player->play(url); }; + +void addStreamPusherProxy(const string &schema, + const string &vhost, + const string &app, + const string &stream, + const string &url, + int retry_count, + int rtp_type, + float timeout_sec, + const function &cb) { + auto key = getPusherKey(schema, vhost, app, stream, url); + auto src = MediaSource::find(schema, vhost, app, stream); + if (!src) { + cb(SockException(Err_other, "can not find the source stream"), key); + return; + } + if (s_pusher_proxy.find(key)) { + //已经在推流了 + cb(SockException(Err_success), key); + return; + } + + //添加推流代理 + auto pusher = s_pusher_proxy.make(key, src, retry_count); + + //指定RTP over TCP(播放rtsp时有效) + pusher->emplace(Client::kRtpType, rtp_type); + + if (timeout_sec > 0.1f) { + //推流握手超时时间 + pusher->emplace(Client::kTimeoutMS, timeout_sec * 1000); + } + + //开始推流,如果推流失败或者推流中止,将会自动重试若干次,默认一直重试 + pusher->setPushCallbackOnce([cb, key, url](const SockException &ex) { + if (ex) { + WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex; + s_pusher_proxy.erase(key); + } + cb(ex, key); + }); + + //被主动关闭推流 + pusher->setOnClose([key, url](const SockException &ex) { + WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex; + s_pusher_proxy.erase(key); + }); + pusher->publish(url); +} + template static void getArgsValue(const HttpAllArgs &allArgs, const string &key, Type &value) { auto val = allArgs[key]; @@ -973,59 +1045,6 @@ void installWebApi() { val["count_hit"] = (Json::UInt64)count_hit; }); - static auto addStreamPusherProxy = [](const string &schema, - const string &vhost, - const string &app, - const string &stream, - const string &url, - int retry_count, - int rtp_type, - float timeout_sec, - const function &cb) { - auto key = getPusherKey(schema, vhost, app, stream, url); - auto src = MediaSource::find(schema, vhost, app, stream); - if (!src) { - cb(SockException(Err_other, "can not find the source stream"), key); - return; - } - lock_guard lck(s_proxyPusherMapMtx); - if (s_proxyPusherMap.find(key) != s_proxyPusherMap.end()) { - //已经在推流了 - cb(SockException(Err_success), key); - return; - } - - //添加推流代理 - auto pusher = std::make_shared(src, retry_count); - s_proxyPusherMap[key] = pusher; - - //指定RTP over TCP(播放rtsp时有效) - (*pusher)[Client::kRtpType] = rtp_type; - - if (timeout_sec > 0.1) { - //推流握手超时时间 - (*pusher)[Client::kTimeoutMS] = timeout_sec * 1000; - } - - //开始推流,如果推流失败或者推流中止,将会自动重试若干次,默认一直重试 - pusher->setPushCallbackOnce([cb, key, url](const SockException &ex) { - if (ex) { - WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex; - lock_guard lck(s_proxyPusherMapMtx); - s_proxyPusherMap.erase(key); - } - cb(ex, key); - }); - - //被主动关闭推流 - pusher->setOnClose([key, url](const SockException &ex) { - WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex; - lock_guard lck(s_proxyPusherMapMtx); - s_proxyPusherMap.erase(key); - }); - pusher->publish(url); - }; - //动态添加rtsp/rtmp推流代理 //测试url http://127.0.0.1/index/api/addStreamPusherProxy?schema=rtmp&vhost=__defaultVhost__&app=proxy&stream=0&dst_url=rtmp://127.0.0.1/live/obs api_regist("/index/api/addStreamPusherProxy", [](API_ARGS_MAP_ASYNC) { @@ -1058,8 +1077,7 @@ void installWebApi() { api_regist("/index/api/delStreamPusherProxy", [](API_ARGS_MAP) { CHECK_SECRET(); CHECK_ARGS("key"); - lock_guard lck(s_proxyPusherMapMtx); - val["data"]["flag"] = s_proxyPusherMap.erase(allArgs["key"]) == 1; + val["data"]["flag"] = s_pusher_proxy.erase(allArgs["key"]) == 1; }); //动态添加rtsp/rtmp拉流代理 @@ -1100,8 +1118,7 @@ void installWebApi() { api_regist("/index/api/delStreamProxy",[](API_ARGS_MAP){ CHECK_SECRET(); CHECK_ARGS("key"); - lock_guard lck(s_proxyMapMtx); - val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1; + val["data"]["flag"] = s_player_proxy.erase(allArgs["key"]) == 1; }); static auto addFFmpegSource = [](const string &ffmpeg_cmd_key, @@ -1112,25 +1129,21 @@ void installWebApi() { bool enable_mp4, const function &cb) { auto key = MD5(dst_url).hexdigest(); - lock_guard lck(s_ffmpegMapMtx); - if (s_ffmpegMap.find(key) != s_ffmpegMap.end()) { + if (s_ffmpeg_src.find(key)) { //已经在拉流了 cb(SockException(Err_success), key); return; } - FFmpegSource::Ptr ffmpeg = std::make_shared(); - s_ffmpegMap[key] = ffmpeg; + auto ffmpeg = s_ffmpeg_src.make(key); ffmpeg->setOnClose([key]() { - lock_guard lck(s_ffmpegMapMtx); - s_ffmpegMap.erase(key); + s_ffmpeg_src.erase(key); }); ffmpeg->setupRecordFlag(enable_hls, enable_mp4); ffmpeg->play(ffmpeg_cmd_key, src_url, dst_url, timeout_ms, [cb, key](const SockException &ex) { if (ex) { - lock_guard lck(s_ffmpegMapMtx); - s_ffmpegMap.erase(key); + s_ffmpeg_src.erase(key); } cb(ex, key); }); @@ -1164,8 +1177,7 @@ void installWebApi() { api_regist("/index/api/delFFmpegSource",[](API_ARGS_MAP){ CHECK_SECRET(); CHECK_ARGS("key"); - lock_guard lck(s_ffmpegMapMtx); - val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1; + val["data"]["flag"] = s_ffmpeg_src.erase(allArgs["key"]) == 1; }); //新增http api下载可执行程序文件接口 @@ -1245,22 +1257,27 @@ void installWebApi() { api_regist("/index/api/connectRtpServer", [](API_ARGS_MAP_ASYNC) { CHECK_SECRET(); CHECK_ARGS("stream_id", "dst_url", "dst_port"); - connectRtpServer( - allArgs["stream_id"], allArgs["dst_url"], allArgs["dst_port"], - [val, headerOut, invoker](const SockException &ex) mutable { - if (ex) { - val["code"] = API::OtherFailed; - val["msg"] = ex.what(); - } - invoker(200, headerOut, val.toStyledString()); - }); + auto cb = [val, headerOut, invoker](const SockException &ex) mutable { + if (ex) { + val["code"] = API::OtherFailed; + val["msg"] = ex.what(); + } + invoker(200, headerOut, val.toStyledString()); + }; + + auto server = s_rtp_server.find(allArgs["stream_id"]); + if (!server) { + cb(SockException(Err_other, "未找到rtp服务")); + return; + } + server->connectToServer(allArgs["dst_url"], allArgs["dst_port"], cb); }); api_regist("/index/api/closeRtpServer",[](API_ARGS_MAP){ CHECK_SECRET(); CHECK_ARGS("stream_id"); - if(!closeRtpServer(allArgs["stream_id"])){ + if(s_rtp_server.erase(allArgs["stream_id"]) == 0){ val["hit"] = 0; return; } @@ -1271,19 +1288,18 @@ void installWebApi() { CHECK_SECRET(); CHECK_ARGS("stream_id", "ssrc"); - lock_guard lck(s_rtpServerMapMtx); - auto it = s_rtpServerMap.find(allArgs["stream_id"]); - if (it == s_rtpServerMap.end()) { + auto server = s_rtp_server.find(allArgs["stream_id"]); + if (!server) { throw ApiRetException("RtpServer not found by stream_id", API::NotFound); } - it->second->updateSSRC(allArgs["ssrc"]); + server->updateSSRC(allArgs["ssrc"]); }); api_regist("/index/api/listRtpServer",[](API_ARGS_MAP){ CHECK_SECRET(); - lock_guard lck(s_rtpServerMapMtx); - for (auto &pr : s_rtpServerMap) { + std::lock_guard lck(s_rtp_server._mtx); + for (auto &pr : s_rtp_server._map) { Value obj; obj["stream_id"] = pr.first; obj["port"] = pr.second->getPort(); @@ -1518,18 +1534,11 @@ void installWebApi() { api_regist("/index/api/getProxyPusherInfo", [](API_ARGS_MAP_ASYNC) { CHECK_SECRET(); CHECK_ARGS("key"); - decltype(s_proxyPusherMap.end()) it; - { - lock_guard lck(s_proxyPusherMapMtx); - it = s_proxyPusherMap.find(allArgs["key"]); - } - - if (it == s_proxyPusherMap.end()) { + auto pusher = s_pusher_proxy.find(allArgs["key"]); + if (!pusher) { throw ApiRetException("can not find pusher", API::NotFound); } - auto pusher = it->second; - val["data"]["status"] = pusher->getStatus(); val["data"]["liveSecs"] = pusher->getLiveSecs(); val["data"]["rePublishCount"] = pusher->getRePublishCount(); @@ -1539,18 +1548,11 @@ void installWebApi() { api_regist("/index/api/getProxyInfo", [](API_ARGS_MAP_ASYNC) { CHECK_SECRET(); CHECK_ARGS("key"); - decltype(s_proxyMap.end()) it; - { - lock_guard lck(s_proxyMapMtx); - it = s_proxyMap.find(allArgs["key"]); - } - - if (it == s_proxyMap.end()) { + auto proxy = s_player_proxy.find(allArgs["key"]); + if (!proxy) { throw ApiRetException("can not find the proxy", API::NotFound); } - auto proxy = it->second; - val["data"]["status"] = proxy->getStatus(); val["data"]["liveSecs"] = proxy->getLiveSecs(); val["data"]["rePullCount"] = proxy->getRePullCount(); @@ -1926,31 +1928,12 @@ void installWebApi() { } void unInstallWebApi(){ - { - lock_guard lck(s_proxyMapMtx); - auto proxyMap(std::move(s_proxyMap)); - proxyMap.clear(); - } - - { - lock_guard lck(s_ffmpegMapMtx); - auto ffmpegMap(std::move(s_ffmpegMap)); - ffmpegMap.clear(); - } - - { - lock_guard lck(s_proxyPusherMapMtx); - auto proxyPusherMap(std::move(s_proxyPusherMap)); - proxyPusherMap.clear(); - } - - { + s_player_proxy.clear(); + s_ffmpeg_src.clear(); + s_pusher_proxy.clear(); #if defined(ENABLE_RTPPROXY) - RtpSelector::Instance().clear(); - lock_guard lck(s_rtpServerMapMtx); - auto rtpServerMap(std::move(s_rtpServerMap)); - rtpServerMap.clear(); + s_rtp_server.clear(); #endif - } + NoticeCenter::Instance().delListener(&web_api_tag); } diff --git a/server/main.cpp b/server/main.cpp index c946beab..c25a89fb 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -392,8 +392,8 @@ int start_main(int argc,char *argv[]) { #endif//defined(ENABLE_WEBRTC) #if defined(ENABLE_SRT) - // srt udp服务器 - if(srtPort) { srtSrv->start(srtPort); } + // srt udp服务器 + if (srtPort) { srtSrv->start(srtPort); } #endif//defined(ENABLE_SRT) } catch (std::exception &ex) { From 1e3959433522808f3390bc9748ae96af793906dc Mon Sep 17 00:00:00 2001 From: johzzy Date: Sun, 10 Mar 2024 10:34:03 -0300 Subject: [PATCH 09/20] fix for https://bugs.chromium.org/p/webrtc/issues/detail?id=15845 (#3360) --- webrtc/WebRtcTransport.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 14bcf19e..786e26c3 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -31,7 +31,6 @@ #define RTP_CNAME "zlmediakit-rtp" #define RTP_LABEL "zlmediakit-label" #define RTP_MSLABEL "zlmediakit-mslabel" -#define RTP_MSID RTP_MSLABEL " " RTP_LABEL using namespace std; @@ -707,9 +706,9 @@ void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { // 发送的ssrc我们随便定义,因为在发送rtp时会修改为此值 ssrc.ssrc = m.type + RTP_SSRC_OFFSET; ssrc.cname = RTP_CNAME; - ssrc.label = RTP_LABEL; + ssrc.label = std::string(RTP_LABEL) + '-' + m.mid; ssrc.mslabel = RTP_MSLABEL; - ssrc.msid = RTP_MSID; + ssrc.msid = ssrc.mslabel + ' ' + ssrc.label; if (m.getRelatedRtxPlan(m.plan[0].pt)) { // rtx ssrc From 69800632feb9b90a7bac120c6bd72f8f8d1dae4d Mon Sep 17 00:00:00 2001 From: johzzy Date: Tue, 12 Mar 2024 23:48:17 -0300 Subject: [PATCH 10/20] feat(html): update webrtc page (#3361) optimized webrtc page --- www/webrtc/index.html | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 26a129b7..87c217b7 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -115,17 +115,10 @@ document.getElementsByName("method").forEach((el,idx) => { el.checked = el.value === type; el.onclick = function(e) { - let url = new URL(document.getElementById('streamUrl').value); + const url = new URL(document.getElementById('streamUrl').value); url.searchParams.set("type",el.value); document.getElementById('streamUrl').value = url.toString(); - - if(el.value == "play"){ - recvOnly = true; - }else if(el.value == "echo"){ - recvOnly = false; - }else{ - recvOnly = false; - } + recvOnly = 'play' === el.value; }; }); @@ -145,6 +138,25 @@ let h = parseInt(res.pop()); let w = parseInt(res.pop()); + const url = new URL(document.getElementById('streamUrl').value); + const newUrl = new URL(window.location.href); + let count = 0; + if (url.searchParams.has('app')) { + newUrl.searchParams.set('app', url.searchParams.get('app')); + count++; + } + if (url.searchParams.has('stream')) { + newUrl.searchParams.set('stream', url.searchParams.get('stream')); + count++; + } + if (url.searchParams.has('type')) { + newUrl.searchParams.set('type', url.searchParams.get('type')); + count++; + } + if (count > 0) { + window.history.pushState(null, null, newUrl); + } + player = new ZLMRTCClient.Endpoint( { element: document.getElementById('video'),// video 标签 From 8e16a698b6c0a7774df2a2bde57ea8a3d7eea6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=BC=BA=E5=85=88=E7=94=9F?= <74175499+linshangqiang@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:57:43 +0800 Subject: [PATCH 11/20] =?UTF-8?q?=E9=99=8D=E4=BD=8Ewebrtc=E6=8F=A1?= =?UTF-8?q?=E6=89=8B=E6=9C=AA=E7=BB=93=E6=9D=9F=EF=BC=8C=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=85=88=E5=88=B0=E7=9A=84=E6=97=A5=E5=BF=97=E7=AD=89=E7=BA=A7?= =?UTF-8?q?=20(#3368)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/DtlsTransport.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webrtc/DtlsTransport.cpp b/webrtc/DtlsTransport.cpp index 5ae4f4b0..83311736 100644 --- a/webrtc/DtlsTransport.cpp +++ b/webrtc/DtlsTransport.cpp @@ -741,8 +741,7 @@ namespace RTC if (!IsRunning()) { - MS_ERROR("cannot process data while not running"); - + MS_WARN_TAG(nullptr,"cannot process data while not running"); return; } From af155ef87a4c7fb77f6f23fa9880611dc22b30f8 Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Sat, 16 Mar 2024 18:56:35 +0800 Subject: [PATCH 12/20] no track throw error avoid addstreamproxy already exist --- src/Common/MediaSink.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Common/MediaSink.cpp b/src/Common/MediaSink.cpp index 41b4f415..f888b297 100644 --- a/src/Common/MediaSink.cpp +++ b/src/Common/MediaSink.cpp @@ -187,6 +187,8 @@ void MediaSink::emitAllTrackReady() { pr.second.for_each([&](const Frame::Ptr &frame) { MediaSink::inputFrame(frame); }); } _frame_unread.clear(); + } else { + throw toolkit::SockException(toolkit::Err_shutdown, "no vaild track data"); } } From c9c2706843a3b8aee8f078fa0bd2e4ae4f46a67e Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Sat, 16 Mar 2024 19:34:06 +0800 Subject: [PATCH 13/20] avoid addstreamproxy rtsp user or pass contain + --- src/Common/Parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index ad33f575..ab41c53e 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -294,8 +294,8 @@ void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const st splitUrl(ip, ip, port); _url = std::move(url); - _user = strCoding::UrlDecodeComponent(std::move(user)); - _passwd = strCoding::UrlDecodeComponent(std::move(passwd)); + _user = strCoding::UrlDecode(std::move(user)); + _passwd = strCoding::UrlDecode(std::move(passwd)); _host = std::move(ip); _port = port; _is_ssl = is_ssl; From 1930d909f9ab5afca0d80cc6642168659d64db59 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Fri, 15 Mar 2024 22:24:20 +0800 Subject: [PATCH 14/20] Fix the thread safety issue caused by poller thread switching when paced sender enabled --- src/Common/MultiMediaSourceMuxer.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 5b3b8d72..fa46e4fa 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -44,6 +44,7 @@ public: } void resetTimer(const EventPoller::Ptr &poller) { + std::lock_guard lck(_mtx); std::weak_ptr weak_self = shared_from_this(); _timer = std::make_shared(_paced_sender_ms / 1000.0f, [weak_self]() { if (auto strong_self = weak_self.lock()) { @@ -55,6 +56,7 @@ public: } bool inputFrame(const Frame::Ptr &frame) override { + std::lock_guard lck(_mtx); if (!_timer) { setCurrentStamp(frame->dts()); resetTimer(EventPoller::getCurrentPoller()); @@ -66,6 +68,7 @@ public: private: void onTick() { + std::lock_guard lck(_mtx); auto dst = _cache.empty() ? 0 : _cache.back().first; while (!_cache.empty()) { auto &front = _cache.front(); @@ -110,6 +113,7 @@ private: OnFrame _cb; Ticker _ticker; Timer::Ptr _timer; + std::recursive_mutex _mtx; std::list> _cache; }; @@ -593,15 +597,17 @@ void MultiMediaSourceMuxer::resetTracks() { } } -bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame) { +bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { + auto frame = frame_in; if (_option.modify_stamp != ProtocolOption::kModifyStampOff) { // 时间戳不采用原始的绝对时间戳 - const_cast(frame) = std::make_shared(frame, _stamps[frame->getIndex()], _option.modify_stamp); + frame = std::make_shared(frame, _stamps[frame->getIndex()], _option.modify_stamp); } return _paced_sender ? _paced_sender->inputFrame(frame) : onTrackFrame_l(frame); } -bool MultiMediaSourceMuxer::onTrackFrame_l(const Frame::Ptr &frame) { +bool MultiMediaSourceMuxer::onTrackFrame_l(const Frame::Ptr &frame_in) { + auto frame = frame_in; bool ret = false; if (_rtmp) { ret = _rtmp->inputFrame(frame) ? true : ret; @@ -629,7 +635,7 @@ bool MultiMediaSourceMuxer::onTrackFrame_l(const Frame::Ptr &frame) { } if (_ring) { // 此场景由于直接转发,可能存在切换线程引起的数据被缓存在管道,所以需要CacheAbleFrame - const_cast(frame) = Frame::getCacheAbleFrame(frame); + frame = Frame::getCacheAbleFrame(frame); if (frame->getTrackType() == TrackVideo) { // 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处 auto video_key_pos = frame->keyFrame() || frame->configFrame(); From 50f65c4ba46f705d4c5427394be5b2adab2ef458 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Fri, 15 Mar 2024 22:24:33 +0800 Subject: [PATCH 15/20] Random port pool ensures that both UDP and TCP modes are available simultaneously --- src/Rtsp/Rtsp.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index 331daf15..dc2d0944 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -352,12 +352,20 @@ public: } void makeSockPair(std::pair &pair, const string &local_ip, bool re_use_port, bool is_udp) { - auto &sock0 = pair.first; - auto &sock1 = pair.second; auto sock_pair = getPortPair(); if (!sock_pair) { throw runtime_error("none reserved port in pool"); } + makeSockPair_l(sock_pair, pair, local_ip, re_use_port, is_udp); + + // 确保udp和tcp模式都能打开 + auto new_pair = std::make_pair(Socket::createSocket(), Socket::createSocket()); + makeSockPair_l(sock_pair, new_pair, local_ip, re_use_port, !is_udp); + } + + void makeSockPair_l(const std::shared_ptr &sock_pair, std::pair &pair, const string &local_ip, bool re_use_port, bool is_udp) { + auto &sock0 = pair.first; + auto &sock1 = pair.second; if (is_udp) { if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) { // 分配端口失败 From 69738ad24e6c391bb3203c61f06144faedd98809 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Fri, 15 Mar 2024 22:24:45 +0800 Subject: [PATCH 16/20] BugFix: configuration of `downloadRoot` cannot use absolute paths Fix for http api `/index/api/downloadFile` --- server/WebApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 669caa06..d0ab2e43 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -1879,7 +1879,7 @@ void installWebApi() { std::set ret; auto vec = toolkit::split(str, ";"); for (auto &item : vec) { - auto root = File::absolutePath(item, "", true); + auto root = File::absolutePath("", item, true); ret.emplace(std::move(root)); } return ret; From 3b7f16b75557a552d6907c60f87f4c06028c5f62 Mon Sep 17 00:00:00 2001 From: haorui wang <56127613+HR1025@users.noreply.github.com> Date: Sat, 16 Mar 2024 21:58:33 +0800 Subject: [PATCH 17/20] Fix the issue of failing to push streams to FMS 3.0 server. (#3362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [how] 1. AMF 为简单类型时填插 AMF Null (参考 OBS 以及实际测试) 2. createStream 前附加 releaseStream 和 FCPublish, 兼容旧 FMS3.0 3. 正确处理 RTMP Header fmt 为 1 和 2 的业务逻辑 --- src/Rtmp/RtmpProtocol.cpp | 19 ++++++++++++++++++- src/Rtmp/RtmpProtocol.h | 4 ++++ src/Rtmp/RtmpPusher.cpp | 30 ++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/Rtmp/RtmpProtocol.cpp b/src/Rtmp/RtmpProtocol.cpp index 010e30d4..f98468bf 100644 --- a/src/Rtmp/RtmpProtocol.cpp +++ b/src/Rtmp/RtmpProtocol.cpp @@ -165,7 +165,14 @@ void RtmpProtocol::sendResponse(int type, const string &str) { void RtmpProtocol::sendInvoke(const string &cmd, const AMFValue &val) { AMFEncoder enc; - enc << cmd << ++_send_req_id << val; + if (val.type() == AMFType::AMF_OBJECT || val.type() == AMFType::AMF_NULL) + { + enc << cmd << ++_send_req_id << val; + } + else + { + enc << cmd << ++_send_req_id << AMFValue() << val; + } sendRequest(MSG_CMD, enc.data()); } @@ -619,12 +626,22 @@ const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) { case 12: chunk_data.is_abs_stamp = true; chunk_data.stream_index = load_le32(header->stream_index); + _last_stream_index = chunk_data.stream_index; case 8: chunk_data.body_size = load_be24(header->body_size); chunk_data.type_id = header->type_id; + _last_body_size = chunk_data.body_size; + _last_type_id = chunk_data.type_id; case 4: chunk_data.ts_field = load_be24(header->time_stamp); } + switch (header->fmt) { + case 2: + chunk_data.type_id = _last_type_id; + chunk_data.body_size = _last_body_size; + case 1: + chunk_data.stream_index = _last_stream_index; + } auto time_stamp = chunk_data.ts_field; if (chunk_data.ts_field == 0xFFFFFF) { diff --git a/src/Rtmp/RtmpProtocol.h b/src/Rtmp/RtmpProtocol.h index 7ace93eb..2b7adfff 100644 --- a/src/Rtmp/RtmpProtocol.h +++ b/src/Rtmp/RtmpProtocol.h @@ -11,6 +11,7 @@ #ifndef SRC_RTMP_RTMPPROTOCOL_H_ #define SRC_RTMP_RTMPPROTOCOL_H_ +#include #include #include #include @@ -87,6 +88,9 @@ protected: private: bool _data_started = false; int _now_chunk_id = 0; + uint32_t _last_stream_index = 0; + size_t _last_body_size = 0; + uint8_t _last_type_id = 0; ////////////ChunkSize//////////// size_t _chunk_size_in = DEFAULT_CHUNK_LEN; size_t _chunk_size_out = DEFAULT_CHUNK_LEN; diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index 92363241..8f3596fc 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -163,14 +163,28 @@ void RtmpPusher::send_connect() { } void RtmpPusher::send_createStream() { - AMFValue obj(AMF_NULL); - sendInvoke("createStream", obj); - addOnResultCB([this](AMFDecoder &dec) { - //TraceL << "createStream result"; - dec.load(); - _stream_index = dec.load(); - send_publish(); - }); + // Workaround : 兼容较旧的 FMS3.0 + { + { + AMFValue obj(_stream_id); + sendInvoke("releaseStream", obj); + } + { + AMFValue obj(_stream_id); + sendInvoke("FCPublish", obj); + } + } + { + AMFValue obj(AMF_NULL); + sendInvoke("createStream", obj); + addOnResultCB([this](AMFDecoder &dec) { + //TraceL << "createStream result"; + dec.load(); + _stream_index = dec.load(); + send_publish(); + }); + } + } #define RTMP_STREAM_LIVE "live" From ff43fa507535493d56ef2fbde956212475dc0f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BE=E9=B8=A3?= <94030128+ixingqiao@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:01:06 +0800 Subject: [PATCH 18/20] Fix the issue of abnormal timestamps during MP4 recording. (#3363) Co-authored-by: xingqiao --- src/Record/MP4Recorder.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index f54c1d42..c8cee016 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -117,11 +117,13 @@ bool MP4Recorder::inputFrame(const Frame::Ptr &frame) { if (!(_have_video && frame->getTrackType() == TrackAudio)) { //如果有视频且输入的是音频,那么应该忽略切片逻辑 if (_last_dts == 0 || _last_dts > frame->dts()) { - //极少情况下dts时间戳可能回退 - _last_dts = frame->dts(); + //b帧情况下dts时间戳可能回退 + _last_dts = MAX(frame->dts(), _last_dts); + } + auto duration = 5; // 默认至少一帧5ms + if (frame->dts() > 0 && frame->dts() > _last_dts) { + duration = MAX(duration, frame->dts() - _last_dts); } - - auto duration = frame->dts() - _last_dts; if (!_muxer || ((duration > _max_second * 1000) && (!_have_video || (_have_video && frame->keyFrame())))) { //成立条件 // 1、_muxer为空 From 437ae778c0f85eeb094477e1a6da39625b097a0b Mon Sep 17 00:00:00 2001 From: KkemChen <704361748@qq.com> Date: Sat, 16 Mar 2024 22:56:32 +0800 Subject: [PATCH 19/20] feat: VideoStack (#3373) --- CMakeLists.txt | 6 + conf/novideo.yuv | 1 + postman/ZLMediaKit.postman_collection.json | 71 ++- server/VideoStack.cpp | 590 +++++++++++++++++++++ server/VideoStack.h | 207 ++++++++ server/WebApi.cpp | 34 ++ 6 files changed, 906 insertions(+), 3 deletions(-) create mode 100644 conf/novideo.yuv create mode 100644 server/VideoStack.cpp create mode 100644 server/VideoStack.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 96e79890..d951bc3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -532,3 +532,9 @@ 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}) + +# 拷贝VideoStack 无视频流时默认填充的背景图片 +# Copy the default background image used by VideoStack when there is no video stream +if (ENABLE_FFMPEG AND ENABLE_X264) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/novideo.yuv" DESTINATION ${EXECUTABLE_OUTPUT_PATH}) +endif () \ No newline at end of file diff --git a/conf/novideo.yuv b/conf/novideo.yuv new file mode 100644 index 00000000..37b41363 --- /dev/null +++ b/conf/novideo.yuv @@ -0,0 +1 @@ +ո|iamxt_PGEKQcqŠO9;=99A<<:V~X73==74:697B\ҦI??hفR<>>=;<<<;<<<@8?<<::MsA<<====<9>?<<=>=<<;<>8AC=Q|܏[B<;=?<:<8<=9B>;ZܥmG>:=>;:>:>>9@9=nǍL?9<=9:>;>=:B:LOB9;<99=9<<:7@6=>A8GmqTIC??HL>@BMiݸm`THMSEFFEM޾|oJACpΚE;ES{VD?;`֐g?;Mo۲y8B>LٗOGC*qnHB2PkΛWAA:?sDI@:hZBA@Lp﵀@9;Gl4;KGY꼉<:<:QiRD65FDzX<>=Aeаk9KeD7:=Bm‹M=:A9Jvݲt>5:>C[ȌQ@;:>EenC67@;GɐD67E??8CXӄ[96@?=>ʷyC9CC@@`۲jRBJ\wّQAD=7=EvŔhTQKBCGOZytK@>=98@JwܳdM98=;9;?9<98:HnؕY@===A:ZĊPC:>B=:;=9><==8L\kI@;>?977;6<;==<=<8LZ~M:=<;;>=:;>=B=>=7IMӘ]=<<39?K[DA<9:==:998>;=<7HDň62BA8CDOZI58::?9;:=:9=:=I[ܣPBA?:@;?69<<9;>:ATmuS:8=B<5IiŅZ98>?<4:<;:=>7D_٣f;6AB@9?Pݯk@7=><7A===?<8MuJ<@;=:;D^҄Q>=<<9D=>=;9BjaC:5====>|یYA;:<6<;>==JcU;6B?>:9X{P>BC}X<=aզmB9>99:>=hP<8C}ύJ:B998:8Pkٴu;<99;BMxXDAR{ɄP?:>:8@<=^ڟPA>=Vn?4<;5@:SzN=CBa]Q:C6;6D@J[ͫ^D=>F̔`<@<;:;>?::\~ЍOD>@QsԎXB:>86<>ExQC;?=̝U6C?8<:6CBW܎RFF:@Tp@B@:<>7B=>bحk>=:7RҚ`D=>??=?;?4SQ;B>97:;?=><>Y˄V?<;=Pբd>9:>@>?@;BT͟b?==8Cg}E9=:?=>B>:?xrK7B>;W[C@9=;9A@;67>;9B8LzmS:?<:Mh|EA;5:D>9A:CMۘDE9?;9ZݥkF89A?>?8@?6xÌG@>>9<=:>:;OtR9A?<>PYI@<79>:A9>@UϛU;5>C;Fx}W<:::><[ӫg76=<;<5C<6AQzφI>?A3;EsуE<><:;8F84@BPәe?89B8B[fI>=<:9Vݺn?A;A:;;<=>=>\֠hE7;@B:Sj։QE<@;:<<<<>=O{{RB?;8>:jkK:<;;;;<<>>EJ˒U>>?:8>>ΏX:9<<;;<<===;uqG;<79AJToE;;<;;<<<=8?YoxW>@?66BY{ՎZ:<=<<<<<<D˞[B6?D<:?eyC====;<<;?>8=tߦjE845;FTiω@77;>;;:@:<>AWԑg<>76<=FJ͕]8?C:@?@@;<=;IxlO:<:<@6C`rG=>9>>@@;=>7A6@=9;|涒@4H:<<::<<==A>XЈb@7>A?;6EM֬Z@B:<<;;<==>>:EogO<9==<>?==N„E>;>>99>?YԢm9C<==<<==>?A>B`ٖ\;9;?GjύC@======<>;;<=EjlF?>?@<<>BY[=?>>===<>>8:;7JÇQ??>;::=BDjĊP?>=====>A:;@:=gaB=<<989<@Ckۺr?>?====>?9@`ފP@>99>9>>>>>@@BCh?G;:8:?>8:BGiI>;=B<>===<>??@ixFC??<76:@A=@SuΎR:9:?;9;;:;<==>Fkٛ`E<9>;67@A8>?Jgf>67=;9=;<;;;<;>Sǎ]G7=B@<<=;BAEMViԊU;7>=?>>=<<<<=>HaʘwNC>BB<G[quC8=9@>=<====>==@[返hOE=:=;CGA;:=?BJ`~^C@1<=;;<<<=>@9@JgΠsRCAC?A>:>DC;A8:Jgϖf=0<=<;<>??>7F<=OڹZHB@>B>=@NduF0=><<<=>=<>B=>FeÞuP@;<<===;:B<>>BGm߫rNBA=>?7?<>@?<989EwrO><;<<>=<:?9@>B>IدoPC;;@:??===?ABB=g͛g8;:;<>><<::A:B@A>;<=?@>AjÀA=<;<=>>=9=?:A?;LqbE>9A@>===???=9BxݛcBA<<<===;@9;>O诈HD?;;<<<=?4?=9J?=l΀IAF<>?==??<;;>@bעX;?;:@7@<<<====<;O{Ϸ_M:7=A=8;8AD0B@[oK?=:<:><<<<===<7D\PE;9>?==?=:?;@@uj@<=:@<=======<;?Ch܊PA>=>=:;?>@7=EAKÊG>=:F:========A@>=BaSDC@>=;99;<9BB;Z~S<:B7=<======?;A<<=A8A?CuٰoA=<;==<<<<<=A;;=>;=CeՇGA;A=<<<<<<?;9<=;;:>D=8Qsޢ^><>>;<@@?=<:>=ATćQ;<;??<<==:>:<@;7EW~Q;79;;=<;<9Suh@<8=><<;;=7lʈV>;<<<=><;==<<@B>6IٸmEF@??=<;>=?CB;@Z迀Q?E;;A:=>?>=<;=G^ҔFA<7:<<<=<>?=<>???Fb՟VEBC;;A9=>?><;==KcI@;;<;;=><>?=<=?>AJvEAA@??@=>>=<;<=>a҆QE=;=;:=>9;@@==B===<<=>~߿z2;?B8;:<<======@:BGlצXH<=>==>==;?;=C789<=======:7>>V{M;==>=>>=>7TtQ8;98;<=====<=7;>7DcՈMD6<=<=>==;=A@NvB=@:<<=====<<>><;CBTX;@>;;==<=<<;<=;<========>9::A@Bgл[JB;9;<<<=<<:=@>9PN=;>========>7><<@>Kvlc_[XUJIGGGHJKUX^enxٯxJ>B99;;<;<<:;>>@Cf΢Y;=B<<====<=@:D?8A@COc`_^VZZTNHEFGECGFDDDFHIEFFGHLQURY^_bo}_C4@>>::<;<<:;:9CT{K;>=<;:9;=?B<:?@>;=EDB@?@ABA@?>@???AAAAAAAAAAAAAAAAAA@>AJWb~̞[;B=<;<>>G0Bw؝^><<<<;;<>?@=<@B>>?BBABBDEFCA?>>???AAAAAAAAAAAAAAAA?ACA=;;?7=AIa{K8@;:@;<<===>??D7VɆOA;=<==>@@>>@CCAACBCEGIKMMIFB@??@AAAAAAAAAAAAAAAAA@????DsY;===??@AA>@ABBCFJHJMQSUUURPLFB@ACAAAAAAAAAAAAAAAA=?AB@ACC69;8;??:B<@B:;?:?;;>??>=A^oP>=>?AAAA>@CBBGMRRTWZ\]\\\ZVOHDCCAAAAAAAAAAAAAAAAAA@>>=?A<=;:=CEC>>@?;=>:@;:==<=?Hzިq^????@@AA@BDCGOVY\]_`a``_bb_XOGCCAAAAAAAAAAAAAAAA@@@@@><:A>=:9884;=;;=>=???AEGHOY]\aabca`_^`cc^TJDBAAAAAAAAAAAAAAAA>?@ABCA@>?ADFD@=;<;<@>;?=;<>>=>>?AFIMV`a]aabba_^^^be`VLEBAAAAAAAAAAAAAAAAB@==>?@B@>;;<;<<<>>?B<9@<:<@>=>=<;;=<=====<>;;>=;FW۶kVEDE??DB??CC@@AGKUadaacba``bba`_``]VMFCAAAAAAAAAAAAAAAA@@@@AA@@??===<;;=<=====<>;;>=AB@ACJQ[debbeb``bdddc`bc`YOGBAAAAAAAAAAAAAAAA@@@@AA@@@?>=<<<;==<<<<<<<;<>=;BL~⸇fQHBDEB?ABAB??@A@BFNW`edaada``adeedacdaYPFBAAAAAAAAAAAAAAAA@@@@AA@@@@>=<<<<===<<<<<;:=?<;?GUfɢyR=?K@B@=?EGDC@@@??CIRZdeb`ac``_`bcbcbdd`XNFAAAAAAAAAAAAAAAAA@@AAAA@@@@>>=<;<<=====<<;;=><;>B>DLW^db``bb`_^_`aa`ccb]UKD?@AAAAAAAAAAAAAAA@@AAA@@@A@?>=<<<<<====<<;<=>=;=?9GD@]ʬa?;FID=A@BFFDBBEB@?==DN^bc`_bdba`_``a`_cb`ZQHB>@AAAAAAAAAAAAAAAAAAA@@@AAA?>======<<==<<<=<==>?@4?\֤vKJE@?BA>@>@DD@BIEC@><=EOadc_`dfbba`aaa``ba^WNFA=AAAAAAAAAAAAAAAAAAAA@@@AAA@?>><==<<====<===;<>@@>22CG>>@@@?>>??@ABBB??AA??FO_`bb``ad``abaabba\TLGC@=AAAAAAAAAAAAAAAAAAAAAAA@BA@@@@?@>==<<<<<<<<<====9998:@HNkຈ]HD===>??>>===>@ABB??AA>>DM^`bca```^_aba``_^XNFCA@?AAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====<<<;;=@CD^ṂVB;;=<=?BCB@?><<>@AA@@AA>=AIY]acb`^]aabb`^]\WQF?=>@AAAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====<=??=;;:3=Xر}UEDC>??AFKNMLIEA?@BBB@@@A>@BAAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====:;>?>=<<<=AS}ףxL<@CDD@@DLTYYWTMEA@BA@A?@A?<=BHNV]`aaa`_^[WTPMGC@>?@A@AAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====<<==>????><=QөoR=<><>F?@DNX^^\ZQF@?@?>A?@BA>=ABGNUXZ\\WVSQMIFDA@?@BB@?AAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====@?=<=>>>?E@@DNZ`_\]SF?>@?>A?@CC??A@CHLNOQRNLIFCB@@?>>?AA@>AAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====?<;<=>;;=BAFO[a_\_TG??AA@A?ADD@?AABEFFFHIHFDA@?@A?>==?@?>AAAAAAAAAAAAAAAAAAAAAAAABBAAAA@?===<<<<<<<<<====:89;>?<:>69DB8=Kvڮ|ECDCA@?ABCCCFNY`bbaXLECCCBAAAAAAAABBAAAAAAAAAAA@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?><;;<<;<<<<<<<<<<==<<<<<<;;<<<;DrڰrM9MCA@A@ABCCCEKSXYXYQGBACCCAAAAAAAABBAAAAAAAAAA@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?><;<<<<<<<<<<<<=<==<<<<<<;;;;<<9EA@AA@ACCDCCFKMMKNHA>?BDDAAAAAAAABBBBBBBB@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?><;<<<<<<<<<<<<====<<<<;;<<;;;<8:N{|UGF>8?@AABBBCCBBCEEDBFC?=?BCDAAAAAAAABBBBBBBB@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@?<;<==<<<<<<<<;====<<<<;;;<;;;<8@AO^JE=>@@@ABACCCAAABBBA@CB@?@ABBAAAAAAAABBBBBBBBAAAAAAAAAAAA@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@?=<<==<=======<=<<<===<===<<<==:>>?UӋ]HCD9AG@@ABCBAA@AABBBAABBBAA@@@AAAAAAAABBBBBBBBAAAAAAAAAAAA@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@?=<===========<=<<<========<<=<=6:CCU͢nG=>H@I@?AACCA@@?ABBBBBB@ABBA@??AAAAAAAABBBBBBAAAAAAAAAAAAAA@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@A@=<=>>========<<=<<<<=========@BBA???AAAAAAAABBBBBBBBAAAAAAAAAAAAAA@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@B?<<<==<<<<<<<<<<=<=<<<<<<==<<<=?><;:DXkʈG@@@AAAAAAAAAAA@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@A@@AAAAAAAA@@@@@@@@>>>>>>>>>>>>>>>>>====>>=`FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@?????????????????????????>?>??>?c´xUHM@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@????@@@@HgþxvtrnmjjhgdcbccdgffecdbbbZX]T@:?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAA@AAA@@@AA>JXdfbbfccdefhjkmnqswy{||yvutrpnkjhhfedc`^\[QPMKHFCCBB@@?BBD<<>>>???A<;>DC>:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?????@@??@>>A>;=BDDCBBBABFGILORTVXZ]_abaalnqsuwz{pmhc^XSPJKJJIIIHKKJIHGFEECBAAAAACBCCCCCBCCBAA@??BA@?>?@@DDBCCBABBFFABGIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@AAAAAAAA@@@@@@@@@@@@@@@?????@@@@EBAABAAC@@??>>>>>??@ABCDCDEEEDBACEFFFFHIDEEFGIJKHGHHHHHITW]bfkor}~zwromnnkhd`\ZSPLIFC?EEEDEEFFBBBBAABBCCCCBAAABCDCA@AAFFEEDCBAFEDCB@?>>@DFIMQTVWZ^bfikmnqtz~ļ{xsqmgaZURKIEA?@AB???@@@@AAAA@?>==@?>@BCBBCCA@><;:AAAAAAABCBA@@@??AABA@@??A@@@@@@@==>>>>>=AAA@ABBA@?@CDDA=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@AAAAAAAABBBBBBBBBBBBBBBBBBBBCCCC@DC?AFGCA@?@@@@A@AAABAAA@@@AACDDABCBAABCABBB@><;===>>???<<<;:;=?>??@@AAB??>=:9999=ACA@ACIOV\`emssv|xvsokhed``^][YXWPONLIGFEDDDCCCCCAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@AAAA@@@@@@AAAAAAAAAAAAAAAAAAAAA@AAA@AAAA@@@AA@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBBBBBBCCBBBBCCBBBCBBBCCCCCCBBBBCCCBBBBBBBBAAAAAAAA@@AAAAAA????>>>??=<<<=>?=======>;<;;<<<<<<<;<<<<=>@CFIKLVWY]`dfigiknqtwx}yomga\VPMKJIHFEDDCDCCBAA@EDDCBAA@AA@@@@@@AAAA@@@@A@A@A@A@A@A@A@AAAAAAAAAAAAAAAAAAAAAA@@@@@@AAAAAAAAAAAAAAAAAAAAAA@@@AAAA@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCBCCCBCBCBBCBCCCCCCCCCCCCCAAAAAAAA@A@@@@@@@@@@@@@@?>===>?@>>>>>>>=<;;<<<<<;;<<<<<<<<==>>>?::;<=>??=>>?@@AAHLRY_fmrx|ü|wkid_ZWUTYWUQNJFEKHGFFECB?????@@A@AABBBBC@@@AABBB????????@@AAAAA@@A@A@A@A@A@A@A@@@@@@@@@@@@@@@@@AAAAAAAAA@@@@AAAAA@A@A@A@A@A@A@AAAAAAA@@@@AAA@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBCCBBBBBBCCCCCCCCCCCCBCBCBBCBCCCCCCCCCCCCCAAAAAAAA@AAAAA@@@@AAAAAA>><=<>>@=>=>=>=>;<<<<;;<<<<;;;;;==<<;:::<<;;::98>?>>===<<=@CDEGIIKNQSUXZSW[]`cjoy|ɿ|{wpic\VROLIEBA@B??@AACCD?=>@BDDDAAA@@@@@@??@@AAA@@AABCDDAAAAAAABAAAA@@@A@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAA@@@@AAAAA@@@@@@@@@@@@@@@@AAAA@@@@@A@AA@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCBCBBBBBBCCCCCCCCDDDDDDDDAAAAAAAA@AAAA@@@ABBBBBBB>=<<<=>?=======>;<<<;;;<;<<<<<<<<<====>>=====>>>88888789@AA@;876>??>:9:;AACBBEIMRTX_fmrvy{ҿxfXLD@B?>>??@?BAA@AA?>BAA@????CDDDCEDEA@?>?>=;@???>>>>>>>>>>>>@@@@AAAABCBBBBBCAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@A@A@@@@@AAAA@@@@A@@@@@@@@@@@@@@@@@@@@@@AAA@A@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBAABBBBBAAAAAAAAAAABBBAAAAAAAAAABBCBBBBBBB@@@@@@@@@@@@@@@ABBBBBBBB?>===>?@>>>>>>>><<<<<<<<<<<<<;;<:::;<<==8899:::;??@@@@@@:<>><;;;;<==<<=?9;:;::;<;;:::;;<99999:;;BCISbvιvqf]RG?<:9BA?@CDED?>?@BCBB@?@ACCCB???????>ECAABA@?BBBCCDDEEFEEDDCDBCBBABABAAAAAAAA@@@@@@AA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AA@A@AAAA@@@@@@@@A@@@@@@@@@@@@@@@@@@@@AAAAA@A@A@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAABBBBBAAAAAAAAAAAAABABABAA@A@A@AA@@@A@AAABBBBBABA?>===>?@>>>>>>>>;;;;;;;;;;;;;<;;==<<<;;;>>>====<:::99888;=??>@@A8:;<<<>@==>????>==;:9888=<<;:9889:=DLV`dhp{ègQB>ADC@@DFHJCCA@???>FDBABA?>?@@AABAB@ABCDEEE=<<>@AA@??@AABCCCCCCBBBBEEDDDCCC??????@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@A@@@@@@@@A@A@AAAAAAAAAAAAAAAA@@AAAA@@@@@@A@A@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAA@@@@@@@@@@@@@@@@BBBBBBBBBBBBBBBBAAAABBAAABBBBAAABAAAABBBBAAAAAAAAAAAAAAAAAAAAAAA@AAAAAAAA@@@@A@A>=<<<=>?========<;;;;;<<<<<<<<<==<;::;;;:::99??>=====<;;;978:;<=<:9:;88789::9:::;<=>?88:;<<=<<;99@BBI\{ġg\OJHCABDCABB@>=>==>@BBB@?>@AB@@AA@??@ABAABA@???CB@AAA@?BBBBBBBBDDDEEEEFBBBBBAAA@A@A@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@A@AAAAAAAAAAAAA@@AAAA@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@A@A@A@A@A@A@A@AAA@A@A@A@A@A@A@A@@A@AAAAAAAAAAAAAAAAAAAAA@@@@@A@@@@A@A@A@A@A@A@AABBBBBBBBBBBBBBBBAAAAABAAAAAABBBBAAAAAABBBBAAAAABAAAAAAAAAAAAAAAA@AAAAAAAAAAA@@@@?>=<==>?=====>=>;<;<;;;;;;<<<<<<;;;;;;::;;;<<===:;;<=>>>???><<=?:;<<;;;<><:9:;;=;;9:899:;<<=;;99<;;::<>><>@ADMZf®dTGDA>>?BC@?@@AABBBBAA@AAACCCBAA@???@@@@@@>??@@ABACB@>>@BD=>?@@>>;AAAAAAAAAAAAAAAAA@A@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@AAAAAAAAAA@AAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@A@A@A@A@A@A@A@A@@A@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@A@A@@@@@@@@@AA@@??>>::;<=>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;<<<<<<<<<<<<<<<<<<<<<==========<=<=<=<=<=<=<<;;;;;<<<<<<<;;;>::<<==>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================<<<<<<<<<<<<<<<;;>QgṘv_I@???@BCDCBA@@AAAABB@@ABBCDDCCBAA@??A@@@@@@????????@EEDCBBA@BAA@@AAB@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAAAA@@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAA@@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@@@@@@@@@@@@@@AAA@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@??>>::<<==>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================<<<<<<<<<<<<<<<;:;=G[tֵV?EHMK?@@@ABBBDDDEEFFGHHHGFDCBCAA??@ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@AAA@@@@AAAAAA@A@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAA@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@??>>;;<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================<<<<<<<<<<<<<<<<9<68AANmҤ{`E=@99BBCBBA@@@@@@AAAABBCCCCCCBB@@@AAABBFFGIJKLMLLMOPQRSOOOONLIIEDB@@ABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@AAAAAAAAA@@AAAAAAAAAAAAAA@@AAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AA@@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@??>>;;<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<F@HcoŜpRHBEF;6=DDCA?>>?@@@AAAABBDDDCCBBBBCDFHIKJPQRTUWXYYYZ[]^_`[[[ZXVTSLKHDBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@AAAAAAAAA@@@@@@@@@@@@@@@@@@AAAAAAABBBBBBBB@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@??>><<======<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4558;59EKieNE;@@BJHBBBB@?>?@A@@AAAABBCCCCCCCCGHJMPSUXYYZZ[\]]__``aaabbbba`_]]YVQLGD@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@A@AAAAAA@@@@@@A@@@@@@A@A@A@AA@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@A@A@A@A@A@A@A@@A@@@@@AAAAAAAA@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@??>><<==<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>E?@@@@@AA@@AAAABBABBCDEDDKMPTY\_accccccccbbbaa```cdccba``c`YRKE@?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@AAAAAAA@@@@@@@@@@@@@@@@@@@AAA@AAAAAAABBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@A@A@A@A@A@A@AAA@@@AAAAAAAAAA@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@??>><<==<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@7;;2;B839C[}XKFCDAAA@AAAA>@@@@AAA@ABCCBA@?ADEFJQV_`bcddcbbaaaaabbbbaaaaabaaabcca_c_XPHB>=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@?AA@@A@@@A@?@@@AA@?AABBAA@@AA@@???>AAAAAAAAAAAA@@@@ABBBBBBBAAAAAAAABBBBAAAA@@AAAAA?@A@B@A???@?A@A@BBBBBBBBBAAABABBB@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??@@@@@@>=>==>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<<<<=8;BKk忓`F@?@AAAA@A@AA?@@@@AAAAABCCBAACHNRTWZ^_`abcba`a``aaaabaabbbbbcababcb_]\YSLEA>>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?>???@@BBAAABBAAAACCBA@@???@@@@?@@@@@@A@?>>>>>>>????>>>?????????????????@@@@@???@@@AA@@A@AABAB@A@A@A@A??BBAA@@@A@AA@????AAAAAAAA@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??@@@@@@>>>==>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<@@@??>>>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??@@@@@@>>>===>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<;<<<<>:<=;@]ͦeR@=BCAA@@@@@AAA@@@AAAABBBBCCBBBPV^decddcbaaa``__^`_``a`a`a`a`a``_^\ZVQNGEDCBABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@??@@AAABAABBB@@@??>@@ABBBA@BABA@@???AAAAAAACCCCCCCCAAAABBBCBBBBBBBBCCCCCCCCCCCBBBBB@@@AABCDAA@AAABBBBBBBBBB???@@AAAAA@@@???@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??@@@@@@?>>>===><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;=:;>;9FXئrMD=>AA?>>??A@@@BBA@@>==>?>====><<=<=<=<>=?>??@?========>>>>>>>>>====<<?@ABB??>>>?@@@@@@@@@?AAAAAAAA@????@AA@@@@@@@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??@@@@@@???>====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;<<<<:;=<:?G_|F@><>@@@>A@@A@@@@AAAABBBBDCCBBCCD[^`aa`bdcbba___`cccbaa``^]]\[ZYYSPMKGD@>@@ABCDDEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@AA@@@@AAABCABBBBCCBABBDEFGHDEEEECCCDEFFFEFGDDDDDDDDCCDDEEFFEDDDDDDDEEEEEEEEDDCCCCBBCBA@@@BBFECBBBCDCBBAA@@@CBAA@@?>@A??????@@@@@@@@BBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??@@@@@@@??>====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<;;<<;<=:9;<;=?AsƊZ=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;<;<;<<=<:::==:?ACAA@A@@A@AAABBBBCEDCBBCDEBGLPRUY]_^]\Z[]\XWVTRQPOKJHHEDABCA?>?ABB@@AAAA@?A@AAAAAAA@@@@@AAA@AAAAAAAAA@@@@@@@A@A@A@A@A@A@A@AA@@AAAAA@@AA@@@EFFEFFFFHJMPSVWY`behmqsu{|}ywvljfa\YTRMLKHECAACDDDDDDCBABABAAA@@@A@@AA@@@AAA@AAAAA@A@@@@@@@@@A@A@AAAAAA@@@AAA@A@A@@@@@@@@@@@@@AAAAAAAAAAA@@@@@AAAAAAA@@@AAAAAA@@???@@@A?>==<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<;;<;<<<;:9:>>:@9X번N:MBB@@??ABBCBBA@@@?@@@??ABAAAAAAAABBDEGIKKLLLLLLLLGGGGGGGGDDCA@>>>A@?>AABCBBBABA?>@@ACDC@>A@@@BBAA?A@@>???=ABCAA?@ABBC@?>@@A???@ABGJMPSVZ]h`[aijnqstx{~{xtk`XRPPPNNMMBCCA@@@????@BBCACBCCDDEEA@ABCCA@BBBCCBBCAA@@@@??CCBBBBA@@@A@????@@@@@AAAA@@@@@A@A@AAA@@@@@@@AAA@?@??@@@?>???=<<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<<<<<<<<<<;;;IBAA@@AAB@???@ABB@AAA@@@AAAAAAAAA>>??@ABCABBBBBBB@@@@@@@@BBBAA@>?A@?>@@AA=<=??@@@DCA@BBCCBA@?AB@AABCB@??@FGHHFEFFEGLORTXYWW[]bdijquyyponlihfeXWVTSPPOTROLJIEC???==<;;@>>>??>>@?????@@A?>?ABCB>>>????>A@ABACBCA@@@AAAA@@@@@@A@A@AAA@@@@@@AAA@@@@@@??@@>>??>=;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>?>>??????@@BBB@A????@@A@ABBAAAABAAB<;<<=>?@??>>=>@B?@BAAADE>>???>ABRW_hqyּ}{ywlhea\XURPOMKHEDCBA?@AA@@DBBAAABCBAAABB@?BABCBAAA?@?@@@AA@@@@@@@@@@@@@@@AA@AAAA@@@@AA@AAA@@@@??@@??@?>=;<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:@:E[ʙN?GG?@BAACC?>?>>?@@ACEFDB@?EECB@@AC@DJQY`fknsyϲ~|zxurigeb_]ZZ\YTPLJFEEDA@=>?>@@>>@@?>@?>???>>A@A@??>>AAA@@@@@@AAAAAAAAAAAAA@@@AA@AAAA?@@@??@@??@@?><<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<8C?@ABCCA??@AA@AAAAAAAA@@?@?@AA@AAAAAA@>?>?>???@@@@@@BB@AA@?>>?==>=>ACC=>?@>?AB>????ACEZ_juͮ~vmg_ZWNMJHDCCACBAAAAA?@?@@?@ABBBBBABBB@@@AA@@@@AAAA@@@AAA@@@@@AAA@AAAA?@@@??@@??@@?><<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:@@CGKOQSKGA?@AA@AAAAAAAA>??@?@@@??@??????@@@@@@?A@@A@@@AAAA>;:<>A@?<<==>FILPV`ltw}̮}vtspljhf`\WSOIEAA@@@@ADD?AAABBBC@A@@@@@@AAAA@@A@A@@@@@@@@AA@AAAA?@@@??@@@@A@?><=;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;5A:PyN=F>?B@AABBAA@ADJQW\_`WPGBAA@?AAAAAAA@A@@AAAAAABBAAAAAAAAAAAAAA@AAAAA@?BA@?@FKKJKORVY]^dmu|β~wrje^ZSQLIFEFFHFFFECBBAAAAAAAAA@@@@@@A@@@@@@@@@AAAA@@A@@@@??@@@@AA@?<=;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<3?8Br۸dCAAAAAAAA@A@@@@>?BBAAAABB@@@AA@AAABAAAA@?=@ABEIS[kovͱ|vohda`VUSPKIFDAAA@@@@AAAAA@@@A@@@@@@@@@@AAAAAA@@@@??@@@@AA@?==;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:@7@7;`WAJA;CCABAAAA@@@HS]bb`^e]RHCBCCCCCCBBBBBA@?@A@>:@DB?ACCCCA?>>>@;=?CGLTXXclor{дqlrupkic_]YTOKFAA@?ACDCD@AA@>@AEA@@ABB?>@AA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;>@ECCC>>@A?CBB>=<<=;AJVcp}ϳxj^RLAA@??>>=;>BEDA<9DB??>@@@@AA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;><5Dc\B=E@@D=BBAAAA@@AGQ[bdcbaZPHCBBCCCCBBBBABCED@?@CCC@=@FEBIOV]fow|дɺ|umf]WSKF@=>ABBDB?@AAAA@AA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;=:9<88ENB@D?@D;BBAAAA@@BFNW_bba\VNGCBBBCCBBBBAAGC?BGE@<<@CBDJJJgmzгǻ~xk_PFDCEE@A@BCCB@@AA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;7=<8><9=~FCB@>@B=BBAAAA@@BDJQX\][VQJEBBAABBBBAAA@FCDGE@@BJYl{гveWMHDA?@??AAA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;4?>9<><>u@@><=?ABBBAAAA@@BBDJPTSROKFBAAAABBBAAAAA:>GIB?L_eyϳrhYJB>@AAAA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;6A=89;:=sӲp?@=<@>?DBBAAAA@@A@?CHKJGHFB@@AA@BBAAAA@?;=ENUa{гÿlZMEB@AA@??@@?@AAA@>?<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;:?:8<<8;cʧmCC??D??====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<88@?9:Y˅a@@@?@AAAAAAAAAAAAAAAAAA@@AA@@A@A;DC:;GJB7KlгxUCCCA?>@BCA@A@AA>@>=<;;<<<<<<<<<<<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<;;;;<<<<<<<<<<<<<<<<<<<<<<<<;<;@>=<;;<<<<<<<<<<;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===<<<<;;;;<<<<<<<<<<<<<<<<<<<<<<;<<;<<;ObkJA@@@@ABBAAAAAAAAAAAAAAA@A@AA@AAAB<=HE;EZгjIDC>=FCA?@ABA?@>=<;;<<<<<<<<<<;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<<<<;;;;;<<<<==<<<<<<<<<<<<<<<<<<<<;<<;DW`DA@@@@ABBAAAAAAAAAAAAAAA@A@A@AAA@D;=CCGdгŬjK>BDBC@?AAA@??>=<;;<<<<<<<<<<<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==<<<<;;<<<<<===<<<<<<<<<<<<<<<<<<<<;;<;=<;;<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;====<<<<<<<=====<<<<<<<<<<<<<<<<<<<<;;<;=<;;<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;>>>>>===<<====>><<<<<<<<<<<<<<<<<<<<;;<;?FIDA@@@@AAAAAAAAAAAAAAAAAA@AAAAA@@@A=?U|г±`HCB@@@ABA>?>=<;;<<<<<<<<<<<<<<<<;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;???>>>>=<<<<<===<<<<<<<<<<<<<<<<<<<<<;<;BBDAA@@@@AAAAAAAAAAAAAAAAAAA@@@AA@A@G=FoгcHC@@AAA@?@>=<;;<<<<<<<<<<<<<<<<;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@????>><<<<====<<<<<<<<<<<<<<<<<<<<<<;;C?ۚDBA@AAAA@@AAAAAAAAAAAAAAAA?AB@A@@?FL^γnOFACD==B@@?>=<<<<<<<<<<<<<<<<<=<===>?@@AADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCBAA@??>==<<<<;;;;<<=<<<<<<<<<<<<<<;ԕCCAAAAAA@@AAAAAAAAAAAAAAAA@ABAAA???W{ϴűgF?BC=>AA@?>==<<<<<<<<<<<<<<;<<<==<<<<;;;<<=<<<<<<<<<<<<<<;BA@?>>=<<<<<<<<<<<=<<;;;<=<=<<;;<<=<<<<<<<<<<;;<<;<=;ˆAB@AAAAA@@AAAAAAAAAAAAAAA@@ABCB@AERϳȾWD>B@>AAA??>=<=<<<<<<<<<=<<<;;<=EEEFFGGHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHGGFEDBA?>=<=<<<<<=<<<<<<<<<<;;<<<<<;{繁A@@AAAAA@@AAAAAAAAAAAAAAAABAADA?BJjγǣnO>BA?AAA@?>>===<<<<<<<<==<=<=?@FGFGIIJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJHHHHHHGGGGFDCA@>=>=<<<<<<<<<<<<<;;;;<<<<=DPϵ̴^@@A?@AA@??>===<<<<<<<<===>>?BCGGGGIJKKIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJGGGGGGGGHHGFDCBA>>>=<<<<<<<<<<<<;;;;<<<<==mޡo>@@AAAA@@@AAAAAAAAAAAAAAA@D@?B?>HTӺȿmC=A@A@A@@?>=>=<<<<<<<<==>?@BEFFGFGIJJJGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHGHHHHHHHHHGGFEDC??>=<<<<<<<<<<<<;;;;<<<;==f٘i>A@AAAAA@@AAAAAAAAAAAAAAA@C>=A?=IY־xD;<<<<<<<===?AEGHFFFFGHJIGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHIIIIIHHHGGFEE??>=<<<<<<<<<<<<;;;;<<<;=AAAAA@?==<<=====;>ACFGGGGGGHHHGHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGHHHHHHHIC@=;;;<<<<<<<<<<;;;;<<<<;CS~݈ZE>@@A@A@@@AAAAAAAAAAAAAAAAB@A@C;awƔXKB:F=@@@@A@?>==<<===<=>ADFGGGGGGHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGHHHHHHHID@=;;;<<<<<<<<<<;;;;<<<<;AMv܂UC?@@@@A@@@AAAAAAAAAAAAAAAA@@A@D?nľ̟hPA;C=@@@@A@?>==<<===<=?ADFGGGGGGHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGHHHHHHHIEA=;;<<<<<<<<<<<;;;;<<<<;=Fl{MA@@@@@A@@@AAAAAAAAAAAAAAAA@?@?CCxþΩzV?==<<===<>@BDFGGGGGGHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGHHHHHHHIEA><;<<<<<<<<<<<;;;;<<<<:;AfrH?A@@@@A@@@AAAAAAAAAAAAAAAABAB?BCȾ˯\>?C?@@@@A@?>==<<===A@@@@AA@@AAAAAAAAAAAAAAA@BAA?@Bžȵb>BC?@@@@A@?>==<<===<@ACEFGGGGGGHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGHHHHHHHIGC?=<<<;<<<<<<<<;;;<<<;;:A@@@A@?>==<<===<@BDEGGGGGGGHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGHHHHHHHIHD@=<<<;<<<<<<<<;;;<<<;;;>>X_@?B@@@@AA@@AAAAAAAAAAAAAAA@AADBCG¼Ǿk>D@?A@@@A@?>==<<===><<====>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHHGEA=;=><<<<<<<<;;;<<<;;;?:]ׯZE@><<====>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHHGEA><=><<<<<<<<;;<<<<;;<=8XҦWVjμX=B@A@@@AAAA?><<<==<>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHHGEB><==<<<<<<<<;;<<<<;;=<7TʝU>B@CB??AA@?AAAAAAAAAAAAAAAAACB>^}c?@@A@@@AAAA?>=<<<=<>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHHGFC?===<<<<<<<<;;<<<<;<<;8R’R?BCCB??AA@?AAAAAAAAAAAAAAA@ABC>fĽÕmC>?A@@@AAAA@?=<<<<;>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHHGFD@===<<<<<<<<;;<<<<;<<;9Q{N@BECB??AA@?AAAAAAAAAAAAAAA@B@C=mƠxG??A@@@AAAA@?=<<<<;>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHHGGEA>==<<<<<<<<;;<<<<;<<;;Or~J?@ECB??AA@?AAAAAAAAAAAAAAA@BAEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGHGGGEB>==<<<<<<<<;;<<<<;<>;;JgxH>?DCB??AA@?AAAAAAAAAAAAAAA@A;C=xľ˰PBBA@@@@AAAA@><<<;:>AEGGFGGHHHHHHHHGGGGGGGGGGGGGGGGGHHGGGGHHHHHHHHGGGGGHHHHHGGGGGGGGGGGGGGGGGGGGHGHGGHFB><=<<<<<<<<;;;<<<;<@;:F_cOC?BBB@?@@@@AAAAAAAAAAAAAAA@=@9H~ƿWC>@@@@@@AA?@@=<<;=?ACFHHHGHHHHHHHHHHHHHHHHLEAEHFGKIIHFGIGEJIJKKIHFJGEFIJKIKKNMFGIBCGHFDDFGGHHHHHGGGHHFC><<::;;;;;;;;;<<<<;==;HV_MB@CBB@?@@@@AAAAAAAAAAAAAAA@?B>9CNXIAADBB@?@@@@AAAAAAAAAAAAAAA@AFAQʣbHB?@AAAABB?@@=<<;=>@CFGHGGHHHHHHHHHHHHHHHHDHIGEEFF@DJJDBN]vĺ~gSIKHGD>8>ESFAADBBA?@@@@AAAAAAAAAAAAAAA@@IFZƾΫlKB?@AAAABB?@@=<<;=>@CEGGGFHHHHHHHHHHHHHHHHFIHDCGIIHEK[guʸ~laSLLLHGEGGEEEGHHHHHGHGHHFC?<<::;;;;;;<;;<<;<<=>9>?}MDBBCBBA?@@@@AAAAAAAAAAAAAAA@>HMgϳwSB?@AAAABB?@@=<<;=>?BEGGGFHHHHHHHHHHHHHHHHEGHHJNMIVNTtšgEBEHEHLKHGKGGHHGGHHGHHFC?<<::;;;;;;<;;<<;<<;><@>x߉HBCCBBBA?@@@@AAAAAAAAAAAAAAA@=FRwͷ]A@@AAAABB?@@=<<;==?BEFGFFHHHHHHHHHHHHHHHHFFHKKGEG\tӴy^MFDHHFGMGGHHGGHHGHHFC?<<::;;;;;;<;;<<;<<:>=B>tځCADECBBA?@@@@AAAAAAAAAAAAAAAA;DYƲͻfC@@AAAABB?@@=<<;==?BDFFFEHHHHHHHHHHHHHHHHJFFJGEQcܭzfTDCGEEHGGGGGGHHGHHFC?<<::;;;;;;<;;<<;<<;=;AACEGHHHHHHHHHHGIIGFFGHMKJLTq޴hKICGHHFHGDGKJEGHHFC?<<::;;;;;;<;;<<;<<=9A=9ot>BB>A@AA@@@@@AAAAAAAAAAAAAAAAABkǵű~BD@?BC@?A?@?=>=;<=>@CEGHHHHHHHHHHHHFEFGHIHJLW|ҝ\FF?DEDFIIFGJIFGHHFC?<<::;;;;;;<;;<<;<<>:A<8kp>BA>@@AA@@@@@AAAAAAAAAAAAAAAADDpǵɾCD@?ACA@B?@?>>=;<=>@CEFGHHHHHHHHHHFDDGHGHEDPvНiA;@;9f޽k@B??@@AA@@@A@AAAAAAAAAAAAAAAAFIxǵǑFEA?ABAAC@@@>>=<<<>@BDFGHHHHHHHHHHEDFIHFGIIi؟lB1=ABIGGEHIHFFHIGHHFC?<<::;;;;;;<;;<<;<<=;?<;c۴hDC?@A@AA@@@AAAAAAAAAAAAAAAAAAGNǵ˗NFB?ABAAC@A@>?><=<=?BDFGGHHHHHHHHHEEIKGEGNk֞hK=7A=>JFDFFGGGFHJGHHFC?<<::;;;;;;<;;<<;<<<;><=<=?BDEFGHHHHHHHHHEFJKFHM[ӝ`;<:9:6Gc]VLGEFGGHJGHHFC?<<::;;;;;;<;;<<;<<;<==B]ӥ`FA;A?AAA@@@AAAAAAAAAAAAAAAAAAHcǵ˥nJDAAA@@BAAA??>==<=?ADEFGHHHHHHHHHFEHGENZٟoJ9@78=Hw~VLEFHHGHGHHFC?<<::;;;;;;<;;<<;<<;=<@A@@@@AAAAAAAAAAAAAAAAAAJjǵ̫zJDAAA?@BA@@?>>=>:=?ACEFGHHHHHHHHHDCECESdܠ^FC<:?DPjǠ^QFFHHFFGHHFC?<<:;;;;;;;;;;<<<<<<>;;BTΖf>HB>ABBABHyıǯZADCACEEFGGGGHHHHGEN@IAZաfJB?<=ABtoQ;IBJFHGGGGEA=<====<<<<<<<<====@<=B:\ȏb>ACA@BNñʵ^CDC;BB@@AA@??<<<=>@CEEFHHGGHHHHHEIFNKxٖkC79:<@KnܕkHMCHDGHHGGE@=<====<<<<<;<<====>;;A9W|\;D@>CA@@A@@@A@A@@@@@@B??BB@@CR;cEDD@CEEFHHGGHHHHJFDLQSͣhL;CQnʼnSPDIDHHHGGEA>=====<<<<<;<<=====::?8Qs|X@CEEFHHGGHHHHKHDONU٠kI=;@>;PmWLELHKHHGHFA>=====<<<<<;<<====<;9?9KiuW>B?AAA@@A@@@A@A@@@@@@BAAAA?AFWƶoHCB>B@@@AA@??<<<=>@CEEFHHGGHHHHHIEMQaҠkJ;9;=====<<<<<;<<====<=9@;GapUAB?B?AA@A@A@@@A@@@@@@A@AAA>BI[ȾȻsJBB>BAB@@AA??<<<=>@CEEFHHGGHHHHDIFK`ؠhID=<:?PxwZBKFFGHGHFC@?====<<<<<;<<====<>9A>CZiQAA?D?AA@@AA@@@@@@@@@@@@AA@?DL`ȼxKAA?CBC@@AA??<<<=>@CEEFHHGGHHHHEGHErϠoK?A:8:MxՙoDFEHGHGHGC@@====<<<<<;<<====:>8@?@TdN@?>E?@AA@AA@@@@@@@@@@@?AB@?FLeǼyJ?A?DCDA@@A?><<<=>@CEEFHHGGHHHHHIHB֞iKC>7<PVMFA>@?@AA@AA@@@@@@@@@@C=C?=BFUsȾQBG>=F?A@@A?><<<==>ADEFGGFGHHHHIMI@ˣqM;69>6Kv~CKIEIHGHGEB@?>=<<;<<<;<<====<;;?<;FQIC@>@@@AA@A@@@A@@@@@@@C=B?=CEXy»ʉZFH=>E?AA@A?>=<<==>ADEFGGFGHHHHGLHBײsB=@36GOtՃEJHFIHGHGEBA?>=<<;<<<;<<====<;;?<A@@AAA@@@@AAAA@@@@C>A??BD\ƼʔfKG>>D@AA@A?>==<==>ADEFGGFGHHHHGIHEХeI9CHA:>yGHFGJIGHHEBA?>=<<;<<<;<<====<<;><B@AAAA@@AAAAAA@@@@B@A?CABcʝoNE>?CAAA@A?>==<==>ADEFGGFGHHHGEIGHܠkEEC=5:ToJFFHJIGHHECA?>=<<;<<<;<<====;<;><=AFCAA?B?AAAA@@AAAAAAAA@@BA@?E@AkǾʢxO@?ABBAA@@@>==<==>ADEFGGFGHHHGDHFIӠfCC:;B9=<<;<<<;<<====;<;==>?DCBB@B?AAAA@@AAAAAAAA@@AA@?H?@sɨQ>@AACAAA@@?==<==>ADEFGGFGHHHGDHFHܣdB:957BQsHFHHKJHIIFCB?>=<<;<<<;<<====;<;==>=ޑAABB@B?AAAA@@AAAA@@AA@@AB@?J>?xĺɮV?AB?BAAA@@?==<==>ADEFGGFGHHHGFJGGեoH:=<<;<<<;<<====;=:<=?;֎>?AB@B@@AAAA@AAAAA@AA@A@D??I>@zʲX@BC=AAAAA@?==<==>ADEFGGFGHHHGFJGEڦoK>:6;8GuFKKGKJIHIGDB?>=<<;<<<;<<====;>:<>?9΃F:E???C@@BBCABAAA@@@A@@BA@@>GFʶWC@C=A@A??>?>><===?ADGIIHHIIIHFGHEӣjG=;<;<;:;;=<<;;<<<<<=8@:=AûE;D??AB?ABBBABAAAA@@A@@BA@@?DFźʽ[B@C=@@A?@>?>=<===?ADGIIHHIIIIHFHE՟zR><9:=RvzMEIIHHHHIIFC?><;:;;=;<;;<<<<<=:?:>AsD?>=<===?ADGIIHHIIIIIFFF͟eO?=>9=IytJEJHHHHHIIFC?><;:;;=;<;;<<<<;=:>:>@p|C=D?@AA?ABBBABA@A@@A@@@B>AE@CIżɯcE>D>@@A?@>?>=<===?ADGIIHHIIIIHFFHٞhE<<;:;;=;<;;<<<<;>;>:>>lݸxC>E?@B@?ABBBABA@@@@A@@@A?ADAGPžȱgG>C>A@A?@>?==<===?ADGIIHHIIIIFFFHuўiG=:;<9IzٛcGGJEHHHHIIFC?><;:;;=;<;;<<<<;>;>:?=gׯtB?E?AB@?ABBBABB@@@@@@@A@AAAAPZǾŶpN>B>B@A??>?==<===?ADGIIHHIIIIFHGGc֞eGB:659HozYFHIFGHHHIIFC?><;:;;=;<;;<<<<:?;=:?;cӦqA?E>AC??ABBBABB@@@@@@@A?CB>CXdſĺyU@A=BA@@?>>==<===?ADGIIHHIIIIFHIGSoϠkH=A>:<;:;;=;<;;<<<<:?<=:@:`ѢpA@E>AC??ABBBABA@A@AA@@A?EC=B_ižž~YBA==<===?ADGIIHHIIIIGJIDH_՝hH@<6BAJoGGEHHJGHHHIIFC?><;:;;=;<;;<<<<:@<=:@:^ӗg@?C@A???ABBBABAAA@AA@@@?CA>DYƻ`BE<@A@@??>==<=<<=@CGHHGGHHHHKDGID]ʤiEC3C3>JtĈQBIKGHIIIIJIFC@><;;;;<;<;;<<<<=><<>:AU}Ӕg@?C@A???ABBBABAA@AA@@@@?B@>E]ûǼdCD==<=<<=@CGHHGGHHHHJGGGEO}۞tI<@5;;OwtIDLKHKIIIIJIFC@><;;;;<;<;;<<<<=><<>:@S}юe@@C?A@??ABBBABA@AAA@@@@>BA?GdƽkDC=?AA@???==<=<<=@CGHHGGHHHHGIGHID]ѠkE3<=;7Or|^DFLJHKIIIIJIFC@><;;;;<;<;;<<<<<><;>;@Pz͇b?@B>A@??ABBBABA@AAA@A@@?AA>DiƿrDB??AA@@>?==<=<<=@CGHHGGHHHGEJFIPAJvۣcL9;D9GIwݝ`QGIKFDGIIIIJIFC@><;;;;<;<;;<<<<;?<;>BA??ABBBABA@AA@AA@@B@B?Cmžƿ{DB@?AA?@>?==<=<<=@CGHHGGHHHGFKDGPABe؞n@<<;;;;<;<;;<<<<:?<:><=Iqz\>BB=BB??ABBBABA@AA@AA@AC@C>AsľýDAA>AA?@>?==<=<<=@CGHHGGHHHGHJEELCBT_ɥdP@1;A;Au׉VMLJIHHIJJIIIJIFC@><;;;;<;<;;<<<<:?=:>=BBBzD@B>AA?@>?==<=<<=@CGHHGGHHHGGIGEFFGFNsoCGC4;@Q{ΘdHKKHGHJKJJIIIJIFC@><;;;;<;<;;<<<<9?=9>=>>=<=<<=@CGHGGHHGHGFEHHELMCHTgDJ9G9>@GobRGMKHEGIHEIIIIKIFC?><;<<;<;<;;<<<<9?=9=>@CA>>@?@@A@BB@@AA@@AA@<<<<=<>=>@DFGGGGGGHHGGHHGGHHKLJIGDDBEMqݩrRKNGGHHHGGFHGGIHHGEC@=;;<;;:::;<<<=<<=@@ViMD>?CA??@@AA@@AB@@@@A@@@A=AFKOF>DA@?>=<=<=<>=>@CFGGGGGGGGHHHHHHHHJIIHGGFFEsک{ZHFHGGHHHGGGHGHIHHGFC@=;;<;;:::;<<<=<<>@@VfJC>?BB@@@@AA@@AB@@@@A@@@A?BGR¿UH?CA@?>=<=<=<>==?CFGGGGGGGGHHHHHHHHGGGHHHHI|ڨpQLJGFGFHHHGGFHGHIHHGFC@=;;<;;:::;<<<<>>=<<>??UaFB>?BCB@@@AA@@AA@A@@AA@@@BAHY¾YJ@EA@?>=<=<=<===?BEGGGGGGGGHHHHHHHHGGGHHHII¡wNGNOJGFFHHHGGFHGHIHHGFC@=;;<;;:::;<<<<>>=<==>>R]CA?>ACB@@@AA@@AAA@@@AA@@>B?G`¾[KBGA@?>=<=<=<===>ADGGGGGGGGHHHHHHHHHHHHHHHG㳁hTGKOJEFFFHHHGGFHGHIHHGFC@=;;<;;:::;<<<<>======@CB@@@AA@@AAA@A@A@@@=C=Ge`NBG@@?>=<=<===<<>ACGGGGGGGGHHHHHHHHIIHHHGGG]|ٹ~aNKLNLECIFFGHHGGFHGHIHHGFC@=;;<;;:::;<<<<=====<<;NU@BA>@CB?@@AA@@AAA@@@A@@A=E=Hk¾fS@C@@?>=<=<===<<=@CGGGGGGGGHHHHHHHHHHHHHGGGIJWzТjOJEIKONKGGJGFGHHGGFHGHIHHGFC@=;;<;;:::;<<<<=====<;:LS?BB>?BB>@@AA@@AAAA@@A@@A>F>JnԽkV??@@?>=<=<===<<=@BGGGGGGGGHHHHHHHHGGGHHHHHFCL_p|ǾgPLL@DHIHJLJDGGGHHHFFHGHIHHGFC@=;;<;;:::;<<<<=====<;9KN@@@@AAAAAAAA@@@@A@@@A@A@E=<<<==>>==?@CEGGFFGHHHHHHHHHHHHHHHHHDEGIKQX\{ż~ZSJFFHIIHGGHHHHHHHGHHGGGEHJHGHIGC@><;;;:9:;;=<<<========<<<<=>>==?@CEGGFFGHHHHHHHHHHHHHHHHHJIIGFILPLUbjmnpruutpi]TOTNIGGIKKGGHHHHHHHHGGHGGGDHJHGHIGC@><;;;:9:;;=<<<========<<=<=>>==?@CEGGFFGHHHHHHHHHHHHHHHHHMMJFDCEFFILLKHGFJJJIHEDDKJHHHJJJHHHHHHHHHGGGHGGGDHJHGHIGC@><;;;:9:;;=<<<=======?>=<<=<=>>==?@CEGGFFGHHHHHHHHHHHHHHHHHLMJHDDGIKIIIIJIHEEEEEFGHEFGHJIIHHGHGHGHGGGGGHGGGDHJHGHIGC@><;;;:9:;;=<<<=======?>=<<=<=>>==?@CEGGFFGHHHHHHHHHHHHHHHHHJJJGFFHKHECDFHHHHGGGFFEEEGIKJIGFGHGHGHGHHHGGGFFFDHJHGHIGC@><;;;:9:;;=<<<=======?>=<<=<=>>==?@CEGGFFGHHHHHHHHHHHHHHHHHJJJHFFFHLJGGHIIIIIJJJHGFFHKJHFEDHHHHHHHHHHGGGFFFDHJHGHIGC@><;;;:9:;;=<<<=======FCW˴hBA@>?>=<<=<=>>==?@CEGGFFGHHHHHHHHHHHHHHHHHIIJHFEEGHHGFEEEEEFFFGEEDGJKJGFFGHHHHHHHHHHHHGFGGDHJHGHIGC@><;;;:9:;;=<<<=======ECW˴ĿiBB@>?><<==<=>><=?@CEGGFFHIHHHHHHHHHHHHHHHHIIIIGFGHEFHHGFGHJJJJKJJKGJKIGFGJGHHGGGGGGHHHGGGGDHJHGHIGC@><;;;:9:;;=<<<========FӟM?@AAAABB@@@@@@A@@@AAA@BB?BBU˳¿q@D>==;<;<=<>>?BDGIHFGGGGGGGGGGGGGGGGGHHHHHGHHGGHHGHHHHIIIIIIIHIHHHHHIIHHHHHHHHHHGHHFGGGJLIHIIHEB?===<<;;;<<<<<========KМL?@AAAABB@@@@AAA@@?@AAAAB=BCX̳¿q@E=>=;<<<;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGGHHHGHGGGGHGHHHHIIIIIIIIIIIIIIIHGHHHHGGGHHGHHFGGGJLIHIIHEB?===<<;;;<<<<<========J̘L?@AAAABB@@@@AAA@@@@AAAAB=@C\̳tAC===;<<<;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGHHHGHHHGHHGGHHGGGIHIIIIIIIIIIIIIIHGGHHHHHHHGHHFGGGJLIHIIHEB?===<<;;;<<<<<========IƔL?@AAAABB@@@@AAA@@@@AAAAB=AD_̳xBB<=<;;<==<=>?BDGHHGGHGGGGGGGGGGGGGGGHHHHGGHHHHHHHGHHGIHIIIIIIIIIIIIIIHHHGGHHHHGHHHFGFFJLIHIIHEB?===<<;;;<<<<<========HK?@AAAABB@@@@AAA@@@@AAAAA>@Db̳|D?=<;;;<==<=>?BDGHHGGHGGGGGGGGGGGGGGGHHHHHHGGHHHHGGHHHIHHHHHHHHHHHHHHIHHHHHGGGGGHHHGFFFJLIHIIHEB?===<<;;;<<<<<========H컋K?@AAAABB@@@@AAAA@@@AA@BA@@Df̳¿E><;;;;===?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGHHHHHHHGGGGGGHHHHIIIIIIHIHHHHHHHHHHHHHHHGGGHHHGFFFJLIHIIHEB?===<<;;;<<<<<========G{궇K?@AAAABB@@@@AAAA@@@AA@BBC@Cf̳F>;<;;;=>=?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGHHHHHGGGGGGHHHHHHHIIIIIIIIIIIIIIIGGHHHHHHHHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========Fx괅J?@AAAABB@@@@AAAA?@AAA@BAD?Dh̳¾E=;<<;;=>=?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHIIIIIHHHHHIIIIIIHHHHHHHHHHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========FuzJ?@AAAABBAA@@AA@@@@AAA@A@A@Pn̳Q?;;;;;<<;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGMIEEGIIHMKHFFHJLQUY[XQJEEKJEELMGGJLJHGFEHHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========CqyJ?@AAAABBAA@@AA@@@@AAAA@@BBTs̳TA;;;;;<<;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGADHKKKLODHMQUZ^adgkljd\XYVSOJFEFOKECEJLLHHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========CpxJ?@AAAABBAA@@AA@@@@AAAA@@CAXy̳ZD;;;<<<<;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGQMHJNMKETbvĽvdPKODFKNMGC@HHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========BnߩuJ?@AAAABBAA@@AA@@@@AAAA@@D@]̳^F;::<<;;;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGIGDDJVdmѼqZOKIJGDBEMHHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========AkޥrI?@AAAABBAA@@AA@@@@AAAA@@D?b̳aI:::<<;;;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGBILLPeȢ_TIHLKLLHHHHHGGGFJLIHIIHEB?===<<;;;<<<<<========@hޣoG?@AAAABBAA@@AA@@@@AAAA@AB>g̳cJ:;:<<;;;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGGIJPbٿ|YNNMFAGHHHHGGGFJLIHIIHEB?===<<;;;<<<<<=======k̳fK:;:<<;;;?=>=<=>?BDGHHGGHGGGGGGGGGGGGGGGHINf쾇`MEHPGHHHHGGGFJLIHIIHEB?===<<;;;<<<<<=======<>dޠlD?@AAAABBAA@@AA@@A@@@@AAAF>o̳½hL:;;;<<<;>>>=<=>?CDGHHFGGHHHHGGGGGGGGGGHHXwȨ~WHKGHGGGGGGGJLIHIIHFB>===<<;;;<<<<<=======<><<<<;<=>BCFGFGFGHGHHHHIHHFFIKMPRz[JGBHOHAFIGJKIHJIGGD?>==<;<<<<<<<<<<<<===>><<<<;<=>BDFGGGGGGHHHHIHHIFHJGJ`zٰWPFEHHJMHGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\fGCBAABCBAAA@@AA@@@A@AA@AABDv˲¿ºvO==<<=>>><<<<;<=>BDFGGGGGHHHGHHIIHDGKJZWJDEDKLDGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\fGCBAABCBAAA@@AA@@@A@AA@AAACx˲¿yQ==<<===><<<<;<=>BDFGGGGGGHGHHHHHIFFL[~ݸnVIJHFHFGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\eFCBAABCBAAA@@AA@@@A@AA@AA@Cz˲}S>=<<====<<<<;<=>BDFGGGGHHHHHHHHILKHPsoLKKGGHGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\dECBAABCBAAA@@AA@@@@@A@A@@?D|˲þT>>=<====<<<<;<=>BDFGGGGHHHHHHHIHJJJWܒVGIHFEGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\dECBAABCBAAA@@AA@@A@@A@A@@?D}˲ºU=>====;<<<<<;<=>BDFGGGGHHHHHHHIHEFHV}XGFHBGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\cDCBAABCBAAA@@AA@@A@A@@A@@=D~˲V=>>===;<<<<<;<=>BDFGGGGHHHHHHHIIEFEMeޭvKEKHGJKIHJIFGD@>==<;<<<<<<<<<<<<====@\aEAAAAAAAAAA@@AA@@A@A@@A@@ABz˲¼Y<=<<<<<=<<<<<<==BDFGGGGHKEFHDHKEJEBEGPbtޕQIGHGIJJHHHIGEB?==<<====<<<<<<<<====>T`EAAAAAAAAAA@@AA@@AAA@AA@@AD{˲¼Y<<<<<<<=;<<<<<==BDFGGGGGJGFGEHJFCBCB>T^EAAAAAAAAAA@@AA@@AAA@AA@@@D|˲Y<<<<;<<=;<<<<<==BDFGGGGGHGGHHHHHKFA@?==>CDQi]IAGIJJHHHIGEB?==<<====<<<<<<<<====>Tߌ\DAAAAAAAAAA@@AA@@AA@@AAAA@E~˲ľZ<<<<;<<=;<<<<<==BDFGGGGHFHFGJGGNMC737>?<>:9DZvߢkG>GIJJHHHIGEB?==<<====<<<<<<<<====>TފZDAAAAAAAAAA@@AA@@AA@@AAAA?F˲Z<<<<;<<=;<<<<<==BDFGGGGHEJGFLGFTaaZK@?A?A>=>>AMZ|MEGIJJHHHIGEB?==<<====<<<<<<<<====>TއXDAAAAAAAAAA@@AA@@AA@@AAAA?F˲Z<<<<;<<=;<<<<<==BDFGGGGHCKGFMGG^jODA<<>A<7T݅VDAAAAAAAAAA@@AA@@AA@@AAAA>G˲[<<<<;<<=;<<<<<==BDFGGGGHCNFENGJe̦n\LA>@A?BIEAFVtaNGIJJHHHIGEB?==<<====<<<<<<<<====>T݄UCAAAAAAAAAA@@AA@@AA@@@A@A?G˲[<<<<<<==;<<<<<==BDFGGGGHCOFCOHLj׹lT@;<:78;=CHGKbzpWGIJJHHHIGEB?==<<====<<<<<<<<====>TނSBAAAAAAAAAA@@AA@?@@@@@AA@BE˲]==<;<<=<;<<<<<==BDFGGGGHJEFFMHMvݳoS=9A>=:9=DJLsSGJKIHJIFGGD?=?><<<<<<<<<<<<<====;M|ށSBAAAAAAAAAA@@AA@?@@@AAA@@AE˲¿]==<;<<=<<<<<<<==BDFGGGGHHDGELHP{ȡyVA7@BA;67@K7AUtWFJKIHJIFGGD?=?><<<<<<<<<<<<<===<;M|ށSBAAAAAAAAAA@@AA@?@@@AAA@@@E˲þ^==<;<<=<<<<<<<==BDFGGGGHGEHFJGTǮwVH=?@>=H\y̝ZFJKIHJIFGGD?=?><<<<<<<<<<<<<===<;M|ށSBAAAAAAAAAA@@AA@?@@@AAA@@?F˲Ŀ`==<;<<=<<<<<<<==BDFGGGGHFEIFIGV׿uZNJB:8=>?<<<<<<<<<<<<<<===<;M|ށSBAAAAAAAAAA@@AA@?@@A@AA@@>G˲¼a==<;<<=<<<<<<<==BDFGGGGHFFIFHGXţ_GAE:=CD?;@IQf٨YGJKIHJIFGGD?=?><<<<<<<<<<<<<===<;M|ށSBAAAAAAAAAA@@AA@?@@A@A@@A>G˲b==<;<<=<<<<<<<==BDFGGGGHGFIEHHXȪiXM@;=@<86:=I^{اXGJKIHJIFGGD?=?><<<<<<<<<<<<<===<;M|ށSBAAAAAAAAAA@@AA@?@AA@A@@A>H˲c==<;<<=<<<<<<<==BDFGGGGHJFHEHIWܱ\H9;AB@A@:9@IWo֥[GJKIHJIFGGD?=?><<<<<<<<<<<<<===<;M|ށSBAAAAAAAAAA@@AA@?@AA@A@@A=H˲d==<;<<=<<<<<<<==BDFGGGGHKHGDHIW̴|eP?8;B;;?>;J_y֦]GJKIHJIFGGD?=?><<<<<<<<<<<<<===<;M|܀PE@@AAAABBAA@@AA@?@AA@A@@A>I˲b?=<;=>><<<<<<<==BDFGGGGHJGJFFL[伒qYJE>;@S|њ\DHKIGIIGGGD?=?><<<<<<<<<<<<<===><<<<<<<==BDFGGGGGIEKFFKX{ƧlRB2/6<@E=AHP\p컈WDHKIGIIGGGD?=?><<<<<<<<<<<<<===><<<<<<<==BDFGGGGGGELFGJSrëu_PG@=<8>B>9CdثXCHKIGIIGGGD?=?><<<<<<<<<<<<<===><<<<<<<==BDFGGGGGFELFGIMiվpO@?E?@AA>>EOWjuQCHKIGIIGGGD?=?><<<<<<<<<<<<<===<>EvxID@@AAAABBAA@@AA@@@A@@@AA@=L˲c?=<;=>><<<<<<<==BDFGGGGGFELFGHI`ӵ|fYD=:=A=74>CJiyn^FDHKIGIIGGGD?=?><<<<<<<<<<<<<===<><<<<<<<==BDFGGGGGFFKEHHGZҡ|YM@<=>@@<@9?EGOIDHKIGIIGGGD?=?><<<<<<<<<<<<<===<;BssED@@AAAABBAA@@AA@@@A@@@AA@=N˲d?=<;=>><<<<<<<==BDFGGGGGGFKDHIEWjĪoWG>:9;C989@NKCHKIGIIGGGD?=?><<<<<<<<<<<<<===<9AsrED@@@@AABBAA@@@A?@@AAA@A@A>N˲d?=<;=>><<<<<<<==BDFGGGGGGGKCHJFUSԨq_NB9E>@FJO?CHKIGIIGGGD?=?><<<<<<<<<<<<<====9ArsC@@AAAAAAAAAAAA@A@@@@A@AA@?C˲b?=<;=>><<<<<<<==BDFGGGGHGGHHHHGHM~`A:=ABIKBFIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@@@@AAA@@@C˲b?=<;=>><<<<<<<==BDFGGGGHGGHHHHGHImܽkXLB@C@GIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@@@@AAA@@@B˲b?=<;=>><<<<<<<==BDFGGGGHGGHHHHGHMUקy^LDIKGIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@@@@AAA@@AB˲b?=<;=>><<<<<<<==BDFGGGGHGGHHHHGHRMhtWGAEIGIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@@@@AAA@@BA˲b?=<;=>><<<<<<<==BDFGGGGHGGHHHHGHHITxeOHDFFGIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@A@@AA@@@DA˲b?=<;=>><<<<<<<==BDFGGGGHGGHHGHGH@HJY͑^QNKIFFIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@A@@AA@@@DB˲b?=<;=>><<<<<<<==BDFGGGGHGGHHGGGGFEFLfԑiLGFDB?FIIGFHHFGGD?=?><====<<<<<<<<====;>osCA@AAAAAAAAAAAAAA@AAAAA@@ADB˲b?=<;=>><<<<<<<==BDFGGGGHGGHHGGGGMA?GOhԦkTHJHGJHFIIGFHHFGGD?=?><====<<<<<<<<====;>ovGD@AAAAAAAAAAAAAA@AAA@A@@A>G˲b?=<;=>><<<<<<<==BDFGGGGHGGGGGGHGKLMICOzҟyMLKJIHHGGHHHGGGGGGD?=?><====<<<<<<<<====G˲`?=<;=>><<<<<<<==BDFGGGGHGGGGGGHHCDGHCG\tsFNLKIIHGGHHHHGGGGGGD?=?><====<<<<<<<<=====EvvGD@AAAAAAAAAAAAAA@AAA@A@@A>E˲^?=<;=>><<<<<<<==BDFGGGGHHHGGGGHHFCFJIFGNqǛqUHMLKHHHFFHHHHGGGGGGD?=?><====<<<<<<<<=====EwvGD@AAAAAAAAAAAAAA@AAA@A@@A?E˲]?=<;=>><<<<<<<==BDFGGGGHHHGGGGHHLGEJKHFHOcmOGJLKIIHGFFHHHHGGGGGGD?=?><====<<<<<<<<====>FwvGD@AAAAAAAAAAAAAA@AAA@A@A@@E˲]?=<;=>><<<<<<<==BDFGGGGHHHGGGGHHIFEFHFGHMF\֪wZSOKFJJIHFFFFHHHHGGGGGGD?=?><====<<<<<<<<====>FxvGD@AAAAAAAAAAAAAA@AAA@A@A@AD˲û^?=<;=>><<<<<<<==BDFGGGGHHHGGGGHHDEHIHGFFLDIYlưvO@EKLNJIHGFFGGHHHHGGGGGGD?=?><====<<<<<<<<====?GyvGD@AAAAAAAAAAAAAA@AAA@A@A@BE˲º]?=<;=>><<<<<<<==BDFGGGGHHHGGGGGGFHJKJIHGFHJNRUgԪ~bRPRUQEDHHGFFEFFGHHHHGGGGGGD?=?><====<<<<<<<<====?HyvGD@AAAAAAAAAAAAAAA@AA@@A@@CD˲º\?=<;<>=<<<<<<<==BDFGGGGHHHGGGGHHIIFFGIJIGJFFLDG_jʻr[RJFEHFBFNGGFEEFFGHHHHGGGHGGC>=??=====<<<<<<<<===<@Hy|KC@AAAAAAAAAAAAAAAAAA@@@A@>J˲Y;<<;<===<<<<<<==BDFGGGGHGGGGGGGGGGGGGGGGGGHHHGHHJMR_rzaMHIKJHIHHHGGGHHHIHHGFHGGGHGGGHGB<<>>;<<<<<<<<<<<<===J˲Y;<<;<<==<<<<<<==BDFGGGGHGGGGGGGGGGGGGGGGHHHHHHHHIJKOWajorsttrlgd\UMIJMLIHHHHHGGGHHHIHHGFGGGGHGGGHGB<<>>;<<<<<<<<<<<<===H˲Y;<<;<<==<<<<<<==BDFGGGGHGGGGGGGGGGGGGGGGHHHHHHHHEEEFGHHHIJJHGEDBCEGKLKIFGGGHHHHHHHHIHHGFGGGGHGGGHGB<<>>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===<>WۆWA@AAAAAAAAAAAAAAA@A@@A@@@@Fx˲W<;<<===<<<<<<<==BDFGGGGHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHGHGHHHHGGGGGGGGGGHHHHHHHHHHHHHGGGHGFFHHGFHGB<<>>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===>;<<<<<<<<<<<<===Ik̳wO?<<:<<<;<<<<<=>>CEHIHFGGGGGGGGGGGGGGGGGGGGHHGGHGGGGGGHHGGHHHGGHGGGGHGGHGHHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===<<:;<;;=<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGGGHHHGGGGHGGGHHHHHGHGGGHGHHHHHGHGGHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===<@^bHCB@@AAA@AAAAAAAAAA@@@AA@=Ed̳nM?=;:;<;;=<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGGHHHGHHHHHHHHGGGHHHHGGHGHGHHGHHHHHHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===>CEHIHGGHGGGGGGGGGGGGGGGHHHGHGGHGHHHHHGHGHHHGHHGHHGHGHHGHHHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===>CEHIHGGHGGGGGGGGGGGGGGGHHHHGGGHHHHHHHGGGHHHHHHHHHHHHGGHGHHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===<@cbHCB@@AAA@AAAAAAAAAA@A@@A@B@T{̳VE?=;;;<;;=<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGHGHHGHHHHHHGGGGHGGHGHGHHGHGGHHHGGHHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<====;;;<;;=<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGHGHHHGGHHHHHHHGGHGHGHGHHGGGHGHHHHGHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===<<;;<<;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGGGGHHGGGGHGHGHHGHHHGGGGHHGGGHHHGHHHHHHHHHHHHHHHHHHHGHJKIHHD?<===<<<<<<<<<<<<<===JmɰG><;;;<==;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGGIHHFGGGFJKJHEFKRHJIIHGGHGHHGFHIIHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======JkʱG>=<;;<==;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGHHGHHHIIGEHKNU[bhpmjc\RIDGFFFFFGEHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======Hh˲F><<;;<==;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGGFFGHJIHIDIR]jt{}~ypcVLHGFGGGFFGHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======<;:;<==;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGGGFHJIGGHITbp{~ui`LHEGJIHGHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======E`ʹwA><;:;=>=;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGHHGHIGCCGOauysUJDFJHHIHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======<;:;=>=;<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGHIGGIGBEKWlaQFEGGFIHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======?<;:<=>=<<<<<<=>>CEHIHGGHGGGGGGGGGGGGGGGHIGFJIDHRewt_LHIFFJHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======@=;;;<>=<<<<<<=>>CEHIHFGGGGGGGGGGGGGGGGGHKFGKIFLWriRLKGHJHHHHHHHHHHHHHHHHHHGHJKIHGD><<<<<<<<<<<<<=======;=<======<<=<;;=<=<====<<=<;<<<<====<<=<;<<<<<<<<<<<<<===JӞNAAAAAAAAAAAAAAA@@@@@?@@@9<=====<<<==<;=ADHHFFGGHHHHHHHHHHHHHHHHHHHHHHHHGGMZl{tbROKHGHGGGHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===K֡OAAAAAAAAAAAAAAA@?@@?@?@@@BAAOh}}pnokgfjkjkjkjkijijijijijijijijijijijijjkjkjkjkjkjkjkjkjjijiiijkklkljjjopppnnnnoppppoooppppppppppppppppppppppppooooooooqqqqqqqqqqqqqqqppoopnic`]\]\]\\]aabababacbcbcbcbcbbbbbbbbbbbbbbcbbaaaaabdbccablwiG=:======<<<==<;=ADHHFFGGHHHHHHHHHHHHHHHHHHGHGGGGHHHMVcr~|wn`SOLJHGGHHHHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===KۥQAAAAAAAAAAAAAAA@>>>>>???ABB>=?=;======>>=======================================>==<<==?>A@A@@@@A@A@A@A@A@A@A@A@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@A@@@@E>=?@=;;==<=<=<=>?>?>?>?>?>?>?>?>?>?>?>??@?@?@?@>?>?>?>?????>>>?>?>>==<<>=>==<<<===<<<<<==<=<=<=<=<=<=<==<=<=<=<<;<;<;<;>?>?>?>?>?>?>?>?:<>>=<<<999:99:::99999999999999:;:;:;:;::9:9:9:9::;;:::;8:;=?@QkeE=;======<<<==<;=ADHHFFGGHHHHHHHHHHHHHHHHHHHGGHHGGGJHJS^iprzxtlbWOGMIHFFHGHGHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===LߩRAAAAAAAAAAAAAAA@========:=@?>>;9====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======<===>=>??>=>>>>=>>===============>>?>?>?>?>?>?>?>?>?>?>?>?>?>?>?>>>>=====::<>@>=?<<<<<<<<========================>>>>>>>>==========>=>>=>=>>>=<<;<<<;;::::::9999999999999999999999999999999999999<<<<<<<<<<<<<<<<9;==<<@A;;;<;<;;9999999988888888::::::::99999999;;:;:;:::<89:9Jl`B?<<<====<<<==<;=ADHHFFGGHHHHHHHHHHHHHHHHHHHGHHHHGHHIHKMSWZ_^\XQKGFJHGEFHHGGHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===NSAAAAAAAAAAAAAAA@>>====>>==;;>>>===>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?@@@?:;:;;:;:;;:;:;:;:;:;:;:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<9:9:::99:<==;;:=========<<<<<<<<<<<<<<<<<<<<<<<<================<<;<;;<;>=>>=<=====>====;<;;<;<<<<;<;<;<;<;<;<;;;;;;;;;;<<<<<<<<>?>?>?>?>?>?>?>>??>===>????>?>>?????????>>>>>>>>>?>?>?>?>?>?>?>?@?@?@@@@?A<=@:Jt\??=<<<===<<<==<;=ADHHFFGGHHHHHHHHHHHHHHHHHHGGHHHHGHFHHIGFIKHGIJIFHKJHGFGIHGGHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===OTAAAAAAAAAAAAAAA@=<<<====@><978:=<;:::999::::::::::::::::::::::::::::::::::::::::89:::899=<<==<<=================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>=<=<<===<;;;;889:<;;;;;;;::::::::::::::::::::::::::::::::;;;;;;;;::;:;;:;;<<==<=<=====>=?<=<<=<==================================>>>>>>>>>>>>>>>=?>>===<;@?@?@?@@;;;;;;;;;;;;;;;;:;:;:;:;;<;<;<;<<;<<<<;<:<68@8GvW<==<<<===<<<==<;=ADHHFFGGHHHHHHHHHHHHHHHHHHHHHGHGGGDGGHFFGHDBEIIGHJIGFHIIGFGHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===OTAAAAAAAAAAAAAAA@====<=<<::=??=::<;::9998:::::::::::::::::::::::::::::;;;<<<<<;<;<<<<;;::<<==<==<================;;;;;;;<;<<<<<<<<<<<<<<<<<<<<<<<=<<=====<=?=<=>=;;:::::::;;;;;::;;;;;;:::::::;;;;;::::::;<<<<<;;;;;:::;:99;:;;;<;:<;;<<<;;;;;;;;<<<<<<<<<<<<<;;;;<;;;;;;;;;;;;::<<<<<<<<<<<<<<<<;99<=><:<===<<==@@?@?@?@@A@A@A@A@@@@??@@AAAAAAAA?>>??????C:ADGGFFFGGGGHHHHHHHHHHHHHHHHHHGHGGGGFFGHIIFKGGJKFDGJIHGIHGGHHHHHHHHHHHHHHHHHGFFHIHGE@;<=;;><<<<<<<<<<<<<===PSDA@BA??BAAABCB@==<;;;;;;<;<;;<;<================================;<<====>>6::;;::::=9:=;7:A;;<<86899:===;;:<<<<<<<<<<<<<<<<<<;<<<<<<<<<<<<<=7;@=;=:><===;;=@?;9<=<:========?;8===:=:?>;;>@==<<=<=<==<98;>=;==97;AA>E<;?>88=<===<==<;C=58<=8B================7=@;89;;??=:<>><>???????A@=<>@@>A;9<=:::<;><@BDHHGFGFGHGGGGGGGGGGGGGGGGHHGHHHHGHHGGGGGGHHGGHHHHHGHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<=>:;@<;BYUDA@AA??BAAABCB@=<=<;;;;<<<<<<<<<================================;<====<;968<;?@;=:::<;<<>?@=867:::::::::89;=;9:=6988=@@><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@;;??999<>???<<==<<=<======<<<<D=<==<====<;==>;;;?>@?<<><79;==?@?<====<====><>A=;>::A;<@8?:>?:=;6>===============<>?>=>@?<=????@@==>>>>>>>@><<=>>=;:A8?5;;=<==<<=ADIIGFGGGHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<<>:;@=;D[XFA?AB@@AAAABCB@=<=<;;;;<<<<<<<<<================================<<====<>:<;=<;9<<<8<=>=>?@A;;;;;;;;9;=<<>>;==<<;<;;>;99=>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:<<:8:?><<=======<<8:>;:48;=?><=>=;=========:=B@99==@>::==;5=?::??8<===<===?:8A>=@9?<@<;A?849:;>>=;<<=======>=<<<<<=::@:@ADIIGFGGGHGGGGGGGGGGGGGGGGIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<;=::?==F_[HB?AB@@AAAABCB@=<=<;;;;<<<<<<<<<================================<=====<;<;=5?7;:<=>;<<=;;<;7:??:<<<<<<<CA::==:;=><<>@@>=;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<6<:7:;;=>?<:;=;:B=;=>=<<======<<<:>6@>B:==>>>?><======<=@98>@<;A;>>:;=>=>@=68=>9<===<===?89;=AB>B6>?=@:=9@9;@:88===============<<9:=>??AC<9<=:9<<========<=><:<@=><7:=>9=<==<===?:9==::;;;=;@;FwwH9<;;<;<<;;<==<;<>AEIIGFGGGHGGGGGGGGGGGGGGGGIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<:=::?=>Id_JC?@BA@AAAABCB@=<=<;;;;<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<=====<;;<8;?lriW::<<;==AA<3EWkv~~xrbVG==<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:@<:A>;CKZny|tiYG87;==;<<<<<<;;A=?:`sx]=<<>??>=<;<<<<;<@:887>Qenx~tfZF<6:>=?B<=======<;;7DhvcN69=?==EXpbO>=?=<@>???????>=>@=:?EctpW<9:8=<==<===?:9==::;;;=;@;FwrH<><;<;<<;;<==<;;:<<<<<<<<<<<<:=;;>=?KibLD>@BA@AAAABCB@=<=<;;;;<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<==>==<;:C=>P«~E=8:;;DRLjI7@D<<<<<<<<::=A@>>AiͿX>:>?:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@98?9”A=;=??>?<;<;<;<;9?B>BYˮ[F9:?=>C<<====<=:>>9WoD;;8@?>XÁ::G===============<????????=?A=:BMnjD8><<=?BEIIGFGGGHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<:><;>?BBAAAAABCB@=<=<;;;;<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<>>>==<;9;8==<<<<=<<:=?;9<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:?66=7MĘfH<8:><<<<<<;;:8<;G?;=><;<;<;<<4AMYsˠX@<=?=<<====<=;?@;`ޥfJ@7>@<^51<================<=6>nÆM:=<:<=>>>>>>>?=>@<:DSB38><===<===?:9==::;;;=;@;FwìfF><<=?BEIIGFGGGHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<;?=;=;?MrfME>?BBAAAAABCB@=<=<;;;;<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<>>>>=<;95=EP։^>=@;F\I>=7ExӣjC:=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;A99@=cąY?57><<<<<<;;@>>:JB:<=<;=;<<;<;<<6?QrÂM:AC<<<===<===>>:_Շ]G8=?;jB;>================??9IW?AA=><=======><=?;9EVGBA?<<==<===?:9==::;;;=;@;Fw©cD;>:;<;<<;;<>>=<=?BFIIGFGGGHGGGGGGGGGGGGGGGGIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEFGGHGFE@>;:<<<<<<<<<<<<<@=;=;?MtoQAC?=DABAAABCB@=<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>=====;9>;8YɎVP9=Kg><=<<=<<<==<>=?:DL븤ـR>:<<<<<<<<<<<<<<<<<<<<<<<<<<<<====>7<=BS͒׭xP;68<====<<;;==;=V<;<;>8>;<<;<;<<@CpȞ~zxc@;:@=<===<==;><<_шHKABAc<<:<<<<<<<<========9C?UhB>@A9<========?>@8@=XGA==:;<=<===?:9==::;;;><@=Hx~UC;:<<<<<<<<<<<<=====;9<>:XmF4;Kd=<<=<=<<===<>:<>==<<::=<;=V<<=<>8=;<;;<;<=<===<==;><;`jI7?Of<<=<<<<<<<<========6>Dh~P@<@@<========?>@8@=XGA==;;<=<<=<@=HxwOB<@:;<;<<;;<<=<<=@CGJHFFHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHIIHHHHGGEFGGHGFE@>;:<<<<<<<<<<<<D@BAAABCB@=<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>=====;:8A;Xߤ_A6F`=;====<<===<=::=jÑTA;B?CzNF<<<<<<<<<<<<<<<<<<<<<<<<<<<<====9<::<>>>=<;::==;>V<<<<=8=;<;;<;<;;`ߝ^>;Pe:;?<<<<<<<<========68M֡eB:=B<========?>@8@=XGA>=;;<=<<=<@=HxnI?;:<<<<<<<<<<<<D@AAAABCB@=<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<====;:7A:Wфa;Ea=<<===<<===<==<>sk@9;F=5^ZH<<<<<<<<<<<<<<<<<<<<<<<<<<<<====:[eK9;?<=AA?AC?>>=<;::<=;>V;;=<=9=;<;;<;<=CA=98>OZBB9CED=<<<<==<;>;<_΋W<<<<<<<<========;9WzA;;<<========?>@8@=XGA>=;<;=<<=<@=HxfD=;@;;<<<<;<<<=<<=@CGJHFFHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHJIIIIHHHEFGGHGFE@>;:<<<<<<<<<<<<<>:=9>;cτ[BA@?C?BAAABCB@=<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<===<::9=8Y筄LKg><<<==<<===<<5Lo\><<<<<<<<<<<<<<<<<<<<<<<<<<<<=====:@=;Hs罕mK@;7687445>>==<<:;==;>V;<=;>9>;<;;<;<B@EG>F;B7>>5==<<===<<>;<`vGDc;;=;<<<<<<<========<@8@=XF@>=;<;<<<=<@=Hxb@<;?:<<<<<;<<<=<<=@CGJHFFHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGJJJJJJJJJJJJJJJJJJJJJJJJKKKKJJJJEFGGHGFE@>;:<<<<<<<<<<<<<>:>9=AAAABCB@=<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<::;:9[ׯbQj>;<<==<<===<:@?>woK@=;;66GX^8<<<<<<<<<<<<<<<<<<<<<<<<<<<<====?8??:8TŸVJB@AA@@====<<;<=<<>V<<<<>9>;<<;<;<<\N<<;@_w~ja@@F@==<<==<<<>;<`խhOj>:<;<<<<<<<=======<;EzsVLD<;=========?>@8@=XF@><:<<<<<=<@=Hx^?<;?;<<<<<;<<<=<<=@CGJHFFHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIIIIIIIIIIJJJJJIIIEFGGHGFE@>;:<<<<<<<<<<<<;=:?9<=mԎ`C@@@C>AAAABCB@=<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<;9=7<\pҔlk>;====<<===<;><;<;3:GP^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====>8>=;6?dmN>::;:<<<<==<===<>U<;=<>9><<<;<;<<`wN75;MC5<=<=<<==<<<>;<`sӠnw><=;<<<<<<<=======@8@=XGA=<:<<<==<=?:9==::;;;><@=HxY><:><<<<<<;<<<=<<=@CGJHFFHHHGGGGGGGGGGGGGGGGGBBBBBBBBBBBBBBBBEEEEEEEEEEEEEEEEEEEEEEEEGFFFFEEEEFGGHGFE@>;:<<<<<<<<<<<<;=:@9;>pՐbD?A@C=AAAABCB@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<==<<;9?5?\dΑi>;<<==<<<==<><8;{^??;=?8;;:::;<===><=<>U<;=<>9>;<<;;<<DY׵fC?;=<<<==<<<><<`[Ғ><>;;;<;<<<=======@8@=YF@><;<;<=<==?:9==::;;;><@=HxV==9>=<<<<<;<<;<<==@CGIGFFHHHGGGGGGGGGGGGGGGGGAA@A@A@A@A@A@A@A@@@@@@@@@@@@@@@@@@@@@@@@CCCBBBBBEFGGHGFE@>;:<<<<<<<<<<<<;=:@8:?r֠m=A>@@G>AAABBA?><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<<=<=;9;C=UWl{=;6B==<<===@<:<96=BPbsգd?:8><<<<<==>;>?AS;<::?:<<<<<<;<;c}L2:GTZI>8===<<;<<=<=@_W^>:C:;;:<;<<=A6B:>?@Oդשa<;========<C=;;:;;:<<<;<:>;Hy»T9??7;<<;;;;<<==<=?BEGIHFGHIHHGGGGGGGG<<<<<<<=<<<<<<<<;;;;;;;;========================DDEEFGHHDEFGIHEB>=<;<<<<;;<<<<<<;<<;9>:zؤm>A>@?F?AAABBA@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<==<=;98@;VHIa?;:@==<<==<=;<:8hՄW?A59DETeW8<=======<<<<<<<<<<<<<<<<<<<<====><:<>86=?Ko{H>64<<<<;=>>8;<>S;<;:?9<<;<<<<<<]̔_;BA>XptVD?>=<=<<;<<:8:>^QMh:8=;;;<<<<<;========<;A<;<>XHEA;Hy»R9?@9=<<;;;;<<==<=?BEGIHFGHIHHGGGGGGGG???????@>>>>>>>>========????????????????????????EEFFGGHHEEFGIHDB>=<;<<<<;;<<<<<<;<<<9><|ݭq?@>A?F?AAABBA@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<=<<<;98>9WPHIy=;=>==<<==<=<><7agA>:H54Y{W=========<<<<<<<<<<<<<<<<<<<<====98;BDCDGB<:::Nw<;>>?S;<;:?9<<;<<<<<;Wܬo><9@DIOgS?>@=<=<<;<<<:<@_UFFu789;;<<<<<<;@9@9=:JeJC========<;A<<<>VEA=6359<::>B;;:;;:<<<;<:>;Hy»zN9>A<><<;<;;<<====@CFHIHFGHIHHGGGGGGGGBBBBBBBCCCCCCCCCBBBBBBBBDDDDDDDDDDDDDDDDDDDDDDDDGGHHHHHHEEFHIHDA>=<;<<<<<<<<<<<<;;;<9><uA@>A?E?AAABBA@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<==<<;99?8VDD?Q:9?<<=<<==<=A;:=b~L<5CGHyN6========<<<<<<<<<<<<<<<<<<<<====@@?83=D@L>:?>S;<;:?9<<;<<<;<;WύTB7C>>=<=<<;<?^XG;S7;<<<<<<<<<:?:?9=8VmWA<=======<;@<<==UICB?>>>AA:8>;;:;;:<<<;<:>;Hy»qJ9=@==<<;<;;<<===>@CGIIHFGHIHHGGGGGGGGEEEEEEEFHHHHHHHHGGGGGGGGIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHEFFHIGDA>=<;<<<<<<<<<<<<<;;<9==yB@>A>E?AAABBA@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====<====;9:?:WEB@A^;:?;<=<<==<=?6:>Ru弃cIJ[lʨqE;========<<<<<<<<<<<<<<<<<<<<====4======;;:?<=9=7d漒qJ<========;@;==A;:A=9@;;:;;:<<<;<:>;Hy»jF;=>>;<<;<;;<<=<=>ADHJIHFGHIHHGGGGGGGGHHHHHHHIIIIIIIIIHHHHHHHHJJJJJJJJJJJJJJJJJJJJJJJJJJJIIHHHFFGHIGC@>=<;<<<<<<<<<<<<<:;>:>@~D?>B>D@AAABBA@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====>=====;98@;Y\FBCJS><>=<=<<==<==9A=?Hߴ}{щR==;<<;<:<<>S;;;9?9;<;<<<<;<0Dxˑ~ldxݞ_<=@?====<;<;<9=?^M><:Da>>====;;;?>;8>8t͘[UYK{ĕU<========:?;==;P}jigb_F4=?;C;;:;;:<<<;<:>;Hy»bB<<=?:;<;<;;<<=<=>AEHJIHFGHIHHGGGGGGGGIIIIIIIJHHHHHHHHGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHJJIIHHHGFFGHIGC@>=<;<<<<<<<<<<<<=:;>:=AҁE??B=D@AAABBA@><===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====>=====;:7@B?w<=<@<=<<<<<===C:=7tѷh@6;<=======<<<<<<<<<<<<<<<<<<<<====<8>;D…S?>6>>><;<<;<;==@R<<:9?:<<;;<<<;<@^N@>:<=u>>>>>>===;;>;NñuB?@8<;;:;;:<<<;<:>;Hy»Z><:=A;;<;<;;<;<<=>AEIKIHFGHIHHGGGGGGGGIIIIIIIJHHHHHHHHGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHIIHHHHHHGGGHIGC@>=<;<<<<<<<<;;;;<8;>;<===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====>=====;:8@;SFAF7<<`y8<:B<=<<<<<<<<<5D<]tΛyFAA@<=======<<<<<<<<<<<<<<<<<<<<====A7>?@?8E=><<<<;<:;::R<<:9?:<;;;;<<<;QӰ[=?=99==<<<<;>>==;;=?@88@;zN7I?;BgܸV<========:>;>>:N<8@=C<;:;;:<<<;<:>;Hy»T;<;?D=;<;<;;;;<<=>AEIKIHFGHIHHGGGGGGGGIIIIIIIJJJJJJJJJIIIIIIIIJJJJJJJJJJJJJJJJJJJJJJJJHHHHHHHHGGGHIGC?>=<;<<<<<<<<;;;;<8:?:<===<<<<================<<<<<<<<;;;;;;;;;;;;<<<===<<====8;8PЗE@<<<;?FؒH4===<<<<<<<==<==<<=fШYD>;:<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<==>@99>;;?<<<===<=;;5H˞CA=;;<<<;;;;<<<;>>>>BYΰ^P>79><9==<<====:@;5VߌE2C44C=O֔A>@<<<<<<;;@=;>=9Koѩk@8=;<@DӥV===<<<<<=<<=<<@Gڅ==?4?<;:;;:<<;=>:>=Jy»vH>>;@==;<;<<<<<>==?CGIJFGGGHIIIJIIHGFFEGGGGGGGHJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIGGGGGGGGEGHIHDA>>=<;<<<=<<<<;;<<9?;;>@B@?@A@BAAAABBA@><===<<<<================<<<<<<<<;;;;;;;;;;;;<<<=<=<<====:=>GczdA>;=><>A_{a@;><=<<<<<<<===<===>::>;7@Sn|ufQD<=?;;?<<<===<=<>=@ZwwdB?;;;<<<;;;;<<<==>=;<<<<====>C?6Cd{c<7A78B6Hisf8?=<<<<<<;;=>=>=9E_||kI9>C?9A:bwnF===<<<<<=<<=<;@GbyX2<@9A<;:;;:<<;=>:>=Jy»tH=>;?==;<<<<<<<>==?CGIJFGGGHIIIJIIHGFFEGGGGGGGHGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGEGHIHDA>>=;;<<<=<<<<;;<<:?;:?ACB??@@BAAAABBA@><==<<<<<<<<<<<<<========<<<<<<<<;;;;;;;;;;;;<<<=<<<<====<:=A>9=F<<?;;?;<<<<<<<<<<<<<<<<<<<<<<<<<<<<==>;;=<<<>?B;9>@;;>======<=:BBB?>==>?39?A>==?<========@@;;?@=?;=>7:=K9@?69BC88C6?=4D?>==<<<<<=;<><;?E=>=:=ABB>;><;:;;:<<;=>:>=Jy»nD<>:><=;<<<<<<<===@DGIJFGGGHIIIJIIHGFFEDDDDDDDEEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGEGHIHDA>==;;<<==<;<<;;<<CJE@?@@BAAAABBA@><=<<<<;;<<<<<<<<========<<<<<<<<;;;;;;;;;;;;<<<<<<<<<===@76@>68F;=>>>>=>><:;?=<<<<<<<=====<=<;<==<><>@?9:=;<<<<<<<<<<<<<<<<<<<<<<<<<<<<==>=?=<>A@======<<<9;==:A><<;:;=>9=@>;;>B<=======;;<>><;<9BA86;=???4=B;C?<<<<<<;;8@?;=<:=4:;:=B>27@:>=7B>>==<<<<<<;<><;>D@834887788:>=Jy»jB=?9=;=;<<<<<<<==>AEHIIFGGGHIIIJIIHGFFEAAAAAAABCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAGGGGGGGGEGHIHDA>=<;;<<=><;<<;;<<=>=<;<<<;;;;<<<<<<<<========<<<<<<<<;;;;;;;;;;;;<<<<<===<<==C:6>C???>=??==AA=:@@?==;;<<;;=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==>>@>88;=<@:=DD=>??=;<;>BA>;;:9<=;<<<<<<<<<>;9:;::<=??@@>=;>>>=;;=?<===<<<:7989B9<>:;;??<<>>86;;CFCC;>==<<<<<<;<><:=CB;9<><;>B69?::?=<;:;;:<<;=>:>=Jy»c>=@9;:><<<<<<<<==?BFIIIFGGGHIIIJIIHGFFE>>>>>>>?????????????????========================GGGGGGGGEGHIHDA><<;;<=>><;<<;;<<>=>>:IYOE@@@B@AAABBA@>;<<;;;;;;;;;;;;;========<<<<<<<<;;;;;;;;;;;;<<<<<===<<===>==>>;7>?><>?<8:@?:<;;;;;<<<<<<<<<<=<;;=><;766;=><:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==>8<=;:;<<=:;@@:9<;<;;;;>@====<<<<:AB;8<=:;;;<<=<<<<<<<<<<:;>=:9;=;<=>>>=<;;=??>;:<===<<<<>::>>;:<@?:9>97@??>>==========<<@B@;9;?B>=>>>=<<===<<<<<;;=>=:78<;=?=<;:;;:<<;=>:>=Jy»^;<@:::><;<<<<<<==?CFIJIFGGGHIIIJIIHGFFE>>>>>>>?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>GGGGGGGGEGHIHDA><<:;<=>?;;<<;;<<>:>?8MaTGAAAB@AAABBA@>;<;;;;::;;;;;;;;========<<<<<<<<;;;;;;;;;;;;<<<<<=======9>@>;==<;>?<;<>?=:<;;;;;<<<<;;;;;;@?=====<79;;<<;:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==>89;?A>=>>><>===><;::<=>>====<<==;@@><===<=<==>=;<<<<<<<<8;>><;=>==>=>>>>7:=?>=<;<===<<<<::;>?=<==<<::::;@<<9;?>>======<<=@;:=:9ACA?=:9<@B@@<8;;>>==<<<<<;;=?=:<@=?>:9<>><>><=>=:<;:;;:<<;=>:>=Jy»Y9=@:99><;<<<<<<==@CGIJIFGGGHIIIJIIHGFFECCCCCCCDBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBGGGGGGGGEGHIHDA>;;:;<=>?;;<<;<<<>9>?8PhWJBAAB@AAAABB@>;;;;;:::;;;;;;;;<=<=<=<=<;;;<;<;<<<<<<<<<<<<====<=<<<<<<;<=>>>=;8==;<>=9>:=A@;;D;<;<;<<<<<<<;;;;9:;=>>=;69:888<><<<<<<<<<<<<<<<<<;;;;<<<<<;;;==>D<:>?:8:=>=<;>>=@@>=>?=:<<======C<9?CA=;><<=?==;===<<<<==>><9999;<=>><:9<>?=::=?<=<<<=<=;=;:;<=;>?@929?::9<==<<<<<=>>;<>76A>:;=>;;;>;?=@C=>===<<<<<;;=?=:;@6;>;;>=88B@9;=<><<;;::<<;=>;>>Ky¼V8<;<<<<=<==@DGJJIFGGGHIIIKJIIGGFFGHGHGHGHHHHHHHHHHHHHHHHHEEEEEEEEEEEEEEEEEEEEEEEEGGGGGGGGEGHIHDA>;;::<=>?<;<<;;<?7QlbL@==H=>><=<;=<=<=<=<=<=<=<=<<=<<<<<<<<<<=<=<;;=>==>>>===<=<;;;:;:;;;;;;;:::;:::;;;;;999:9::;:::::::::::::::::;:::::;;>=>?A?;>>>>>>>>>>>>>>>>========================;:::;;;:;;;:;;:;<;<<;<<;=<<<==<===<<<<=<<;;;;<;<;<;;;;;<<;<<<<<;;;;;;<<;:::::::;;;;;;;;::=??<999:9:;;;;:;<<<<<<<<<<<<<<<:::::;;;;;;;;;;;;;;;;::;;>=8B>FyrN=9>;9;8:<;<===<>=>ADGGGHHHHHHHHHHHHHGGGHHIIIIHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHIJJJIIIIIIIIFGGGFC@?:;::<===;<<<<<<;=?:8@LxiQB>=H>>>?@@@@@@@@@@@@@@?@@@@@@@@>>>???@@<=====================<<<>?=<<;::999999999999999888888888888888888888888;;;;;;;;::;;:;;;;<<;<;<;;<;<<<<;;;;<<<;<::::::::;;;;;;;;;;;;;;;;;;;;;;;;================:<<<:;;<::;<<<<=?>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?>???7<9EydL=9><9:8:;<=====>>>ADGGGHHHHHHHHHHHHHGGGHHIIIIHHGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGHHHIIIJIIIIIIIIFGGGFC@>:;::<===<;;<<;<<<>99AQqVC?>F>>>>>>>>>>>>>>>BCGA=EG>AA@?@???A@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AA@??>=<<=<<<=>=>=>>>>>>>>>>>>>>>>=======;;<<<<=>>>>>>>>>>>>>>>>>==<=====>=>===>B????????????????>>>>>>>>>>>>>>>>>>>>>>>>????????>??>????@??@?@?@?@@@@@@@????????>???????@@@@@@@@???????????????@:;;;;;;;<<<<<<<;>>>?@@@@:99:;;;:9:9:9:9:9:9:9:9::;:;:;:;:;:;:;:::;:::;:;=@B;>?L}SI<9>;:;9:;<=====>>?BEGGGHHHHHHHHHHHHHGGGHHIIIIHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHFGGFEC@>:;::<===<;;<<;<<<=::AVxZA@?C=B@AABB?>???????????????>8;:<9:;<======>?BFHHGHHHHHHHHHHHHHGGGHHIIIIHHIIHIHIHIHIHIHIHIHIHIHIHIHIHIHIHIHHHHHHHHHHHHHHHHGGGFEB?=:;::<===<;;<<;<<<<;:?Y]?A?A?B@AABB?>@@@@@@@@@@@@@@@@@Gnīy?C::=:;=::;<======>@CGHHGHHHHHHHHHHHHHGGGHHIIIIHHJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJHHHHGGGGHHHHHHHHGGGFEA><:;::<===<;;<<;<<=<=:<\Å`?A?@AB@AABA@>AAAAAAAAAAAAAAA@;=hĤ|>@9;=9<>::;<======>@DHIHFHHHHHHHHHHHHHGGGHHIIIIHHJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJGGGGGGGGHHHHHHHHGGGFDA=;:;::<===<;;<<;<<=;>:9`Ɏe@B?>AB@AAAA@?@AAAAAAAAAAAAAA@ECezq?=9;=9AEHIHFHHHHHHHHHHHHHGGGHHIIIIHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHFEEFFGGGHHHHHHHHGGGFD@=;:;::<===<;;<<;<<=:?;9e͔iAC?=AB@@AAA@??@@@@@@@@@@@@@@@>AEHIHFHHHHHHHHHHHHHGGGHHIIIIHHFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGEDEFFGGHHHHHHHHHGGGED@=::;::<===<;;<<;<<<9@<:iӣ`EA?D>A@@A@@AABBBBBBBBAAAAAAAA@AXŧÿd<9;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHIHHGFEEDDDDDEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDEAAABCDEFFFHIIJJJHGFDB?<;:;;;=<<<<;;<=<==AA@A@AAABAAAAAAA@@@@@@@A@@SwƧ];9;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHGFFEDCBA?@@@@AAA>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?@?@ABCDEFGHIIJIIHGFDB?<;:;;;=<<<<;;<=<==<>:;@l׮fDB@B?AA@@AAAAAAAAAAAAAAAAAAAAB=KhƧuS<:;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHEEDCA@??<<<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?@BCDDFGGHIIIIHGECB?<;:;;;=<<<<;;<=<==<=;;@q۷kCBAA@AA@@AA@@@@@@@@@@@@@@@@@?D>E[ƧiK<:;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHFFEDBA@@===>>>>>?????????????????????????????????@ABCDEEFGGHIHHHGGECA><::;;;=<<<<;;<=<===<;:;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHIIHGEDCCBBCCCCDDDDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCCCDEEFGHHGHHIIIHHGFECA>;::;;;=<<<<;;<=<===;<=>|t?AB@AAA@@AA@@?@@@@@@@AAAAAAAABB@IƨT@=:;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHJJIIHGGFGGGGHHHHFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFHHHHHHHIGHHIIHHGGFDBA>;::;;;=<<<<;;<=<===9===x>AB@BAA@@AA@@@AAAAAAAAAAAAAA@@B@BzƨM=<:;<<=<;;::;<<<<<<@DGHHHHHHGGGGHHHHHHHGGGHHHIIIIIGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJJJIIHHHGHHHHHGGFFDB@=;9:;;;=<<<<;;<=<===9>>=y=AB?AAA@@AA@@AAAAAAAABBBBBBBA=B@@tƨJ;;9:<;=;;;::;<<<<<<@DGHHHHHHGGGGHHFFFFFGGGGGGHHHHIHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIKJJHIGGFGGHGHGGEFFDC@=;9::;;=<<;<<;<====<8?>>>CA;BAA@@@A@@AAAAAAAAAAAAAAA@G:GBqŦǵyJB;==<<;<<<<<<<<<<<@BFGGGGGGHHHIIJJGGGFGGHHGGGHHIIIGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJJIJHIGHGHGHGHGHGFDB?>=;9:;<=<<;;;;;====<<;9:<<=<<;;;<;======@;UKHC?CAAA@@@@@AAAAAAAAAAAAAAA@A;D?`ŦhB?>===<<<<<<<<<<<<<@CFHHGGGHHHHHIIIIIIHHIIIJJJJKKKKIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHJJIIIIHHHHHHHHHHGFDA?=;;9:<<=<<;;;<;====>=@>]UIB@CAA@@@A@@AAAAAAAAAAAAAAA@@>==<<<<<<<<<<<<<@CFHHGGHHHHHHHHHJIIHHHIIIIIIIIIIHHGHGHGHFGFGFGFGFGFGFGFGFGFGFGFGIIIIIIHHHHHHHHHHGECA?=;;9:<<=<<;;;<;======>@g_KA?AAA@@AA@@AAAAAAAAAAAAAAA@@>D@RgŦO><>>==<<<<<<<<<<<<=;:9:<<=<<;<;<;====<;==<<<<<<<<<<<<>>>>>>>@@@@@@@@@@@@@@@@@@@@@@@@GGHHHHIIHHHHHHHHFEC@><::9:<<=<<;<<;;====;<;F~oN@C@AA@@AA@@AAAAAAAAAAAAAAAABA@AIPŦh=?:>>==<<<<<<<<<<<<>>>===<<<;;<<<<<<<<=======================>GGGHHHIIHHHHHHHHFDB@><::9:<<=<<;<<;;=<==;===<<<<<<<<<<<<================================FGGGHHIIHHHHHHHHFDB@><:99:<<=<<;<<;<=<==P﹂PD>==<<<<<<<<<<<<=;:;;<<<<<;<<;<=<<<9:?PVG=DAA@@AA@@AAAAAAAAAAAAAAAA@F==<<<<<<<<<<<<=;:;;<<<<<<<;;<<<<<;;?QȘ[I>DAA@@AA@@AAAAAAAAAAAAAAAA?F=A?@tŦQ9>==<<<<<<<<<<<<<@BDFHIIIHHHHHHHHIIHHHHIILKKJJKKLHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFD@>>=;:;;<<<<<<<;;<<=<<=<=SѢ]H@DAA@@AA@@AAAAAAAAAAAAAAAA?CA@=AiŦuM:<>?>==<<<<<<<<<<<<==;:;;<<<<<<<;;;<=<<==;Tة[EAD@@@@AA@@AAAAAAAAAAAAAAAAAACA?C`ŦfK>9==>==<<<<<<<<<<<<<>@BEGHHHHHHHHHHHHHGGGGHHGGFFFFGGGGFGFGFGFGFGFGFGFGFGFGFGFGFGFGFGIIIIIIIIHHHHHHHHEB?===;:;;<<<<<<<;;;<=<<<=:W߯[CBE@@@@AA@@AAAAAAAAAAAAAAAAA>D?@FU|ŦXF@8=<>==<<<<<<<<<<<<<=?ADFGHHHHHHHHHHHGGGGGGHHGGFFGGHFFFFFFFFEEEEEEEEEEEEEEEEFFFFFFFFIIIIIIIIHHHHHHHHDB?===;:;;<<<<<<;;;;<=<<9><\]CCD@@@@AA@@AAAAAAAAAAAAAAAACBGJkŦIBB7=;>==<<<<<<<<<<<<<<>@CFGHHHHHHHHHHIIHHHHIIHHHGGHHHGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGIIIIIIIIHHHHHHHHCA>===;:;;<<<<<<;;;;<=<<9@@baCBC@AAAA@@@AAAAAAAAAAAAAAAAC;B=AGC_Ŧy@>C6=:>==<<<<<<<<<<<<<<=@CFGHHHHHHHHHHJJIIIIJJHHGGGGHHIIIIIIIIHHHHHHHHHHHHHHHHIIIIIIIIIIIIIIIIHHHHHHHHC@><=<;:;;<<<<<<;;;<<<==8BCfaC?CCCA@@@@AAAAAAAAAAAAAAAA@????ADHMŦ`==A:6?<<<<<<<<<<<<<<<?ADGIGHIIHGGHIIIIIIIIHHHHHHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHGGFFFFFF==<<;<<<<<<<<<<<<<<<;;;;>;LseF?CCBBA@@@AAAAAAAAAAAAAAAA@@@???CHKqŦxW;9?;:?<<<<<<<<<<<<<<<?ADFHFHIIHGHHIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGFFEDDC===<<<<<<<<<<<<<<<<<;;;;@<<<<<<<<<<<<<<<?@BDFFGIIHHHIIIIIIIIIHHHHHHHHFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGHGFECBA?===<<<<<<<<<<<<<<<<<;;;;A=WyT@CCBB@@@@AAAAAAAAAAAAAAAA@BCA@@ADGEvŦIC;7;??<<<<<<<<<<<<<<<<?@ABCDFHHHGHIIIIIIIIIHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHGEDB@>====<<<<<<<<<<<<<<<<<;;;;A=^Ն]ACCCA@@@@BAAAAAAAAAAAAAAAABCAA?@CE@]Ŧf>?<8;@@;<<<<<<<<<<<<<<<>??@@BDFGFFGIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHFEDB@>=;===<<<<<<<<<<<<<<<;;;;;<>>gّeACCCA@@@ABAAAAAAAAAAAAAAAABBA@?ACC=HoŦyN=?>9;@@<<<<<<<<<<<<<<<<<>?>>>>==?ACDEEFHGGGGGGGGFFFFFFFFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHCBA@?=<;>==<<<<<<<<<<<<<<<;;;;;<;=oޚlBBCCA@@@ABAAAAAAAAAAAAAAA@@BB@@AAC=@UŦ^>?>;9;>?<<<<<<<<<<<<<<<<<=>==<<;:<>@BBCDFEEEEEEEEDDDDDDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE??>==<<;>==<<<<<<<<<<<<<<<;;;;;<;>vߟoABCCAA@AABAAAAAAAAAAAAAAA@@@BAAABD>><<<<<<<<<<<<<<<<====<;:9:==<<<<<<<<<<<<<<<;;;;;<;={ܮu@FBCBA@AAAAAAAAAAAAAAAAAA@@A@A@AA@;FFD^ŦrN==<;;<<;;;<<<<<<<<<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;<;9߳zAFBCBAAA@AAAAAAAAAAAAAAAA@@A@@AA@ABA>>OqŦnRG@9;;<<<;;;<<<<<<<<<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;<=;⽁BGBCBAAA@AAAAAAAAAAAAAAAAA@AAA@AA@D<:>AMpŦmK<<>;;;<;;;;;<<<<<<<<<<<<<<<<========>>>>>>>>================;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;========<<<<<<<<<<<<<<<<<<;;;;;;=<ȋBEBCBAAA@AAAAAAAAAAAAAAAAA@@@@@@@AA=AEA?OiŦoKCA75A;;;;;;<<<<<<<<<<<<<<<<<<================================================================================<<<<<<<<<<<<<<<<<<;;;;;;;<ӓADBCBAAA@AAAAAAAAAAAAAAAA@A@@A@@@@@ACCA?BHiŦ¶pQ>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>========<<<<<<<<<<<<<<<<<<;;;;;;:<ݛBBBCBAA@@AAAAAAAAAAAAAAAAAAAAAA@@@AEA;>;;<;;;<;;<<<<<<<<<<<<<<<<================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<;;;;;;GL\wŦqYH=@?55>@:<;;;;<;;<<<<<<<<<<<<<<<<================<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;========<<<<<<<<<<<<<<<<<<;;;;;;BECCCCBAAA@AAAAAAAAAAAAAAAA@A@@AAA@A@??AAAA@FABISjƦsUC<;@<;=<89=<<;;;<;;<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<;;;;;;GJKGAA@BBCCBDDCDCDCDCDCDCDCDAAAAA@A@@A@A@@@ADCACL]kuƨľuhTF><:;;;;<<<<<;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<;<<;;I[XM@@AABBCBCBBBBBBBBBBBBBBB@@@@@@@@@@@@@@@@??>=?BEE]gxŨpeWNC:8998<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<;;RhjW@@?AABABBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAABCEEDA>;?FNU\fqzƧÿ~yie^ZVQLH@=89:<<:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;\|ǁ_>>??@AAA@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAA==>??@AB=?@?>@EJTYakt|ƨukaZUFB@===;8=<;=?@?=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;dЙg?????A@@@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAA@A@?>=?@A@BC@>>@BDGJKMNRTRV^kzƧvmeZRHFB=<;97B@?@?><;=<;;<;;:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;<;;;;jܰr=>=?@@?A?@@@@@@@@@@@@@@@AAAAAAAAAAAAAAA@DEFFEEBB?ACCAAA@ABB?=<==BBCJU_egtyƨ}zvsvtv{}wrphaXQMHC>>=:;=@BA68;=?>>>=;::9:;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<;;;;t}=>>>?@?@????????????????@@@@@@@@@@@@@@@@;<>@AA@?@@@@?>?>??@@>?@@DDB?>?@@EB>:98;;<<;;=?;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;ӆ<=>>?>@@@@?@?@?@?@?@?@?@@A@A@A@A@A@A@A@@BAA??@BCCA@A@CEEBCDCB@@A>BED@>AD@AABEGLORW]adju~~Ũ|ywtonjgb`]\YVROMKIIACEEA::<=@BA?=;;?>>?@AA@;;<;;<=>9:;=<;<<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;M:>===<==<<=<====<=======<<========<<=====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========C7A@69?QaA<;;>:?================>>>>>>>>>>>>>>>>AAA@@@A@?@???@??>??A@BBCBBCBCCCCAAABBBAB@@@@AA@@AEHJIKNSRVY^afilu}ywrke`\XRPNMJIGGGEBA>=<====<=<<<<====<======<<<<<=======<<<<<<<<<<<<<<<=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========B7>?;;JmQ9:<:5?;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<>>????>?>>>>?>>??>>??@@BBBBBAAAAA@@@AAAA@@@@@@@?>@B?=;=>@@BA@?AAPU]eks{Ǧ}|yusppmjifdabaXVSQLGFC;::99:::9889::;<<;;;<:;;<=<=<<=<=<<<<<<<<======<=<<<==<==<==<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========>9;<==Wժ`:8=<5?::::::::::::::::;;;;;;;;;;;;;;;;>=>=>>>=??>?>>>>>=>>>?@@AAAAAAAA@?@??@@?>?>>>????ACDCCEGCCDCA@@A8;===?@BBBBAAFMSciq{§|ywtkjf`[UROIGDB?>??<=>=>??@;:<<>>>@<<:;;<<=:;:;;;;;<===<<<<=<<<<<<<<<<<=<=<<<<<==<==<==<<<=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========;;>;<=>>==>>@@@?????>>>??>??==>>>===?@@@?>?@=?@@??@ABBCBA@AAAA?<;?FMEGMSW\adlnsvz}olkjhfeeca`_\YVTTLJIIFEDD?=;88798:9:9::99;<<==>>?>><;999::::;::::<=<==<==<==============<<=<<==<===<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========;>@:=====>=>>=>>>=>=<==<=<<<;;::::9=>??>>?A:;:<;=<>:<=?=>=>??AAABDD<<;:9:>>>@@@A?=<;;;<><;::::88;==<=====<;::::;99:::;;;<=<=<<<=================<=<<<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========;=@9A]uI7<:8<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;<<<<<;;;;<<;;;<<====;;;;<====<==<==<====<=====<=:::;=???:;<<;:;<<;;;<=<@BEGKNNMMOLKOTQPRSVXYX[]`dgkmntuvx{}~|yxxmligdb`a^]ZWTQOOQRQPMLMNIHFCB@====>>>???>>>>>==<==;;;::::99:;;=<>==>>>>>>>==;<<>9::;<;;;<<===<<<<<<<========<<<==<<<===<==<<<=<=;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========<9>;OŽU7=<:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<;;:;:;:;<=<<;;;<==<<<=<<;;<;<<<<;<<<<<;<<;;<=>>=<=>>==>>===>?@@??<99<>=;=>?=<;=><==;::=>;===<<>@=>>><;:98=>ADGILMMNRUY\___abdfijlmnoqsuvw{|}~}|zyvwwvusqmida``^\ZWURPPOLHECA@==>==<<<;<<<87:?<>==>?>?>>=<;:::====>>>=<===;:98:;;<<=<=:;;;:;;;==<;;:;::::;;=<<=<=<=<<<<<<<=========<===<<<===<==<<<===;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======><9;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<<<;;;;<<<<<<<;;;;;;;;;<<<<<<<<<;;;;;<>?>?>?>?>?>?>??@?>=<;:9<<<;;;;;9:::9:::=<====<=>>>>>>>>>==<;;::@?????@@EEEEEEEDHHGFEDCB@@@@@@@@EEEEEEEEDDDEEEEEGGGHGGGGFFFFFFFEEEEEEEEE???@?@@??@@@????>>>>=>==<<<<<<===========<=<<<=<;;<;;;<;;;;;;;;<<<<<====<<;<<;<<======<=;;;;;;;;<<<<<===<<<<=<===<<=====================<<<<<<<======<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;8<:;<>E;Jq֔j:4<;;::;===;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================?>>=<;;:<<<<<<<<;;;;;;;;========>>>>>>>>===<;;::================??>>=<;;=======<=>>>>>>>>>>>>>>>>>>>>>>>????????;<<<<<<<=======<=========<<<<<<<===============<<=======<<<<<<<<<<<<<<<<<<<<====;<;;;;<<==<<===<;<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;:;<;:9::=;AjČF2@=;<;::=@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<<<<<===================<;;;:;;;;;;;;;;;;;;;;<<<;;;;:;;;;;;;:9:::::::::::::::;;;;;;;;::::::::99999999::::::::;;;;;;;;;;;;;;<<===============<<=======<<<<<<<<<<<<<<<<<<<<====;;;;;;<<==<<====;<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<;;<<<:9:;::;>5OhDB@>>><9;?<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<====<<<<<<<<================<<<<<<<<==<<<;;;<<<<<<<<================<<<<<<<<;<<<<<<<<<<<<<<;>>>>>>>>;;;;;;;;========;;;;;;;;<<<<<<<<;;<<===>================<=======<<<<<<<;<<<<<<<<<<<<====;;;;;;<<==<<====;<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<;;<<;:9<====@Ctϛb>><<>=99=;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<========<<<<<<<<;;;;;;;;<<<<<<;;========;;;;;;;;;;;;;<<<========================<<<<<<<<========;;;;;;;;<<<<<<<<========;;<<===>================<<<<<<<<;;;;;;;;<<<<<<<<<<<<====<<<<<<====<<<<==;<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<;;<<=::>?;:=CbƃF@:7<=99=;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================<<<<====<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<<<<<<<<<<<<<<::::::::::::::::;;;;;;;;;;;;;;;;::::::::;;;;;;;;<<<<<<<<;;;;;;<<================<<<<<<<<;;;;;;;;<<<<<<<<<<<<=====<<<<<====<<<<==;<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<;<<>;;=<79@RwK?8;>;:>;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<<<<<<<<<<<<<<================<<<<<<<<::::::::::::::::========;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<=<<<<<<<================<<<<<<<<;;;;;;;;<<<<<<<<<<<<========<<<<<<<<====;<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<<;;>;;=:9BQݲ\J=;=;:=;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<<<<<<<<<<<<<========;;;<<<<<========;;;;;;;;;;;;;:::========<<<<<<<<<<<<<<<<========>>>>>>>>;;;;;;;;<<<<<<<<========????>>==================<<<<<<<<;;;;;;;;<<<<<<<<<<<<========<<<<<<<<===<<<<<<<<<========================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<<<<;;;8:=<>PeqB7<88><<;;;=>=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================<<<<<<<<================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<============================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========<<<::;==<@EA:HyפhH?>98;:977788<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================;;;;;;;;;;;;;;;;================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<============================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========::<===<;=36DOfϚgJA=7<=>>???B<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<============================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=========::;<;98::89:=>;:=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================>>>>>>>>>>>>>>>>========<<<<<<<<================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<============================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========>98:;<>;:=>:9:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================;;;;;;;;;;;;;;;;========<<<<<<<<================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========;8:==;=@EOz币\G>7:>==@;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================<<<<<<<<<<<<<<<<================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========;:=@;45>>>>>>>>>>>>>>>================================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========>;>@=?Ocǜ|[KE>:<<<<<<<<<;<;;;;;;;<;<;<;<;<;<;<;<<<<<<<<<<<<<<<<<<<;<;<;<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================<<;<;<;<;<;<;<;<========<<<<<<<<================<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====<<<<<<<<<<<<<<<<========<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=<<<<<==8:@I\rWHBBB>;<;9:>?;67;><:;:<;<;<;<;<;<;<;<<<=====>>>==<<;;;<;<;<;?>=<=<==;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<========<<<<<<<<==================<=<=<=;<;<;<;<;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<========<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<;<<<<;;;;=;;;<=>=>>=;;;;<==<::<==<997:::::986<;<;<;<;=<=<=<=<;;;;;:::::::;;;<99:9::::;::;<<==;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<========<<<<<<<<==================<=<=<=;<;<;<;<;;;;;;;;;;;;;;;;;<;<;<;<<=<=<=<=;<;<;<;<;<;<;<;<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<========<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;<;;<<<<<<<<<<;<<<<<<<;:9:9:898===<<<;;======<<:9999999:::9999:888999998:=?@?>=9Mpٳq]QC=;::=9=@A?<<<<=;<;<;<:;:;:;:;========<<<<====>>=>====9::<<<<<;;;;;;;;;;;;;;;;;<;<;<;<;<;<;<;<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<;<;<;<;<;<;<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<;<;<;<;<;<;<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<;<<<;;;<<==>=<;:@???>>>>???>>>>>????????<<<<;;;:@@@@@???B?:7:FU`t•~`J?::=89776689<<<<<<==;;;;<<<<99::::::;;;::999::::::::===>=<;:;;;;;;;;;;;;;;;;;;<;<;<;<;<;<;<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:;<<<<<;<<<<<<<<<<<<<<<;799:9;::87899:;;:::99988=<<<<;;;======<<<;98889;@><>I`}¯s^RRKB==?@?::::::99;;;;;<<<<<<<<;;;==============<<;;;;<<<;<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;<;<;<;<;<;<;<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;<;<;<;<;<;<;<;<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;<=<;::;;;;;;<<<;<<<<<;==;;;<=><;;;;;;;<<<<<<<=;<<<<<<<:::;;<<<=<;;>BGHQ^rغti\TSROJLLKIHFEEDCCBA@?@@@?=<:99899::;;;<<<<<<<<:;:9:<=><<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<::::::::;;;;;;;;<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<;<<<<<<<;;;;;;;:<<<<<<<<<<<<<<<<<<;<;<;<;<;<;<;<:;<=<;:9;;;;;;<<;<;<<<<<><:9:::;<<;:98877789:;<=??ABDFGHGGIJLNOPGHJOV^ehĻ~{xvqpnkhecbWVSOLHEDAA@??>==>>>>>>??B@>=;;=><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<;<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;::::::::;;;;;;;;::::::::::::::::;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<;<<<<<<<;;;;;;;:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:;<>=;;:<<<<;;<<;;;;;;<;689<<<;;?>>>>>>>=>@DGKMNUXZ_chkmx{|þ~xtronlhea_]\\\\\\[[NLE@>;<=<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<;;;;;;;;;;;;;;;;:;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;:::::::;<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9:<>><;:<<<;;<<<<;;<<<;=J::D><>>>>>>???8899::;;<<<<<<;;@AAAABBB?=;99:<>99::;;<<<<<<<===;;<<==>>9::;;<<<::;;<<==========:::;;<<<99:::;;;;;;;<<==;;;;;;<;::::::::88999999;:::;;<====>>???<<<<<<<<::::::::<<<<<<<<========AAAAAAAA========>>>>>>>>AA@??>==;;::999899999999;;::9988::::::::======>>========<<<<<<<=:;=>??@A<<<<<<;;>??><::;ABBA?>>?::;<=>>?E<9?C?>==<<9:::::::<<<<<<<<>??????????>>>==@@????>>>>>>????<<<<<<<======<<=;;;;;;;;;;;;;;;;::999:;;?????@@@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>========;;;;;;;;99999999:::99988>>>>>???<<<===>>;;;<<<<=<<<<;;;;>>??@AABBA@?>=<:9::;<<===>@BDEFFHIKNQSUVZ\_`bdfgdfhjkloqwxz|~]:2667:9889999874564:.XJ0/0/-111122223332116:}{ynmkheb`_LKIFC@?>>=<;:877BB@?=<;;>?>==<<;@@??>>===<<;;::9AA@?=<;;AA@?=<;::::::::9====<<<<>>===<<<===========<<;;;;::999::=====<<<;;;;;;;;<<<<<<<<<<<<<<<<================????????========>???@@@@<=>?@ABC89:;<=>>:;<=>@AA=>>?@@AA::;=>?@ABBAA@?>==<=?@ACCNOQTVXZZfilptx{|ȃ=(-102000111010.12-81bF)*-,)....////1//0/.6Auttrrpponllihffddbb_^[ZXWVVUVTUTQRQQPQOPPQOONNMMKKKKKKKKJIIHHGFFGFEDDDDDBBBAAA@@>>>>>>>>@@@@@@@@DDDDDDDDFFFFFFFFLLLLLLLLPPPPPPPPRRRRRRRRWWXXYZ[[]^`bdfhhklmoqrststvxz|}~|}F+22.,+,,,,+,,-+//)31iG,-.-.,-....//0-.1--{f?<::==>>=<<<9<4::88pQ=>;8<998899::?5;8;9o?::<==>==<<<;>5;;9:qQ>?;9<:;::;;<<>7?:::xB::>====<<<==?7<<::rQ=>;9<;;;:;:;;:7@9?>>>==>>=@7==::rP=>;8;:::9889886=6APWA7=?>>>==>>>@7=<::qP<=:8;<<<::9:9;9=8J`aE6;>===<==<>@7=<::qS==<:<<<==<<===9=?RzߨwF<9====<==<>A8>>;:qR===;<=<<=<<==<;?>UJ=;======<<>@8>>::qR=>=;==<<=<<==;>@@8>=:9pR=>=;==<<<====9?@;aȖQ<<======<<=@8>=:9pR=>=<==<<<====8>>:iҟT<=======<<=?7==:9pQ=>><>==<<====9=<=t٪Z=======<<<=?7=<98oQ<>><>==<<====<<;Bb@>=====<<<>=>=<==<<==@<:FiDA=====<<<?=;;====<<<>=;;=<==;;<<>6DZɂSA<====<;:==;;=<==<<<<<7Gi͏_@<<===<;:;pO<>=;<====<<<<=;Kո:<=<<=<;::@8<<>=;<====<<<<<<<===<<;:@8<<>>5><=<<==<=7=^כJ@6@?:?;6?<=<<==<=:Df՜OD6??:?;<>==7;:AjܪO;<=7?<<====<<>;?;<>==8;:@i֨P<;=8@<<====<<=:?:===>9<:?hϦQ<;=8@=<<<<<<<:[ŢsT===;?:===>:=:>fȣR><=7>=<<<<<<<:b¿[?=<;?:=<=?<>;=e¡S@=>7=<=<<<<<<>k`@=<>7<=<<<=<<=CpaA;<><;==;89?A?H}iL8;@;:@=<RjpSA;:<;=?=;;@G[wZB>@=:;::>A>E\wcF89=<<;<=??@LlrQ@=<>;=C@@ObhMA>?=><=@G[~n]L@A:;GQXkhTJC>A?CN_v¿hTQLP]lzucWOPT^oxxz}zz~}}~}}}~}}}}~}}}}}z}~~~~}~~}~}}}~~~~}}~}~}~~}~~~}~}}~~}}~~~}}~}}}}}}}}~~}}}}}~}~}}~~~}}}}}}}zntywwyz}}rccgcefghklnmnpsvy}vgegaceeeggggcdegilopoprvy}{|yjjlgghhgggggfeefefffcdeeggghquux}~ytnjgX`ttdfjfeedcccccilihfcccghggfecccbcgp}ztp^VTPLGDA4Fgyhijffgghgghhccccdfggcceeegggigcdny}q^NIGA;?@@@@@@@?Daxggiefgggggggeeeefeegeeeefffgfigbg|~}p\I@@FD??@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}~}jTI>;@FEA@@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}~xQA<8;BFFB@@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}~}[C?>>@DDC@@@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}cD@BCDDDC@?@@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}zO:ACCDC@>@B@@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}iD9D@@@><<@EA@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}\F@A@@@???@A@@@@@@@@BB_ugfheefffffffffffffffffffffffcgg`g}~|WGC@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~uRD@?@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}lMA@>>@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}dF?@>>@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}]B>@?@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}}Y@=B@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}{V>=BA@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}pIA@<@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}jDBC<@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}dDCA<@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}ZDCB=@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~}~~~}xRDD@?@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~~oIDC@@@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}g@BD@@@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}bB=?@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}|}~vQ@D@=B??@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}}}~~~pL@E@<@@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~~~~~~kH?E@9@@@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~}~~~gD?E@8>A@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~~cA>G@8>A@@@@@@@@@@@@@@@@BB_ugfheefffffffffffffffffffffffdgg`g}~}~z~T@@A@=@@ABA??@ACIJJKKKKKILfugfheefffffffffffffffffffffffdgg_e{||}~N@@@@@@@AB@>?@CEMOOOOOOOLNiugfheefffffffffffffffffffffffeig_ez|}}M@@@@@@@@A?>?AEINOOOOOOONOiugfheefffffffffffffffffffffffeig_ez~~~uK@@@@@@@@?>>@DILOOOOOOOONQiugfheefffffffffffffffffffffffeig_ez~~}|mI@@@@@@@?>>?BEKOOOOOOOOOOSjugfheefffffffffffffffffffffffeig_ez~}}fF@@@@@@@??>?BHMPOOOOOOOOPSjugfheefffffffffffffffffffffffeig_ez_D@@@@@@@@@@@BHMPPOOOOOOOQTjugfheefffffffffffffffffffffffeig_ez\D@@@@@@@AA@@BHNPPOOOOOOORUiugfheefffffffffffffffffffffffeig_ez}~XB@@@@@@@@@@BEIMPOOOOOOOOPTjugfheefffffffffffffffffffffffeig_ez~}}VB@@@@@@@@@@BEJMPNOOOOOOOPRjugfheefffffffffffffffffffffffeig_ez}}~}~zUB@@@@@@@??@ADIMOPOOOOOOOOQjugfheefffffffffffffffffffffffeig_ez}}}~~~yRA@@@@@@@>>?@DHLPOOOOOOOONQiugfheefffffffffffffffffffffffeig_ezwPB@@@@@@@>>?@CFKMOOOOOOOOLPgugfheefffffffffffffffffffffffeig_ezuM@@@@@@@@@@@ABDHJNOOOOOOOKNgugfheefffffffffffffffffffffffeig_ezrL@@@@@@@@@BA@ABDGMOOOOOOOJMgugfheefffffffffffffffffffffffeig_ezqJ@@@@@@@@CCA@@@CDLOOOOOOOJMgugfheefffffffffffffffffffffffeig_ezdJA@@@@@@@@@@@@@@@DFFFGGGGECfugfheefffffffffffffffffffffffeig_ez}_JA@@@@@@@@@@@@@@@ABBBBBBBD@dugfheefffffffffffffffffffffffeig_ez}^IA@@@@@@@@@@@@@@@@BBBBBBBC@dugfheefffffffffffffffffffffffeig_ez}[F@@@@@@@@@@@@@@@@@@@@@@@@C@dugfheefffffffffffffffffffffffeig_ez}ZD@@@@@@@@@@@@@@@@????????C@dugfheefffffffffffffffffffffffeig_ezWB@@@@@@@@@@@@@@@@@@@@@@@@C@dugfheefffffffffffffffffffffffeig_ezV@?@@@@@@@@@@@@@@@@@@@@@@@C@dugfheefffffffffffffffffffffffeig_ezU??@@@@@@@@@@@@@@@@@@@@@@@C@dugfheefffffffffffffffffffffffeig_ez}~||}~|PB@@@@@@@@@@@@@@@@@@@@@@@@C?cugfheefffffffffffffffffffffffeig_ez~}}~xMC@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez~}xMC@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez}}}}xMB@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez}}}~~wMB@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez~}|~|~wMB@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez}|~}vMA@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez}vKA@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffeig_ez}pJ@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffggieax}mK@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffgfjg`u~~}lI@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffgdif_u}|}}|~lI@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheeffffffffffffffffffffffffchf_u~~lI@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheeffffffffffffffffeffffffecge`v~~~~lI@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffdbgd_v}~~}~lH?@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheeffffffffffffffffffffffecafc_v}}}kH?@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffffffffffcadc_u}~~}eA@@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheeffffffffffffffffeeeeecc`_fe`v~}d@A@@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffffffffffeeeccccc`_fd`w}d@B@@@@@@@@@@@@@@@@@@@@@@@@B?cugfhedfffffffeeeeeeddeeeccccc`_fd`w}}}f@C@@@@@@@@@@@@@@@@@@@@@@@@B?cugfhedfffffeeecddccccecccccbb`_fd`w~}}}~||~}gCEA@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffdccccccccccccbbb`_fd`w~}~~}}}|~}gDEA@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffccccccccccccbb``__fd`w}}}}}}~~}gDFA@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffcbbbbbbbcccbb```__fd`w}hDFA@@@@@@@@@@@@@@@@@@@@@@@B?cugfheefffffffcbbbbbbbcccbb``a__fd`w}qLE@@@@@@@@@@@@@@@@@@@@@@@@B?cvffgdccccccddbbbbbbbbbbbbbbbb__fd`w}uMC@@@@@@@@@@@@@@@@@@@@@@@@B?cufegcccccccccbbbbbbbbbbbbbbbb`_fd`w}uMC@@@@@@@@@@@@@@@@@@@@@@@@B?cufegcccccccccbbbbbbbbbbbbbbbb`_fd`w}vMC@@@@@@@@@@@@@@@@@@@@@@@@B?cuffgcccccccccbbbbbbbbbbbbbbbb`_fd`w}xMB@@@@@@@@@@@@@@@@@@@@@@@@B?cvffgcccccccccbbbbbbbbbbbbbbbb`_fd`w}yNB@@@@@@@@@@@@@@@@@@@@@@@@B?cuffgcccccccccbbbbbbbbbbbbbbbb`_fd`w}yNB@@@@@@@@@@@@@@@@@@@@@@@@B?cuffgdccccccccbbbbbbbbbbbbbbbb`_fd`w}yNB@@@@@@@@@@@@@@@@@@@@@@@@B?cuffgdccccccccbbbbbbbbbbbbbbbb`_fd`w~N>????????@@@@@@@@AAA@AAAAB>ducacbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`wN<>>>>????@@@@@@@@@@@@@@@@B>dwc_cbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`wP<<==>==>>????????@@@@@@@@B=cwc_cbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`wS<========>>>>>>>>????????A=cwc_cbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`wV?<<<<<<<<<<<<<<<<========A>dwc_cbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`wX?;;;;;;;;<<<<<<<<<<<<<<<cwc_cbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`w{meiggs]@;:::::::;;;;;;;;<<<<<<<cwc_cbbbbbbbbbbbbbbbbbbbbbbbbb`_fd`wscX_^\g|lC99999999;;;;;;;;::::::::4>>>>>>?@@@@@@@>>>>>>>>Ilwffhdefffffffdccccccccdeeedccc`ccbyyg`^db^fusK=;;;;;;;<<<<<<<<;;;;;;;;4<]}scbb\________^^^^^^^^^____^]\a`ccbyl_\a__gxzZ@;;;;;;;;<<<<<<<;;;;;;;;=C`}ugghcefffffffggggggggffgffcccb`dcbyvh`aaeppmonnnnnnnnnnnnnnnnnnoooolk{|}{|}}}}}}}~||}}}ywtg`ccbyypilt}k`ccby}l`ccbykaca`y~iaca_y~iaca_y~iaca_y~iaca_y~iaca_y~iaca_y}~iaca_y}~iaca_y}~iaca_y}~iaca_y}~iaca_y}~i`ca_y}~i`ca_y}~i`ca_y}~i`ca_yl_cbbywj_ccbywcXMNTJMOOOOOOOOOOOOOOOXWlrtuppqqqqqqqqqqqqqqqppsusqnle_ccbywU@;??37;;;;;;;;;;;;;;;@@]wgff_cdddddddddddddddaadedccab_ccbyV;:@=689999999999999999:Ysccd^_```````````````^^`aaa_`b_ccby_@?A>@?===============<<]qcegccccccccccccccccca`bbbccbc_ccbygD=<7@=<<<<<<<<<<<<<<<=>^tefieddddddddddddddddccccccccc_ccbyuMB83A?===============>=]udcfbbcccccccccccccccccbb``aab_ccbyyL=:5@><<<<<<<========>;`vcd`bbbbbbbbbbbbbbbbbbbbbbbbbb_ec`w}M<;8>=<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_vU@;9==<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_v_D=;<<<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_vjI<>:;<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_vuM>A9:<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_v}Q@B7:<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_vS@C7:<<<<<<<========>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba`ec_vnG7<><9:=<<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feaw}R<=?=:;>@>:;<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feawgI<><:<=?<79<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feaw}[D;989=><7;<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feawqS@966:==;<<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feawnTI@99<=<<<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feawvgSD=;===<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feawgP@<;<<<<<<<<<>:bxbf_bbbbbbbbbbbbbbbbbbbbbbbbba_feawp`RIDCA@?<<;1>>>>>>A;:?A:=I}{{zzxyxz}~z{}}}}}}}}{{}}}}{zzz{{}|}~}|||||||||||||||||||||||||||||||||||||||||||||||||||||||~}}}|}||||||||||||||||~}|}{{{{z{{z}~|{{{{}zzzzzyyyxxxwxxwwvvvvvvvvpsnthGAAAAA?>===>@@@@@@@>=======?;:?A8:8>?8==??=====<<<<<<<<========>:8>>7;GȢj__bdglpsuuuuvx{}{z{{}|}~}|||||||||||||||||||||||||||||||||||||||||||||||||||||||~}}~}{z{||||||||||||||||z{~xqnqtqppppqqqqqqqqppppqqqqqqqpuosgF>?================<<<<<<<<>87=>7;G˧cYZZ\]_cddfgikntx{zz{{}|}~}|||||||||||||||||||||||||||||||||||||||||||||||||||||||~}~~}|{{{}||||||||||||||||xolmqonnnpqqqqpppppqqqqqqqqqqnqkqfF>?<;;;<<<<<=======<<<<<<<<=87=>7;G˾̣aZ[XXXXWWWX[\_flsvzz{{}|}~}|||||||||||||||||||||||||||||||||||||||||||||||||||||||~~}~~}}}{{}}}}}}}}x{{x{}wpnpqqppqpppppppppppppppppppposnsgD==========================>:8>>8;IʥhXXXXXWWWWUUWZ^abenv}|z{}}}|||}}||||||}}||||||||||||||||||||||||||||||||||||||||}|~}~~~~}}{{}}}}}}}}xz{y}uppqqppppppppppppppppppppppppptntgF==========================?:8>?8?8?8?8?8?8?8>==<<<<=======>:8>?8:8>?8==========>:8>?8>=======<>:8>?8>>===<<>:8>?8>==<<>:8>?8==<<>:8>?8==<<>:8>?8:8>?8===?:8>?8===?:8>?8===?:8>?8===?:8>?8===?:8>?8===?:8>?8===?:8>?8===>:8>?8=<=>:8>?8:8>?8:8>?8=<==>:8>?8:8>?8?8=<<==?:8>?8=======>:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8===============>:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8?8:8>?8:8>?8:8>?8:8>?8:8>?8:8>?8A<::::::::::::::::::::::: +#include +#include +#include +#include + +// ITU-R BT.601 +// #define RGB_TO_Y(R, G, B) ((( 66 * (R) + 129 * (G) + 25 * (B)+128) >> 8)+16) +// #define RGB_TO_U(R, G, B) (((-38 * (R) - 74 * (G) + 112 * (B)+128) >> 8)+128) +// #define RGB_TO_V(R, G, B) (((112 * (R) - 94 * (G) - 18 * (B)+128) >> 8)+128) + +// ITU-R BT.709 +#define RGB_TO_Y(R, G, B) (((47 * (R) + 157 * (G) + 16 * (B) + 128) >> 8) + 16) +#define RGB_TO_U(R, G, B) (((-26 * (R)-87 * (G) + 112 * (B) + 128) >> 8) + 128) +#define RGB_TO_V(R, G, B) (((112 * (R)-102 * (G)-10 * (B) + 128) >> 8) + 128) + +INSTANCE_IMP(VideoStackManager) + +Param::~Param() +{ + VideoStackManager::Instance().unrefChannel( + id, width, height, pixfmt); +} + +Channel::Channel(const std::string& id, int width, int height, AVPixelFormat pixfmt) + : _id(id) + , _width(width) + , _height(height) + , _pixfmt(pixfmt) +{ + _tmp = std::make_shared(); + + _tmp->get()->width = _width; + _tmp->get()->height = _height; + _tmp->get()->format = _pixfmt; + + av_frame_get_buffer(_tmp->get(), 32); + + memset(_tmp->get()->data[0], 0, _tmp->get()->linesize[0] * _height); + memset(_tmp->get()->data[1], 0, _tmp->get()->linesize[1] * _height / 2); + memset(_tmp->get()->data[2], 0, _tmp->get()->linesize[2] * _height / 2); + + auto frame = VideoStackManager::Instance().getBgImg(); + _sws = std::make_shared(_pixfmt, _width, _height); + + _tmp = _sws->inputFrame(frame); +} + +void Channel::addParam(const std::weak_ptr& p) +{ + std::lock_guard lock(_mx); + _params.push_back(p); +} + +void Channel::onFrame(const mediakit::FFmpegFrame::Ptr& frame) +{ + std::weak_ptr weakSelf = shared_from_this(); + // toolkit::WorkThreadPool::Instance().getFirstPoller()->async([weakSelf, frame]() { + auto self = weakSelf.lock(); + if (!self) { + return; + } + self->_tmp = self->_sws->inputFrame(frame); + + self->forEachParam([self](const Param::Ptr& p) { self->fillBuffer(p); }); + // }); +} + +void Channel::forEachParam(const std::function& func) +{ + for (auto& wp : _params) { + if (auto sp = wp.lock()) { + func(sp); + } + } +} + +void Channel::fillBuffer(const Param::Ptr& p) +{ + if (auto buf = p->weak_buf.lock()) { + copyData(buf, p); + } +} + +void Channel::copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p) +{ + + switch (p->pixfmt) { + case AV_PIX_FMT_YUV420P: { + for (int i = 0; i < p->height; i++) { + memcpy(buf->get()->data[0] + buf->get()->linesize[0] * (i + p->posY) + p->posX, + _tmp->get()->data[0] + _tmp->get()->linesize[0] * i, + _tmp->get()->width); + } + //确保height为奇数时,也能正确的复制到最后一行uv数据 + for (int i = 0; i < (p->height + 1) / 2; i++) { + // U平面 + memcpy(buf->get()->data[1] + buf->get()->linesize[1] * (i + p->posY / 2) + p->posX / 2, + _tmp->get()->data[1] + _tmp->get()->linesize[1] * i, + _tmp->get()->width / 2); + + // V平面 + memcpy(buf->get()->data[2] + buf->get()->linesize[2] * (i + p->posY / 2) + p->posX / 2, + _tmp->get()->data[2] + _tmp->get()->linesize[2] * i, + _tmp->get()->width / 2); + } + break; + } + case AV_PIX_FMT_NV12: { + //TODO: 待实现 + break; + } + + default: + WarnL << "No support pixformat: " << av_get_pix_fmt_name(p->pixfmt); + break; + } +} +void StackPlayer::addChannel(const std::weak_ptr& chn) +{ + std::lock_guard lock(_mx); + _channels.push_back(chn); +} + +void StackPlayer::play() +{ + + auto url = _url; + //创建拉流 解码对象 + _player = std::make_shared(); + std::weak_ptr weakPlayer = _player; + + std::weak_ptr weakSelf = shared_from_this(); + + (*_player)[mediakit::Client::kWaitTrackReady] = false; + (*_player)[mediakit::Client::kRtpType] = mediakit::Rtsp::RTP_TCP; + + _player->setOnPlayResult([weakPlayer, weakSelf, url](const toolkit::SockException& ex) mutable { + TraceL << "StackPlayer: " << url << " OnPlayResult: " << ex.what(); + auto strongPlayer = weakPlayer.lock(); + if (!strongPlayer) { + return; + } + auto self = weakSelf.lock(); + if (!self) { + return; + } + + if (!ex) { + // 取消定时器 + self->_timer.reset(); + self->_failedCount = 0; + + } else { + self->onDisconnect(); + self->rePlay(url); + } + + auto videoTrack = std::dynamic_pointer_cast(strongPlayer->getTrack(mediakit::TrackVideo, false)); + //auto audioTrack = std::dynamic_pointer_cast(strongPlayer->getTrack(mediakit::TrackAudio, false)); + + if (videoTrack) { + //TODO:添加使用显卡还是cpu解码的判断逻辑 + //auto decoder = std::make_shared(videoTrack, 1, std::vector{ "hevc_cuvid", "h264_cuvid"}); + auto decoder = std::make_shared(videoTrack, 0, std::vector { "h264", "hevc" }); + + decoder->setOnDecode([weakSelf](const mediakit::FFmpegFrame::Ptr& frame) mutable { + auto self = weakSelf.lock(); + if (!self) { + return; + } + + self->onFrame(frame); + }); + + videoTrack->addDelegate((std::function)[decoder](const mediakit::Frame::Ptr& frame) { + return decoder->inputFrame(frame, false, true); + }); + } + }); + + _player->setOnShutdown([weakPlayer, url, weakSelf](const toolkit::SockException& ex) { + TraceL << "StackPlayer: " << url << " OnShutdown: " << ex.what(); + auto strongPlayer = weakPlayer.lock(); + if (!strongPlayer) { + return; + } + + auto self = weakSelf.lock(); + if (!self) { + return; + } + + self->onDisconnect(); + + self->rePlay(url); + }); + + _player->play(url); +} + +void StackPlayer::onFrame(const mediakit::FFmpegFrame::Ptr& frame) +{ + std::lock_guard lock(_mx); + for (auto& weak_chn : _channels) { + if (auto chn = weak_chn.lock()) { + chn->onFrame(frame); + } + } +} + +void StackPlayer::onDisconnect() +{ + std::lock_guard lock(_mx); + for (auto& weak_chn : _channels) { + if (auto chn = weak_chn.lock()) { + auto frame = VideoStackManager::Instance().getBgImg(); + chn->onFrame(frame); + } + } +} + +void StackPlayer::rePlay(const std::string& url) +{ + _failedCount++; + auto delay = MAX(2 * 1000, MIN(_failedCount * 3 * 1000, 60 * 1000)); //步进延迟 重试间隔 + std::weak_ptr weakSelf = shared_from_this(); + _timer = std::make_shared( + delay / 1000.0f, [weakSelf, url]() { + auto self = weakSelf.lock(); + if (!self) { + } + WarnL << "replay [" << self->_failedCount << "]:" << url; + self->_player->play(url); + return false; + }, + nullptr); +} + +VideoStack::VideoStack(const std::string& id, int width, int height, AVPixelFormat pixfmt, float fps, int bitRate) + : _id(id) + , _width(width) + , _height(height) + , _pixfmt(pixfmt) + , _fps(fps) + , _bitRate(bitRate) +{ + + _buffer = std::make_shared(); + + _buffer->get()->width = _width; + _buffer->get()->height = _height; + _buffer->get()->format = _pixfmt; + + av_frame_get_buffer(_buffer->get(), 32); + + _dev = std::make_shared(mediakit::MediaTuple { DEFAULT_VHOST, "live", _id }); + + mediakit::VideoInfo info; + info.codecId = mediakit::CodecH264; + info.iWidth = _width; + info.iHeight = _height; + info.iFrameRate = _fps; + info.iBitRate = _bitRate; + + _dev->initVideo(info); + //dev->initAudio(); //TODO:音频 + _dev->addTrackCompleted(); + + _isExit = false; +} + +VideoStack::~VideoStack() +{ + _isExit = true; + if (_thread.joinable()) { + _thread.join(); + } +} + +void VideoStack::setParam(const Params& params) +{ + if (_params) { + for (auto& p : (*_params)) { + if (!p) + continue; + p->weak_buf.reset(); + } + } + + initBgColor(); + for (auto& p : (*params)) { + if (!p) + continue; + p->weak_buf = _buffer; + if (auto chn = p->weak_chn.lock()) { + chn->addParam(p); + chn->fillBuffer(p); + } + } + _params = params; +} + +void VideoStack::start() +{ + _thread = std::thread([&]() { + uint64_t pts = 0; + int frameInterval = 1000 / _fps; + auto lastEncTP = std::chrono::steady_clock::now(); + while (!_isExit) { + if (std::chrono::steady_clock::now() - lastEncTP > std::chrono::milliseconds(frameInterval)) { + lastEncTP = std::chrono::steady_clock::now(); + + _dev->inputYUV((char**)_buffer->get()->data, _buffer->get()->linesize, pts); + pts += frameInterval; + } + } + }); +} + +void VideoStack::initBgColor() +{ + //填充底色 + auto R = 20; + auto G = 20; + auto B = 20; + + double Y = RGB_TO_Y(R, G, B); + double U = RGB_TO_U(R, G, B); + double V = RGB_TO_V(R, G, B); + + memset(_buffer->get()->data[0], Y, _buffer->get()->linesize[0] * _height); + memset(_buffer->get()->data[1], U, _buffer->get()->linesize[1] * _height / 2); + memset(_buffer->get()->data[2], V, _buffer->get()->linesize[2] * _height / 2); +} + +Channel::Ptr VideoStackManager::getChannel(const std::string& id, + int width, + int height, + AVPixelFormat pixfmt) +{ + + std::lock_guard lock(_mx); + auto key = id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt); + auto it = _channelMap.find(key); + if (it != _channelMap.end()) { + return it->second->acquire(); + } + + return createChannel(id, width, height, pixfmt); +} + +void VideoStackManager::unrefChannel(const std::string& id, + int width, + int height, + AVPixelFormat pixfmt) +{ + + std::lock_guard lock(_mx); + auto key = id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt); + auto chn_it = _channelMap.find(key); + if (chn_it != _channelMap.end() && chn_it->second->dispose()) { + _channelMap.erase(chn_it); + + auto player_it = _playerMap.find(id); + if (player_it != _playerMap.end() && player_it->second->dispose()) { + _playerMap.erase(player_it); + } + } +} + +int VideoStackManager::startVideoStack(const Json::Value& json) +{ + + std::string id; + int width, height; + auto params = parseParams(json, id, width, height); + + if (!params) { + ErrorL << "Videostack parse params failed!"; + return -1; + } + + auto stack = std::make_shared(id, width, height); + + for (auto& p : (*params)) { + if (!p) + continue; + p->weak_chn = getChannel(p->id, p->width, p->height, p->pixfmt); + } + + stack->setParam(params); + stack->start(); + + std::lock_guard lock(_mx); + _stackMap[id] = stack; + return 0; +} + +int VideoStackManager::resetVideoStack(const Json::Value& json) +{ + std::string id; + int width, height; + auto params = parseParams(json, id, width, height); + + if (!params) { + return -1; + } + + VideoStack::Ptr stack; + { + std::lock_guard lock(_mx); + auto it = _stackMap.find(id); + if (it == _stackMap.end()) { + return -2; + } + stack = it->second; + } + + for (auto& p : (*params)) { + if (!p) + continue; + p->weak_chn = getChannel(p->id, p->width, p->height, p->pixfmt); + } + + stack->setParam(params); + return 0; +} + +int VideoStackManager::stopVideoStack(const std::string& id) +{ + std::lock_guard lock(_mx); + auto it = _stackMap.find(id); + if (it != _stackMap.end()) { + _stackMap.erase(it); + return 0; + } + return -1; +} + +mediakit::FFmpegFrame::Ptr VideoStackManager::getBgImg() +{ + return _bgImg; +} + +Params VideoStackManager::parseParams(const Json::Value& json, + std::string& id, + int& width, + int& height) +{ + try { + id = json["id"].asString(); + + width = json["width"].asInt(); + height = json["height"].asInt(); + + int rows = json["row"].asInt(); //堆叠行数 + int cols = json["col"].asInt(); //堆叠列数 + float gapv = json["gapv"].asFloat(); //垂直间距 + float gaph = json["gaph"].asFloat(); //水平间距 + + //单个间距 + int gaphPix = static_cast(std::round(width * gaph)); + int gapvPix = static_cast(std::round(height * gapv)); + + // 根据间距计算格子宽高 + int gridWidth = cols > 1 ? (width - gaphPix * (cols - 1)) / cols : width; + int gridHeight = rows > 1 ? (height - gapvPix * (rows - 1)) / rows : height; + + auto params = std::make_shared>(rows * cols); + + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + std::string url = json["url"][row][col].asString(); + + auto param = std::make_shared(); + param->posX = gridWidth * col + col * gaphPix; + param->posY = gridHeight * row + row * gapvPix; + param->width = gridWidth; + param->height = gridHeight; + param->id = url; + + (*params)[row * cols + col] = param; + } + } + + //判断是否需要合并格子 (焦点屏) + if (!json["span"].empty() && json.isMember("span")) { + for (const auto& subArray : json["span"]) { + if (!subArray.isArray() || subArray.size() != 2) { + throw Json::LogicError("Incorrect 'span' sub-array format in JSON"); + } + std::array mergePos; + int index = 0; + + for (const auto& innerArray : subArray) { + if (!innerArray.isArray() || innerArray.size() != 2) { + throw Json::LogicError("Incorrect 'span' inner-array format in JSON"); + } + for (const auto& number : innerArray) { + if (index < mergePos.size()) { + mergePos[index++] = number.asInt(); + } + } + } + + for (int i = mergePos[0]; i <= mergePos[2]; i++) { + for (int j = mergePos[1]; j <= mergePos[3]; j++) { + if (i == mergePos[0] && j == mergePos[1]) { + (*params)[i * cols + j]->width = (mergePos[3] - mergePos[1] + 1) * gridWidth + (mergePos[3] - mergePos[1]) * gapvPix; + (*params)[i * cols + j]->height = (mergePos[2] - mergePos[0] + 1) * gridHeight + (mergePos[2] - mergePos[0]) * gaphPix; + } else { + (*params)[i * cols + j] = nullptr; + } + } + } + } + } + return params; + } catch (const std::exception& e) { + ErrorL << "Videostack parse params failed! " << e.what(); + return nullptr; + } +} + +bool VideoStackManager::loadBgImg(const std::string& path) +{ + _bgImg = std::make_shared(); + + _bgImg->get()->width = 1280; + _bgImg->get()->height = 720; + _bgImg->get()->format = AV_PIX_FMT_YUV420P; + + av_frame_get_buffer(_bgImg->get(), 32); + + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + return false; + } + + file.read((char*)_bgImg->get()->data[0], _bgImg->get()->linesize[0] * _bgImg->get()->height); // Y + file.read((char*)_bgImg->get()->data[1], _bgImg->get()->linesize[1] * _bgImg->get()->height / 2); // U + file.read((char*)_bgImg->get()->data[2], _bgImg->get()->linesize[2] * _bgImg->get()->height / 2); // V + return true; +} + +Channel::Ptr VideoStackManager::createChannel(const std::string& id, + int width, + int height, + AVPixelFormat pixfmt) +{ + + std::lock_guard lock(_mx); + StackPlayer::Ptr player; + auto it = _playerMap.find(id); + if (it != _playerMap.end()) { + player = it->second->acquire(); + } else { + player = createPlayer(id); + } + + auto refChn = std::make_shared>(std::make_shared(id, width, height, pixfmt)); + auto chn = refChn->acquire(); + player->addChannel(chn); + + _channelMap[id + std::to_string(width) + std::to_string(height) + std::to_string(pixfmt)] = refChn; + return chn; +} + +StackPlayer::Ptr VideoStackManager::createPlayer(const std::string& id) +{ + std::lock_guard lock(_mx); + auto refPlayer = std::make_shared>(std::make_shared(id)); + _playerMap[id] = refPlayer; + + auto player = refPlayer->acquire(); + if (!id.empty()) { + player->play(); + } + + return player; +} +#endif \ No newline at end of file diff --git a/server/VideoStack.h b/server/VideoStack.h new file mode 100644 index 00000000..d4378ab3 --- /dev/null +++ b/server/VideoStack.h @@ -0,0 +1,207 @@ +#pragma once +#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG) +#include "Codec/Transcode.h" +#include "Common/Device.h" +#include "Player/MediaPlayer.h" +#include "json/json.h" +#include +template +class RefWrapper { + public: + using Ptr = std::shared_ptr>; + + template + explicit RefWrapper(Args&&... args) + : _rc(0) + , _entity(std::forward(args)...) + { + } + + T acquire() + { + ++_rc; + return _entity; + } + + bool dispose() { return --_rc <= 0; } + + private: + T _entity; + std::atomic _rc; +}; + +class Channel; + +struct Param { + using Ptr = std::shared_ptr; + + int posX = 0; + int posY = 0; + int width = 0; + int height = 0; + AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P; + std::string id {}; + + // runtime + std::weak_ptr weak_chn; + std::weak_ptr weak_buf; + + ~Param(); +}; + +using Params = std::shared_ptr>; + +class Channel : public std::enable_shared_from_this { + public: + using Ptr = std::shared_ptr; + + Channel(const std::string& id, int width, int height, AVPixelFormat pixfmt); + + void addParam(const std::weak_ptr& p); + + void onFrame(const mediakit::FFmpegFrame::Ptr& frame); + + void fillBuffer(const Param::Ptr& p); + + protected: + void forEachParam(const std::function& func); + + void copyData(const mediakit::FFmpegFrame::Ptr& buf, const Param::Ptr& p); + + private: + std::string _id; + int _width; + int _height; + AVPixelFormat _pixfmt; + + mediakit::FFmpegFrame::Ptr _tmp; + + std::recursive_mutex _mx; + std::vector> _params; + + mediakit::FFmpegSws::Ptr _sws; +}; + +class StackPlayer : public std::enable_shared_from_this { + public: + using Ptr = std::shared_ptr; + + StackPlayer(const std::string& url) + : _url(url) + { + } + + void addChannel(const std::weak_ptr& chn); + + void play(); + + void onFrame(const mediakit::FFmpegFrame::Ptr& frame); + + void onDisconnect(); + + protected: + void rePlay(const std::string& url); + + private: + std::string _url; + mediakit::MediaPlayer::Ptr _player; + + //用于断线重连 + toolkit::Timer::Ptr _timer; + int _failedCount = 0; + + std::recursive_mutex _mx; + std::vector> _channels; +}; + +class VideoStack { + public: + using Ptr = std::shared_ptr; + + VideoStack(const std::string& url, + int width = 1920, + int height = 1080, + AVPixelFormat pixfmt = AV_PIX_FMT_YUV420P, + float fps = 25.0, + int bitRate = 2 * 1024 * 1024); + + ~VideoStack(); + + void setParam(const Params& params); + + void start(); + + protected: + void initBgColor(); + + public: + Params _params; + + mediakit::FFmpegFrame::Ptr _buffer; + + private: + std::string _id; + int _width; + int _height; + AVPixelFormat _pixfmt; + float _fps; + int _bitRate; + + mediakit::DevChannel::Ptr _dev; + + bool _isExit; + + std::thread _thread; +}; + +class VideoStackManager { + public: + static VideoStackManager& Instance(); + + Channel::Ptr getChannel(const std::string& id, + int width, + int height, + AVPixelFormat pixfmt); + + void unrefChannel(const std::string& id, + int width, + int height, + AVPixelFormat pixfmt); + + int startVideoStack(const Json::Value& json); + + int resetVideoStack(const Json::Value& json); + + int stopVideoStack(const std::string& id); + + bool loadBgImg(const std::string& path); + + mediakit::FFmpegFrame::Ptr getBgImg(); + + protected: + Params parseParams(const Json::Value& json, + std::string& id, + int& width, + int& height); + + protected: + Channel::Ptr createChannel(const std::string& id, + int width, + int height, + AVPixelFormat pixfmt); + + StackPlayer::Ptr createPlayer(const std::string& id); + + private: + mediakit::FFmpegFrame::Ptr _bgImg; + + private: + std::recursive_mutex _mx; + + std::unordered_map _stackMap; + + std::unordered_map::Ptr> _channelMap; + + std::unordered_map::Ptr> _playerMap; +}; +#endif \ No newline at end of file diff --git a/server/WebApi.cpp b/server/WebApi.cpp index d0ab2e43..846f280a 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -62,6 +62,10 @@ #include "version.h" #endif +#if defined(ENABLE_X264) && defined (ENABLE_FFMPEG) +#include "VideoStack.h" +#endif + using namespace std; using namespace Json; using namespace toolkit; @@ -1925,6 +1929,36 @@ void installWebApi() { invoker(401, StrCaseMap {}, "None http access event listener"); } }); + +#if defined(ENABLE_X264) && defined(ENABLE_FFMPEG) + VideoStackManager::Instance().loadBgImg("novideo.yuv"); + NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) { + auto id = sender.getMediaTuple().stream; + VideoStackManager::Instance().stopVideoStack(id); + InfoL << "VideoStack: " << id <<" stop"; + }); + + api_regist("/index/api/stack/start", [](API_ARGS_JSON_ASYNC) { + CHECK_SECRET(); + auto ret = VideoStackManager::Instance().startVideoStack(allArgs.getArgs()); + if (!ret) { + invoker(200, headerOut, "success"); + } else { + invoker(200, headerOut, "failed"); + } + }); + + api_regist("/index/api/stack/stop", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); + CHECK_ARGS("id"); + auto ret = VideoStackManager::Instance().stopVideoStack(allArgs["id"]); + if (!ret) { + invoker(200, headerOut, "success"); + } else { + invoker(200, headerOut, "failed"); + } + }); +#endif } void unInstallWebApi(){ From 5a137f8b8ed3aafce7f05dd0b0291554738a14ae Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 17 Mar 2024 10:28:10 +0800 Subject: [PATCH 20/20] Update submodule --- 3rdpart/media-server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdpart/media-server b/3rdpart/media-server index 8bc32a51..527c0f51 160000 --- a/3rdpart/media-server +++ b/3rdpart/media-server @@ -1 +1 @@ -Subproject commit 8bc32a516b279414f749d0dead8bdc2837d3c527 +Subproject commit 527c0f5117b489fda78fcd123d446370ddd9ec9a