1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "camera_streamer.h"
17
18 #include <android-base/logging.h>
19 #include <chrono>
20 #include "common/libs/utils/vsock_connection.h"
21
22 namespace cuttlefish {
23 namespace webrtc_streaming {
24
CameraStreamer(unsigned int port,unsigned int cid,bool vhost_user)25 CameraStreamer::CameraStreamer(unsigned int port, unsigned int cid,
26 bool vhost_user)
27 : cid_(cid),
28 port_(port),
29 vhost_user_(vhost_user),
30 camera_session_active_(false) {}
31
~CameraStreamer()32 CameraStreamer::~CameraStreamer() { Disconnect(); }
33
34 // We are getting frames from the client so try forwarding those to the CVD
OnFrame(const webrtc::VideoFrame & client_frame)35 void CameraStreamer::OnFrame(const webrtc::VideoFrame& client_frame) {
36 std::lock_guard<std::mutex> lock(onframe_mutex_);
37 if (!cvd_connection_.IsConnected() && !pending_connection_.valid()) {
38 // Start new connection
39 pending_connection_ =
40 cvd_connection_.ConnectAsync(port_, cid_, vhost_user_);
41 return;
42 } else if (pending_connection_.valid()) {
43 if (!IsConnectionReady()) {
44 return;
45 }
46 std::lock_guard<std::mutex> lock(settings_mutex_);
47 if (!cvd_connection_.WriteMessage(settings_buffer_)) {
48 LOG(ERROR) << "Failed writing camera settings:";
49 return;
50 }
51 StartReadLoop();
52 LOG(INFO) << "Connected!";
53 }
54 auto resolution = resolution_.load();
55 if (resolution.height <= 0 || resolution.width <= 0 ||
56 !camera_session_active_.load()) {
57 // Nobody is receiving frames or we don't have a valid resolution that is
58 // necessary for potential frame scaling
59 return;
60 }
61 auto frame = client_frame.video_frame_buffer()->ToI420().get();
62 if (frame->width() != resolution.width ||
63 frame->height() != resolution.height) {
64 // incoming resolution does not match with the resolution we
65 // have communicated to the CVD - scaling required
66 if (!scaled_frame_ || resolution.width != scaled_frame_->width() ||
67 resolution.height != scaled_frame_->height()) {
68 scaled_frame_ =
69 webrtc::I420Buffer::Create(resolution.width, resolution.height);
70 }
71 scaled_frame_->CropAndScaleFrom(*frame);
72 frame = scaled_frame_.get();
73 }
74 if (!VsockSendYUVFrame(frame)) {
75 LOG(ERROR) << "Sending frame over vsock failed";
76 }
77 }
78
79 // Handle message json coming from client
HandleMessage(const Json::Value & message)80 void CameraStreamer::HandleMessage(const Json::Value& message) {
81 auto command = message["command"].asString();
82 if (command == "camera_settings") {
83 // save local copy of resolution that is required for frame scaling
84 resolution_ = GetResolutionFromSettings(message);
85 Json::StreamWriterBuilder factory;
86 std::string new_settings = Json::writeString(factory, message);
87 if (!settings_buffer_.empty() && new_settings != settings_buffer_) {
88 // Settings have changed - disconnect
89 // Next incoming frames will trigger re-connection
90 Disconnect();
91 }
92 std::lock_guard<std::mutex> lock(settings_mutex_);
93 settings_buffer_ = new_settings;
94 LOG(INFO) << "New camera settings received:" << new_settings;
95 }
96 }
97
98 // Handle binary blobs coming from client
HandleMessage(const std::vector<char> & message)99 void CameraStreamer::HandleMessage(const std::vector<char>& message) {
100 LOG(INFO) << "Pass through " << message.size() << "bytes";
101 std::lock_guard<std::mutex> lock(frame_mutex_);
102 cvd_connection_.WriteMessage(message);
103 }
104
GetResolutionFromSettings(const Json::Value & settings)105 CameraStreamer::Resolution CameraStreamer::GetResolutionFromSettings(
106 const Json::Value& settings) {
107 return {.width = settings["width"].asInt(),
108 .height = settings["height"].asInt()};
109 }
110
VsockSendYUVFrame(const webrtc::I420BufferInterface * frame)111 bool CameraStreamer::VsockSendYUVFrame(
112 const webrtc::I420BufferInterface* frame) {
113 int32_t size = frame->width() * frame->height() +
114 2 * frame->ChromaWidth() * frame->ChromaHeight();
115 const char* y = reinterpret_cast<const char*>(frame->DataY());
116 const char* u = reinterpret_cast<const char*>(frame->DataU());
117 const char* v = reinterpret_cast<const char*>(frame->DataV());
118 auto chroma_width = frame->ChromaWidth();
119 auto chroma_height = frame->ChromaHeight();
120 std::lock_guard<std::mutex> lock(frame_mutex_);
121 return cvd_connection_.Write(size) &&
122 cvd_connection_.WriteStrides(y, frame->width(), frame->height(),
123 frame->StrideY()) &&
124 cvd_connection_.WriteStrides(u, chroma_width, chroma_height,
125 frame->StrideU()) &&
126 cvd_connection_.WriteStrides(v, chroma_width, chroma_height,
127 frame->StrideV());
128 }
129
IsConnectionReady()130 bool CameraStreamer::IsConnectionReady() {
131 if (!pending_connection_.valid()) {
132 return cvd_connection_.IsConnected();
133 } else if (pending_connection_.wait_for(std::chrono::seconds(0)) !=
134 std::future_status::ready) {
135 // Still waiting for connection
136 return false;
137 } else if (settings_buffer_.empty()) {
138 // connection is ready but we have not yet received client
139 // camera settings
140 return false;
141 }
142 return pending_connection_.get();
143 }
144
StartReadLoop()145 void CameraStreamer::StartReadLoop() {
146 if (reader_thread_.joinable()) {
147 reader_thread_.join();
148 }
149 reader_thread_ = std::thread([this] {
150 while (cvd_connection_.IsConnected()) {
151 static constexpr auto kEventKey = "event";
152 static constexpr auto kMessageStart =
153 "VIRTUAL_DEVICE_START_CAMERA_SESSION";
154 static constexpr auto kMessageStop = "VIRTUAL_DEVICE_STOP_CAMERA_SESSION";
155 auto json_value = cvd_connection_.ReadJsonMessage();
156 if (json_value[kEventKey] == kMessageStart) {
157 camera_session_active_ = true;
158 } else if (json_value[kEventKey] == kMessageStop) {
159 camera_session_active_ = false;
160 }
161 if (!json_value.empty()) {
162 SendMessage(json_value);
163 }
164 }
165 LOG(INFO) << "Exit reader thread";
166 });
167 }
168
Disconnect()169 void CameraStreamer::Disconnect() {
170 cvd_connection_.Disconnect();
171 if (reader_thread_.joinable()) {
172 reader_thread_.join();
173 }
174 }
175
176 } // namespace webrtc_streaming
177 } // namespace cuttlefish
178