blob: bd7acce2c43309209491fc1ebfd86f2a1bdd2d39 [file] [log] [blame] [edit]
/*
* Copyright (C) 2021 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.
*/
#include "camera_streamer.h"
#include <android-base/logging.h>
#include <chrono>
#include "common/libs/utils/vsock_connection.h"
namespace cuttlefish {
namespace webrtc_streaming {
CameraStreamer::CameraStreamer(unsigned int port, unsigned int cid,
bool vhost_user)
: cid_(cid),
port_(port),
vhost_user_(vhost_user),
camera_session_active_(false) {}
CameraStreamer::~CameraStreamer() { Disconnect(); }
// We are getting frames from the client so try forwarding those to the CVD
void CameraStreamer::OnFrame(const webrtc::VideoFrame& client_frame) {
std::lock_guard<std::mutex> lock(onframe_mutex_);
if (!cvd_connection_.IsConnected() && !pending_connection_.valid()) {
// Start new connection
pending_connection_ =
cvd_connection_.ConnectAsync(port_, cid_, vhost_user_);
return;
} else if (pending_connection_.valid()) {
if (!IsConnectionReady()) {
return;
}
std::lock_guard<std::mutex> lock(settings_mutex_);
if (!cvd_connection_.WriteMessage(settings_buffer_)) {
LOG(ERROR) << "Failed writing camera settings:";
return;
}
StartReadLoop();
LOG(INFO) << "Connected!";
}
auto resolution = resolution_.load();
if (resolution.height <= 0 || resolution.width <= 0 ||
!camera_session_active_.load()) {
// Nobody is receiving frames or we don't have a valid resolution that is
// necessary for potential frame scaling
return;
}
auto frame = client_frame.video_frame_buffer()->ToI420().get();
if (frame->width() != resolution.width ||
frame->height() != resolution.height) {
// incoming resolution does not match with the resolution we
// have communicated to the CVD - scaling required
if (!scaled_frame_ || resolution.width != scaled_frame_->width() ||
resolution.height != scaled_frame_->height()) {
scaled_frame_ =
webrtc::I420Buffer::Create(resolution.width, resolution.height);
}
scaled_frame_->CropAndScaleFrom(*frame);
frame = scaled_frame_.get();
}
if (!VsockSendYUVFrame(frame)) {
LOG(ERROR) << "Sending frame over vsock failed";
}
}
// Handle message json coming from client
void CameraStreamer::HandleMessage(const Json::Value& message) {
auto command = message["command"].asString();
if (command == "camera_settings") {
// save local copy of resolution that is required for frame scaling
resolution_ = GetResolutionFromSettings(message);
Json::StreamWriterBuilder factory;
std::string new_settings = Json::writeString(factory, message);
if (!settings_buffer_.empty() && new_settings != settings_buffer_) {
// Settings have changed - disconnect
// Next incoming frames will trigger re-connection
Disconnect();
}
std::lock_guard<std::mutex> lock(settings_mutex_);
settings_buffer_ = new_settings;
LOG(INFO) << "New camera settings received:" << new_settings;
}
}
// Handle binary blobs coming from client
void CameraStreamer::HandleMessage(const std::vector<char>& message) {
LOG(INFO) << "Pass through " << message.size() << "bytes";
std::lock_guard<std::mutex> lock(frame_mutex_);
cvd_connection_.WriteMessage(message);
}
CameraStreamer::Resolution CameraStreamer::GetResolutionFromSettings(
const Json::Value& settings) {
return {.width = settings["width"].asInt(),
.height = settings["height"].asInt()};
}
bool CameraStreamer::VsockSendYUVFrame(
const webrtc::I420BufferInterface* frame) {
int32_t size = frame->width() * frame->height() +
2 * frame->ChromaWidth() * frame->ChromaHeight();
const char* y = reinterpret_cast<const char*>(frame->DataY());
const char* u = reinterpret_cast<const char*>(frame->DataU());
const char* v = reinterpret_cast<const char*>(frame->DataV());
auto chroma_width = frame->ChromaWidth();
auto chroma_height = frame->ChromaHeight();
std::lock_guard<std::mutex> lock(frame_mutex_);
return cvd_connection_.Write(size) &&
cvd_connection_.WriteStrides(y, frame->width(), frame->height(),
frame->StrideY()) &&
cvd_connection_.WriteStrides(u, chroma_width, chroma_height,
frame->StrideU()) &&
cvd_connection_.WriteStrides(v, chroma_width, chroma_height,
frame->StrideV());
}
bool CameraStreamer::IsConnectionReady() {
if (!pending_connection_.valid()) {
return cvd_connection_.IsConnected();
} else if (pending_connection_.wait_for(std::chrono::seconds(0)) !=
std::future_status::ready) {
// Still waiting for connection
return false;
} else if (settings_buffer_.empty()) {
// connection is ready but we have not yet received client
// camera settings
return false;
}
return pending_connection_.get();
}
void CameraStreamer::StartReadLoop() {
if (reader_thread_.joinable()) {
reader_thread_.join();
}
reader_thread_ = std::thread([this] {
while (cvd_connection_.IsConnected()) {
static constexpr auto kEventKey = "event";
static constexpr auto kMessageStart =
"VIRTUAL_DEVICE_START_CAMERA_SESSION";
static constexpr auto kMessageStop = "VIRTUAL_DEVICE_STOP_CAMERA_SESSION";
auto json_value = cvd_connection_.ReadJsonMessage();
if (json_value[kEventKey] == kMessageStart) {
camera_session_active_ = true;
} else if (json_value[kEventKey] == kMessageStop) {
camera_session_active_ = false;
}
if (!json_value.empty()) {
SendMessage(json_value);
}
}
LOG(INFO) << "Exit reader thread";
});
}
void CameraStreamer::Disconnect() {
cvd_connection_.Disconnect();
if (reader_thread_.joinable()) {
reader_thread_.join();
}
}
} // namespace webrtc_streaming
} // namespace cuttlefish