blob: e1c4cf93de02529e2bfa9f51f6775b9e4ea590d2 [file] [log] [blame] [edit]
/*
* 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().FormatForEnv();
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