From 5165ac4f74d14d03fc56ed4ecf4e35068fb623dc Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Tue, 25 Nov 2025 22:26:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=94=AF=E6=8C=81=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E5=8F=8C=E5=90=91=E5=AF=B9=E8=AE=B2webrtc=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/Sdp.h | 20 ++--- webrtc/WebRtcTalk.cpp | 171 +++++++++++++++++++++++++++++++++++++ webrtc/WebRtcTalk.h | 56 ++++++++++++ webrtc/WebRtcTransport.cpp | 8 +- www/webrtc/index.html | 3 +- 5 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 webrtc/WebRtcTalk.cpp create mode 100644 webrtc/WebRtcTalk.h diff --git a/webrtc/Sdp.h b/webrtc/Sdp.h index 3eacfe7c..8139f645 100644 --- a/webrtc/Sdp.h +++ b/webrtc/Sdp.h @@ -55,36 +55,36 @@ namespace mediakit { // k=* (encryption key) // a=* (zero or more media attribute lines) -enum class RtpDirection { +enum class RtpDirection : int8_t { invalid = -1, // 只发送 [AUTO-TRANSLATED:d7e7fdb7] // Send only - sendonly, + sendonly = 1 << 0, // 只接收 [AUTO-TRANSLATED:f75ca789] // Receive only - recvonly, + recvonly = 1 << 1, // 同时发送接收 [AUTO-TRANSLATED:7f900ba1] // Send and receive simultaneously - sendrecv, + sendrecv = sendonly | recvonly, // 禁止发送数据 [AUTO-TRANSLATED:6045b47e] // Prohibit sending data - inactive + inactive = 0 }; -enum class DtlsRole { +enum class DtlsRole : int8_t { invalid = -1, // 客户端 [AUTO-TRANSLATED:915417a2] // Client - active, + active = 1 << 0, // 服务端 [AUTO-TRANSLATED:03a80b18] // Server - passive, + passive = 1 << 1, // 既可作做客户端也可以做服务端 [AUTO-TRANSLATED:5ab1162e] // Can be used as both client and server - actpass, + actpass = active | passive, }; -enum class SdpType { invalid = -1, offer, answer }; +enum class SdpType : int8_t { invalid = -1, offer, answer }; DtlsRole getDtlsRole(const std::string &str); const char *getDtlsRoleString(DtlsRole role); diff --git a/webrtc/WebRtcTalk.cpp b/webrtc/WebRtcTalk.cpp new file mode 100644 index 00000000..1c4dc4ce --- /dev/null +++ b/webrtc/WebRtcTalk.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcTalk.h" + +#include "Util/base64.h" +#include "Common/config.h" +#include "Extension/Factory.h" +#include "Common/MultiMediaSourceMuxer.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +WebRtcTalk::Ptr WebRtcTalk::create( + const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info, WebRtcTransport::Role role, + WebRtcTransport::SignalingProtocols signaling_protocols) { + WebRtcTalk::Ptr ret(new WebRtcTalk(poller, src, info), [](WebRtcTalk *ptr) { + ptr->onDestory(); + delete ptr; + }); + ret->setRole(role); + ret->setSignalingProtocols(signaling_protocols); + ret->onCreate(); + return ret; +} + +WebRtcTalk::WebRtcTalk(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info) + : WebRtcTransportImp(poller) { + _media_info = info; + _play_src = src; + CHECK(src); + _demuxer = std::make_shared(); +} + +void WebRtcTalk::onStartWebRTC() { + auto playSrc = _play_src.lock(); + if (!playSrc) { + onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown")); + return; + } + WebRtcTransportImp::onStartWebRTC(); + // 不支持simulcast + CHECK(!_answer_sdp->supportSimulcast()); + auto sdp = _answer_sdp->toRtspSdp(); + _demuxer->loadSdp(sdp); + auto audio_track = _demuxer->getTrack(TrackAudio, false); + // 必须包含音频track + CHECK(audio_track); + audio_track->addDelegate([this](const Frame::Ptr &frame) { + // 发送对讲语音rtp流 + _sender->inputFrame(frame); + return true; + }); + + MediaSourceEvent::SendRtpArgs args; + args.con_type = MediaSourceEvent::SendRtpArgs::kVoiceTalk; + args.recv_stream_vhost = playSrc->getMediaTuple().vhost; + args.recv_stream_app = playSrc->getMediaTuple().app; + args.recv_stream_id = playSrc->getMediaTuple().stream; + auto url_args = Parser::parseArgs(_media_info.params); + args.data_type = static_cast(atoi(url_args["data_type"].data())); + args.only_audio = true; + args.pt = static_cast(atoi(url_args["pt"].data())); + args.ssrc = url_args["ssrc"]; + + std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _sender = std::make_shared(getPoller()); + _sender->startSend(*(playSrc->getMuxer()), args, [weak_self](uint16_t local_port, const SockException &ex) { + if (!ex) { + return; + } + if (auto strong_self = weak_self.lock()) { + strong_self->onShutdown(ex); + } + }); + + _sender->addTrack(audio_track); + _sender->addTrackCompleted(); + + if (canSendRtp()) { + playSrc->pause(false); + _reader = playSrc->getRing()->attach(getPoller(), true); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + weak_ptr weak_session = static_pointer_cast(getSession()); + _reader->setGetInfoCB([weak_session]() { + Any ret; + ret.set(static_pointer_cast(weak_session.lock())); + return ret; + }); + _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + size_t i = 0; + pkt->for_each([&](const RtpPacket::Ptr &rtp) { strong_self->onSendRtp(rtp, ++i == pkt->size()); }); + }); + _reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); + }); + + _reader->setMessageCB([weak_self](const toolkit::Any &data) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (data.is()) { + auto &buffer = data.get(); + // PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81] + // PPID 51: Text string + // PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e] + // PPID 53: Binary + strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size()); + } else { + WarnL << "Send unknown message type to webrtc player: " << data.type_name(); + } + }); + } +} +void WebRtcTalk::onDestory() { + auto duration = getDuration(); + auto bytes_usage = getBytesUsage(); + // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] + // Traffic statistics event broadcast + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + auto session = getSession(); + if (_reader && session) { + WarnL << "RTC对讲(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration; + if (bytes_usage >= iFlowThreshold * 1024) { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *session); + } + } + WebRtcTransportImp::onDestory(); +} + +void WebRtcTalk::onRtcConfigure(RtcConfigure &configure) const { + WebRtcTransportImp::onRtcConfigure(configure); + auto playSrc = _play_src.lock(); + if (playSrc) { + configure.setPlayRtspInfo(playSrc->getSdp()); + } + + // 不接收视频 + configure.video.direction = static_cast(static_cast(configure.video.direction) & ~static_cast(RtpDirection::recvonly)); + // 开启音频接收 + configure.audio.direction = static_cast(static_cast(configure.audio.direction) | static_cast(RtpDirection::recvonly)); +} + +void WebRtcTalk::onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) { + // rtp解析为音频,视频丢弃 + if (rtp->type == TrackAudio) { + _demuxer->inputRtp(rtp); + } +} + + +} // namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcTalk.h b/webrtc/WebRtcTalk.h new file mode 100644 index 00000000..c6a3bc05 --- /dev/null +++ b/webrtc/WebRtcTalk.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_TALK_H +#define ZLMEDIAKIT_WEBRTC_TALK_H + +#include "WebRtcTransport.h" +#include "Rtsp/RtspMediaSource.h" +#include "Rtsp/RtspDemuxer.h" +#include "Rtp/RtpSender.h" + +namespace mediakit { + +class WebRtcTalk : public WebRtcTransportImp { +public: + using Ptr = std::shared_ptr; + static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info, + WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols); + +protected: + ///////WebRtcTransportImp override/////// + void onStartWebRTC() override; + void onDestory() override; + void onRtcConfigure(RtcConfigure &configure) const override; + void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; + +private: + WebRtcTalk(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); + +private: + // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] + // Media related metadata + MediaInfo _media_info; + // 播放的rtsp源 [AUTO-TRANSLATED:9963eed1] + // Playing rtsp source + std::weak_ptr _play_src; + + // 播放rtsp源的reader对象 [AUTO-TRANSLATED:7b305055] + // Reader object for playing rtsp source + RtspMediaSource::RingType::RingReader::Ptr _reader; + + // 解析对讲语音rtp流为帧数据 + RtspDemuxer::Ptr _demuxer; + // 打包语音帧数据为特定rtp并回复过去 + RtpSender::Ptr _sender; +}; + +}// namespace mediakit +#endif // ZLMEDIAKIT_WEBRTC_TALK_H diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 3ac2cd44..2ad0fb10 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -28,6 +28,7 @@ #include "WebRtcEchoTest.h" #include "WebRtcPlayer.h" #include "WebRtcPusher.h" +#include "WebRtcTalk.h" #include "Rtsp/RtspMediaSourceImp.h" #define RTP_SSRC_OFFSET 1 @@ -1726,6 +1727,7 @@ void push_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWeb } } +template void play_plugin(SocketHelper &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { MediaInfo info(args["url"]); @@ -1748,7 +1750,7 @@ void play_plugin(SocketHelper &sender, const WebRtcArgs &args, const onCreateWeb // 还原成rtc,目的是为了hook时识别哪种播放协议 [AUTO-TRANSLATED:fe8dd2dc] // Restore to RTC, the purpose is to identify which playback protocol during hooking info.schema = "rtc"; - auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info, + auto rtc = Type::create(EventPollerPool::Instance().getPoller(), src, info, WebRtcTransport::Role::PEER, WebRtcTransport::SignalingProtocols::WHEP_WHIP); cb(*rtc); }); @@ -1831,7 +1833,9 @@ static onceToken s_rtc_auto_register([]() { WebRtcPluginManager::Instance().registerPlugin("echo", echo_plugin); #endif WebRtcPluginManager::Instance().registerPlugin("push", push_plugin); - WebRtcPluginManager::Instance().registerPlugin("play", play_plugin); + WebRtcPluginManager::Instance().registerPlugin("play", play_plugin); + WebRtcPluginManager::Instance().registerPlugin("talk", play_plugin); + WebRtcPluginManager::Instance().setListener([](SocketHelper& sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc) { setWebRtcArgs(args, const_cast(rtc)); }); diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 704f4790..2e8bedc6 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -66,8 +66,9 @@

- + echo + talk push play