282 lines
10 KiB
C++
282 lines
10 KiB
C++
/*
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "ClientHandler"
|
|
|
|
#include "host/frontend/webrtc/libdevice/client_handler.h"
|
|
|
|
#include <netdb.h>
|
|
#include <openssl/rand.h>
|
|
|
|
#include <android-base/logging.h>
|
|
|
|
#include "host/libs/config/cuttlefish_config.h"
|
|
|
|
namespace cuttlefish {
|
|
namespace webrtc_streaming {
|
|
|
|
// Video streams initiating in the client may be added and removed at unexpected
|
|
// times, causing the webrtc objects to be destroyed and created every time.
|
|
// This class hides away that complexity and allows to set up sinks only once.
|
|
class ClientVideoTrackImpl : public ClientVideoTrackInterface {
|
|
public:
|
|
void AddOrUpdateSink(rtc::VideoSinkInterface<webrtc::VideoFrame> *sink,
|
|
const rtc::VideoSinkWants &wants) override {
|
|
sink_ = sink;
|
|
wants_ = wants;
|
|
if (video_track_) {
|
|
video_track_->AddOrUpdateSink(sink, wants);
|
|
}
|
|
}
|
|
|
|
void SetVideoTrack(webrtc::VideoTrackInterface *track) {
|
|
video_track_ = track;
|
|
if (sink_) {
|
|
video_track_->AddOrUpdateSink(sink_, wants_);
|
|
}
|
|
}
|
|
|
|
void UnsetVideoTrack(webrtc::VideoTrackInterface *track) {
|
|
if (track == video_track_) {
|
|
video_track_ = nullptr;
|
|
}
|
|
}
|
|
|
|
private:
|
|
webrtc::VideoTrackInterface* video_track_;
|
|
rtc::VideoSinkInterface<webrtc::VideoFrame> *sink_ = nullptr;
|
|
rtc::VideoSinkWants wants_ = {};
|
|
};
|
|
|
|
std::shared_ptr<ClientHandler> ClientHandler::Create(
|
|
int client_id, std::shared_ptr<ConnectionObserver> observer,
|
|
PeerConnectionBuilder &connection_builder,
|
|
std::function<void(const Json::Value &)> send_to_client_cb,
|
|
std::function<void(bool)> on_connection_changed_cb) {
|
|
return std::shared_ptr<ClientHandler>(
|
|
new ClientHandler(client_id, observer, connection_builder,
|
|
send_to_client_cb, on_connection_changed_cb));
|
|
}
|
|
|
|
ClientHandler::ClientHandler(
|
|
int client_id, std::shared_ptr<ConnectionObserver> observer,
|
|
PeerConnectionBuilder &connection_builder,
|
|
std::function<void(const Json::Value &)> send_to_client_cb,
|
|
std::function<void(bool)> on_connection_changed_cb)
|
|
: client_id_(client_id),
|
|
observer_(observer),
|
|
send_to_client_(send_to_client_cb),
|
|
on_connection_changed_cb_(on_connection_changed_cb),
|
|
connection_builder_(connection_builder),
|
|
controller_(*this, *this, *this),
|
|
data_channels_handler_(observer),
|
|
camera_track_(new ClientVideoTrackImpl()) {}
|
|
|
|
rtc::scoped_refptr<webrtc::RtpSenderInterface>
|
|
ClientHandler::AddTrackToConnection(
|
|
rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track,
|
|
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection,
|
|
const std::string &label) {
|
|
if (!peer_connection) {
|
|
return nullptr;
|
|
}
|
|
// Send each track as part of a different stream with the label as id
|
|
auto err_or_sender =
|
|
peer_connection->AddTrack(track, {label} /* stream_id */);
|
|
if (!err_or_sender.ok()) {
|
|
LOG(ERROR) << "Failed to add track to the peer connection";
|
|
return nullptr;
|
|
}
|
|
return err_or_sender.MoveValue();
|
|
}
|
|
|
|
bool ClientHandler::AddDisplay(
|
|
rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track,
|
|
const std::string &label) {
|
|
auto [it, inserted] = displays_.emplace(label, DisplayTrackAndSender{
|
|
.track = video_track,
|
|
});
|
|
auto sender =
|
|
AddTrackToConnection(video_track, controller_.peer_connection(), label);
|
|
if (sender) {
|
|
DisplayTrackAndSender &info = it->second;
|
|
info.sender = sender;
|
|
}
|
|
// Succeed if the peer connection is null or the track was added
|
|
return controller_.peer_connection() == nullptr || sender;
|
|
}
|
|
|
|
bool ClientHandler::RemoveDisplay(const std::string &label) {
|
|
auto it = displays_.find(label);
|
|
if (it == displays_.end()) {
|
|
return false;
|
|
}
|
|
|
|
if (controller_.peer_connection()) {
|
|
DisplayTrackAndSender &info = it->second;
|
|
|
|
auto error = controller_.peer_connection()->RemoveTrackOrError(info.sender);
|
|
if (!error.ok()) {
|
|
LOG(ERROR) << "Failed to remove video track for display " << label << ": "
|
|
<< error.message();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
displays_.erase(it);
|
|
return true;
|
|
}
|
|
|
|
bool ClientHandler::AddAudio(
|
|
rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track,
|
|
const std::string &label) {
|
|
audio_streams_.emplace_back(audio_track, label);
|
|
auto peer_connection = controller_.peer_connection();
|
|
if (!peer_connection) {
|
|
return true;
|
|
}
|
|
return AddTrackToConnection(audio_track, controller_.peer_connection(), label)
|
|
.get();
|
|
}
|
|
|
|
ClientVideoTrackInterface* ClientHandler::GetCameraStream() {
|
|
return camera_track_.get();
|
|
}
|
|
|
|
Result<void> ClientHandler::SendMessage(const Json::Value &msg) {
|
|
send_to_client_(msg);
|
|
return {};
|
|
}
|
|
|
|
Result<rtc::scoped_refptr<webrtc::PeerConnectionInterface>>
|
|
ClientHandler::Build(
|
|
webrtc::PeerConnectionObserver &observer,
|
|
const std::vector<webrtc::PeerConnectionInterface::IceServer>
|
|
&per_connection_servers) {
|
|
auto peer_connection =
|
|
CF_EXPECT(connection_builder_.Build(observer, per_connection_servers));
|
|
|
|
// Re-add the video and audio tracks after the peer connection has been
|
|
// created
|
|
for (auto &[label, info] : displays_) {
|
|
info.sender =
|
|
CF_EXPECT(AddTrackToConnection(info.track, peer_connection, label).get());
|
|
}
|
|
// Add the audio tracks to the peer connection
|
|
for (auto &[audio_track, label] : audio_streams_) {
|
|
// Audio channels are never removed from the connection by the device, so
|
|
// it's ok to discard the returned sender here. The peer connection keeps
|
|
// track of it anyways.
|
|
CF_EXPECT(AddTrackToConnection(audio_track, peer_connection, label).get());
|
|
}
|
|
|
|
// libwebrtc configures the video encoder with a start bitrate of just 300kbs
|
|
// which causes it to drop the first 4 frames it receives. Any value over 2Mbs
|
|
// will be capped at 2Mbs when passed to the encoder by the peer_connection
|
|
// object, so we pass the maximum possible value here.
|
|
webrtc::BitrateSettings bitrate_settings;
|
|
bitrate_settings.start_bitrate_bps = 2000000; // 2Mbs
|
|
peer_connection->SetBitrate(bitrate_settings);
|
|
|
|
// At least one data channel needs to be created on the side that creates the
|
|
// SDP offer (the device) for data channels to be enabled at all.
|
|
// This channel is meant to carry control commands from the client.
|
|
auto control_channel = peer_connection->CreateDataChannel(
|
|
kControlChannelLabel, nullptr /* config */);
|
|
CF_EXPECT(control_channel.get(), "Failed to create control data channel");
|
|
|
|
data_channels_handler_.OnDataChannelOpen(control_channel);
|
|
|
|
return peer_connection;
|
|
}
|
|
|
|
void ClientHandler::HandleMessage(const Json::Value &message) {
|
|
controller_.HandleSignalingMessage(message);
|
|
}
|
|
|
|
void ClientHandler::Close() {
|
|
// We can't simply call peer_connection_->Close() here because this method
|
|
// could be called from one of the PeerConnectionObserver callbacks and that
|
|
// would lead to a deadlock (Close eventually tries to destroy an object that
|
|
// will then wait for the callback to return -> deadlock). Destroying the
|
|
// peer_connection_ has the same effect. The only alternative is to postpone
|
|
// that operation until after the callback returns.
|
|
on_connection_changed_cb_(false);
|
|
}
|
|
|
|
void ClientHandler::OnConnectionStateChange(
|
|
Result<webrtc::PeerConnectionInterface::PeerConnectionState> new_state) {
|
|
if (!new_state.ok()) {
|
|
LOG(ERROR) << "Connection error: " << new_state.error().Message();
|
|
LOG(DEBUG) << new_state.error().Trace();
|
|
Close();
|
|
return;
|
|
}
|
|
switch (*new_state) {
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
|
|
LOG(VERBOSE) << "Client " << client_id_ << ": WebRTC connected";
|
|
observer_->OnConnected();
|
|
on_connection_changed_cb_(true);
|
|
break;
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
|
|
LOG(VERBOSE) << "Client " << client_id_ << ": Connection disconnected";
|
|
Close();
|
|
break;
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
|
|
LOG(ERROR) << "Client " << client_id_ << ": Connection failed";
|
|
Close();
|
|
break;
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kClosed:
|
|
LOG(VERBOSE) << "Client " << client_id_ << ": Connection closed";
|
|
Close();
|
|
break;
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kNew:
|
|
LOG(VERBOSE) << "Client " << client_id_ << ": Connection new";
|
|
break;
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnecting:
|
|
LOG(VERBOSE) << "Client " << client_id_ << ": Connection started";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClientHandler::OnDataChannel(
|
|
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
|
data_channels_handler_.OnDataChannelOpen(data_channel);
|
|
}
|
|
|
|
void ClientHandler::OnTrack(
|
|
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
|
auto track = transceiver->receiver()->track();
|
|
if (track && track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
|
|
// It's ok to take the raw pointer here because we make sure to unset it
|
|
// when the track is removed
|
|
camera_track_->SetVideoTrack(
|
|
static_cast<webrtc::VideoTrackInterface *>(track.get()));
|
|
}
|
|
}
|
|
void ClientHandler::OnRemoveTrack(
|
|
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
|
|
auto track = receiver->track();
|
|
if (track && track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
|
|
// this only unsets if the track matches the one already in store
|
|
camera_track_->UnsetVideoTrack(
|
|
reinterpret_cast<webrtc::VideoTrackInterface *>(track.get()));
|
|
}
|
|
}
|
|
|
|
} // namespace webrtc_streaming
|
|
} // namespace cuttlefish
|