Add test implementation of webrtc calls.

This commit is contained in:
John Preston 2020-05-19 10:33:57 +04:00
parent 438a560a79
commit 6d36176a8d
15 changed files with 367 additions and 27 deletions

View File

@ -318,6 +318,8 @@ PRIVATE
calls/calls_controller.cpp
calls/calls_controller.h
calls/calls_controller_tgvoip.h
calls/calls_controller_webrtc.cpp
calls/calls_controller_webrtc.h
calls/calls_emoji_fingerprint.cpp
calls/calls_emoji_fingerprint.h
calls/calls_instance.cpp

View File

@ -13,6 +13,8 @@ pacman --noconfirm -S pkg-config
PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH"
./configure --toolchain=msvc \
--extra-cflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \
--extra-cxxflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \
--extra-ldflags="-libpath:$FullExecPath/../opus/win32/VS2015/Win32/Release" \
--disable-programs \
--disable-doc \

View File

@ -1726,8 +1726,9 @@ void Updates::feedUpdate(const MTPUpdate &update) {
auto &d = update.c_updateEncryptedMessagesRead();
} break;
case mtpc_updatePhoneCall: {
Core::App().calls().handleUpdate(&session(), update.c_updatePhoneCall());
case mtpc_updatePhoneCall:
case mtpc_updatePhoneCallSignalingData: {
Core::App().calls().handleUpdate(&session(), update);
} break;
case mtpc_updateUserBlocked: {

View File

@ -32,6 +32,7 @@ namespace {
constexpr auto kMinLayer = 65;
constexpr auto kHangupTimeoutMs = 5000;
constexpr auto kSha256Size = 32;
const auto kDefaultVersion = "2.4.4"_q;
void AppendEndpoint(
std::vector<TgVoipEndpoint> &list,
@ -71,10 +72,6 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
| (gsl::to_integer<uint64>(hash[12]));
}
[[nodiscard]] std::vector<std::string> CollectVersions() {
return { TgVoip::getVersion() };
}
[[nodiscard]] QVector<MTPstring> WrapVersions(
const std::vector<std::string> &data) {
auto result = QVector<MTPstring>();
@ -86,13 +83,11 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
}
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
return WrapVersions(CollectVersions());
return WrapVersions(CollectControllerVersions());
}
} // namespace
Call::Delegate::~Delegate() = default;
Call::Call(
not_null<Delegate*> delegate,
not_null<UserData*> user,
@ -170,7 +165,7 @@ void Call::startOutgoing() {
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(TgVoip::getConnectionMaxLayer()),
MTP_int(ControllerMaxLayer()),
MTP_vector(CollectVersionsForApi()))
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
@ -251,7 +246,7 @@ void Call::actuallyAnswer() {
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(TgVoip::getConnectionMaxLayer()),
MTP_int(ControllerMaxLayer()),
MTP_vector(CollectVersionsForApi()))
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
@ -322,6 +317,25 @@ void Call::startWaitingTrack() {
_waitingTrack->playInLoop();
}
void Call::sendSignalingData(const QByteArray &data) {
_api.request(MTPphone_SendSignalingData(
MTP_inputPhoneCall(
MTP_long(_id),
MTP_long(_accessHash)),
MTP_bytes(data)
)).done([=](const MTPBool &result) {
if (!mtpIsTrue(result)) {
finish(FinishType::Failed);
}
}).fail([=](const RPCError &error) {
handleRequestError(error);
}).send();
}
void Call::displayNextFrame(QImage frame) {
_frames.fire(std::move(frame));
}
float64 Call::getWaitingSoundPeakValue() const {
if (_waitingTrack) {
auto when = crl::now() + kSoundSampleMs / 4;
@ -462,6 +476,14 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
Unexpected("phoneCall type inside an existing call handleUpdate()");
}
bool Call::handleSignalingData(
const MTPDupdatePhoneCallSignalingData &data) {
if (data.vphone_call_id().v != _id || !_controller) {
return false;
}
return _controller->receiveSignalingData(data.vdata().v);
}
void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
Expects(_type == Type::Outgoing);
@ -494,9 +516,9 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
MTP_int(kMinLayer),
MTP_int(TgVoip::getConnectionMaxLayer()),
MTP_int(ControllerMaxLayer()),
MTP_vector(CollectVersionsForApi()))
)).done([this](const MTPphone_PhoneCall &result) {
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
auto &call = result.c_phone_phoneCall();
@ -508,7 +530,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
}
createAndStartController(call.vphone_call().c_phoneCall());
}).fail([this](const RPCError &error) {
}).fail([=](const RPCError &error) {
handleRequestError(error);
}).send();
}
@ -597,14 +619,20 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
return static_cast<uint8_t>(byte);
}) | ranges::to_vector;
const auto version = call.vprotocol().match([&](
const MTPDphoneCallProtocol &data) {
return data.vlibrary_versions().v;
}).value(0, MTP_bytes(kDefaultVersion)).v;
_controller = MakeController(
"2.4.4",
version.toStdString(),
config,
TgVoipPersistentState(),
endpoints,
proxy.host.empty() ? nullptr : &proxy,
TgVoipNetworkType::Unknown,
encryptionKey);
encryptionKey,
[=](QByteArray data) { sendSignalingData(data); },
[=](QImage frame) { displayNextFrame(frame); });
const auto raw = _controller.get();
raw->setOnStateUpdated([=](TgVoipState state) {

View File

@ -48,7 +48,7 @@ public:
virtual void playSound(Sound sound) = 0;
virtual void requestMicrophonePermissionOrFail(Fn<void()> result) = 0;
virtual ~Delegate();
virtual ~Delegate() = default;
};
@ -70,6 +70,7 @@ public:
void start(bytes::const_span random);
bool handleUpdate(const MTPPhoneCall &call);
bool handleSignalingData(const MTPDupdatePhoneCallSignalingData &data);
enum State {
Starting,
@ -110,6 +111,10 @@ public:
return _muteChanged;
}
rpl::producer<QImage> frames() const {
return _frames.events();
}
crl::time getDurationMs() const;
float64 getWaitingSoundPeakValue() const;
@ -144,6 +149,8 @@ private:
void startOutgoing();
void startIncoming();
void startWaitingTrack();
void sendSignalingData(const QByteArray &data);
void displayNextFrame(QImage frame);
void generateModExpFirst(bytes::const_span randomSeed);
void handleControllerStateChange(
@ -181,6 +188,7 @@ private:
bool _mute = false;
base::Observable<bool> _muteChanged;
rpl::event_stream<QImage> _frames;
DhConfig _dhConfig;
bytes::vector _ga;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_controller.h"
#include "calls/calls_controller_tgvoip.h"
#include "calls/calls_controller_webrtc.h"
namespace Calls {
@ -18,7 +19,20 @@ namespace Calls {
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey) {
const TgVoipEncryptionKey &encryptionKey,
Fn<void(QByteArray)> sendSignalingData,
Fn<void(QImage)> displayNextFrame) {
if (version == WebrtcController::Version()) {
return std::make_unique<WebrtcController>(
config,
persistentState,
endpoints,
proxy,
initialNetworkType,
encryptionKey,
std::move(sendSignalingData),
std::move(displayNextFrame));
}
return std::make_unique<TgVoipController>(
config,
persistentState,
@ -28,4 +42,12 @@ namespace Calls {
encryptionKey);
}
std::vector<std::string> CollectControllerVersions() {
return { WebrtcController::Version(), TgVoipController::Version() };
}
int ControllerMaxLayer() {
return TgVoip::getConnectionMaxLayer();
}
} // namespace Calls

View File

@ -26,6 +26,7 @@ public:
virtual void setInputVolume(float level) = 0;
virtual void setOutputVolume(float level) = 0;
virtual void setAudioOutputDuckingEnabled(bool enabled) = 0;
virtual bool receiveSignalingData(const QByteArray &data) = 0;
virtual std::string getLastError() = 0;
virtual std::string getDebugInfo() = 0;
@ -48,6 +49,11 @@ public:
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey);
const TgVoipEncryptionKey &encryptionKey,
Fn<void(QByteArray)> sendSignalingData,
Fn<void(QImage)> displayNextFrame);
[[nodiscard]] std::vector<std::string> CollectControllerVersions();
[[nodiscard]] int ControllerMaxLayer();
} // namespace Calls

View File

@ -33,7 +33,7 @@ public:
return TgVoip::getVersion();
}
[[nodiscard]] std::string version() override {
std::string version() override {
return Version();
}
void setNetworkType(TgVoipNetworkType networkType) override {
@ -63,6 +63,9 @@ public:
void setAudioOutputDuckingEnabled(bool enabled) override {
_impl->setAudioOutputDuckingEnabled(enabled);
}
bool receiveSignalingData(const QByteArray &data) override {
return false;
}
std::string getLastError() override {
return _impl->getLastError();
}
@ -81,8 +84,7 @@ public:
void setOnStateUpdated(Fn<void(TgVoipState)> onStateUpdated) override {
_impl->setOnStateUpdated(std::move(onStateUpdated));
}
void setOnSignalBarsUpdated(
Fn<void(int)> onSignalBarsUpdated) override {
void setOnSignalBarsUpdated(Fn<void(int)> onSignalBarsUpdated) override {
_impl->setOnSignalBarsUpdated(std::move(onSignalBarsUpdated));
}
TgVoipFinalState stop() override {

View File

@ -0,0 +1,175 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_controller_webrtc.h"
#include "webrtc/webrtc_call_context.h"
namespace Calls {
namespace {
using namespace Webrtc;
[[nodiscard]] CallConnectionDescription ConvertEndpoint(const TgVoipEndpoint &data) {
return CallConnectionDescription{
.ip = QString::fromStdString(data.host.ipv4),
.ipv6 = QString::fromStdString(data.host.ipv6),
.peerTag = QByteArray(
reinterpret_cast<const char*>(data.peerTag),
base::array_size(data.peerTag)),
.connectionId = data.endpointId,
.port = data.port,
};
}
[[nodiscard]] CallContext::Config MakeContextConfig(
const TgVoipConfig &config,
const TgVoipPersistentState &persistentState,
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey,
Fn<void(QByteArray)> sendSignalingData,
Fn<void(QImage)> displayNextFrame) {
Expects(!endpoints.empty());
auto result = CallContext::Config{
.proxy = (proxy
? ProxyServer{
.host = QString::fromStdString(proxy->host),
.username = QString::fromStdString(proxy->login),
.password = QString::fromStdString(proxy->password),
.port = proxy->port }
: ProxyServer()),
.dataSaving = (config.dataSaving != TgVoipDataSaving::Never),
.key = QByteArray(
reinterpret_cast<const char*>(encryptionKey.value.data()),
encryptionKey.value.size()),
.outgoing = encryptionKey.isOutgoing,
.primary = ConvertEndpoint(endpoints.front()),
.alternatives = endpoints | ranges::view::drop(
1
) | ranges::view::transform(ConvertEndpoint) | ranges::to_vector,
.maxLayer = config.maxApiLayer,
.allowP2P = config.enableP2P,
.sendSignalingData = std::move(sendSignalingData),
.displayNextFrame = std::move(displayNextFrame),
};
return result;
}
} // namespace
WebrtcController::WebrtcController(
const TgVoipConfig &config,
const TgVoipPersistentState &persistentState,
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey,
Fn<void(QByteArray)> sendSignalingData,
Fn<void(QImage)> displayNextFrame)
: _impl(std::make_unique<CallContext>(MakeContextConfig(
config,
persistentState,
endpoints,
proxy,
initialNetworkType,
encryptionKey,
std::move(sendSignalingData),
std::move(displayNextFrame)))) {
}
WebrtcController::~WebrtcController() = default;
std::string WebrtcController::Version() {
return CallContext::Version().toStdString();
}
std::string WebrtcController::version() {
return Version();
}
void WebrtcController::setNetworkType(TgVoipNetworkType networkType) {
}
void WebrtcController::setMuteMicrophone(bool muteMicrophone) {
_impl->setIsMuted(muteMicrophone);
}
void WebrtcController::setAudioOutputGainControlEnabled(bool enabled) {
}
void WebrtcController::setEchoCancellationStrength(int strength) {
}
void WebrtcController::setAudioInputDevice(std::string id) {
}
void WebrtcController::setAudioOutputDevice(std::string id) {
}
void WebrtcController::setInputVolume(float level) {
}
void WebrtcController::setOutputVolume(float level) {
}
void WebrtcController::setAudioOutputDuckingEnabled(bool enabled) {
}
bool WebrtcController::receiveSignalingData(const QByteArray &data) {
return _impl->receiveSignalingData(data);
}
std::string WebrtcController::getLastError() {
return {};
}
std::string WebrtcController::getDebugInfo() {
return _impl->getDebugInfo().toStdString();
}
int64_t WebrtcController::getPreferredRelayId() {
return 0;
}
TgVoipTrafficStats WebrtcController::getTrafficStats() {
return {};
}
TgVoipPersistentState WebrtcController::getPersistentState() {
return TgVoipPersistentState{};
}
void WebrtcController::setOnStateUpdated(
Fn<void(TgVoipState)> onStateUpdated) {
_stateUpdatedLifetime.destroy();
_impl->state().changes(
) | rpl::start_with_next([=](CallState state) {
onStateUpdated([&] {
switch (state) {
case CallState::Initializing: return TgVoipState::WaitInit;
case CallState::Reconnecting: return TgVoipState::Reconnecting;
case CallState::Connected: return TgVoipState::Established;
case CallState::Failed: return TgVoipState::Failed;
}
Unexpected("State value in Webrtc::CallContext::state.");
}());
}, _stateUpdatedLifetime);
}
void WebrtcController::setOnSignalBarsUpdated(
Fn<void(int)> onSignalBarsUpdated) {
}
TgVoipFinalState WebrtcController::stop() {
_impl->stop();
return TgVoipFinalState();
}
} // namespace Calls

View File

@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "calls/calls_controller.h"
namespace Webrtc {
class CallContext;
} // namespace Webrtc
namespace Calls {
class WebrtcController final : public Controller {
public:
WebrtcController(
const TgVoipConfig &config,
const TgVoipPersistentState &persistentState,
const std::vector<TgVoipEndpoint> &endpoints,
const TgVoipProxy *proxy,
TgVoipNetworkType initialNetworkType,
const TgVoipEncryptionKey &encryptionKey,
Fn<void(QByteArray)> sendSignalingData,
Fn<void(QImage)> displayNextFrame);
~WebrtcController();
[[nodiscard]] static std::string Version();
std::string version() override;
void setNetworkType(TgVoipNetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
void setAudioOutputGainControlEnabled(bool enabled) override;
void setEchoCancellationStrength(int strength) override;
void setAudioInputDevice(std::string id) override;
void setAudioOutputDevice(std::string id) override;
void setInputVolume(float level) override;
void setOutputVolume(float level) override;
void setAudioOutputDuckingEnabled(bool enabled) override;
bool receiveSignalingData(const QByteArray &data) override;
std::string getLastError() override;
std::string getDebugInfo() override;
int64_t getPreferredRelayId() override;
TgVoipTrafficStats getTrafficStats() override;
TgVoipPersistentState getPersistentState() override;
void setOnStateUpdated(Fn<void(TgVoipState)> onStateUpdated) override;
void setOnSignalBarsUpdated(Fn<void(int)> onSignalBarsUpdated) override;
TgVoipFinalState stop() override;
private:
const std::unique_ptr<Webrtc::CallContext> _impl;
rpl::lifetime _stateUpdatedLifetime;
};
} // namespace Calls

View File

@ -232,13 +232,19 @@ void Instance::refreshServerConfig(not_null<Main::Session*> session) {
UpdateConfig(std::string(json.data(), json.size()));
}).fail([=](const RPCError &error) {
_serverConfigRequestSession = nullptr;
}).send();
}).send();
}
void Instance::handleUpdate(
not_null<Main::Session*> session,
const MTPDupdatePhoneCall& update) {
handleCallUpdate(session, update.vphone_call());
const MTPUpdate &update) {
update.match([&](const MTPDupdatePhoneCall &data) {
handleCallUpdate(session, data.vphone_call());
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
handleSignalingData(data);
}, [](const auto &) {
Unexpected("Update type in Calls::Instance::handleUpdate.");
});
}
void Instance::showInfoPanel(not_null<Call*> call) {
@ -291,6 +297,14 @@ void Instance::handleCallUpdate(
}
}
void Instance::handleSignalingData(
const MTPDupdatePhoneCallSignalingData &data) {
if (!_currentCall || !_currentCall->handleSignalingData(data)) {
DEBUG_LOG(("API Warning: unexpected call signaling data %1"
).arg(data.vphone_call_id().v));
}
}
bool Instance::alreadyInCall() {
return (_currentCall && _currentCall->state() != Call::State::Busy);
}

View File

@ -35,7 +35,7 @@ public:
void startOutgoingCall(not_null<UserData*> user);
void handleUpdate(
not_null<Main::Session*> session,
const MTPDupdatePhoneCall &update);
const MTPUpdate &update);
void showInfoPanel(not_null<Call*> call);
[[nodiscard]] Call *currentCall() const;
[[nodiscard]] rpl::producer<Call*> currentCallValue() const;
@ -59,6 +59,8 @@ private:
void destroyCurrentPanel();
void requestMicrophonePermissionOrFail(Fn<void()> onSuccess) override;
void handleSignalingData(const MTPDupdatePhoneCallSignalingData &data);
void refreshDhConfig();
void refreshServerConfig(not_null<Main::Session*> session);
bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data);

View File

@ -400,6 +400,11 @@ void Panel::initControls() {
_decline->finishAnimating();
_cancel->finishAnimating();
_call->frames() | rpl::start_with_next([=](QImage frame) {
_videoFrame = std::move(frame);
update();
}, lifetime());
}
void Panel::reinitControls() {
@ -742,6 +747,17 @@ void Panel::paintEvent(QPaintEvent *e) {
p.fillRect(0, _contentTop, width(), height() - _contentTop, brush);
}
if (!_videoFrame.isNull()) {
const auto to = rect().marginsRemoved(_padding);
p.save();
p.setClipRect(to);
const auto big = _videoFrame.size().scaled(to.size(), Qt::KeepAspectRatioByExpanding);
const auto pos = QPoint((to.width() - big.width()) / 2, (to.height() - big.height()) / 2);
auto hq = PainterHighQualityEnabler(p);
p.drawImage(QRect(pos, big), _videoFrame);
p.restore();
}
if (_signalBars->isDisplayed()) {
paintSignalBarsBg(p);
}

View File

@ -158,6 +158,8 @@ private:
QPixmap _bottomCache;
QPixmap _cache;
QImage _videoFrame;
};
} // namespace Calls

@ -1 +1 @@
Subproject commit 2b1cd3b5b0c909834aea00e0590435a63d318468
Subproject commit eb5be18405d47a226f6a197280176f82b3e903bd