blob: 435d5f9186f1c6a6a8e6f1a0ae3205b58a46b8f0 [file] [log] [blame]
/*
* 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 "vsock_frame_provider.h"
#include <hardware/camera3.h>
#include <libyuv.h>
#include <cstring>
#define LOG_TAG "VsockFrameProvider"
#include <log/log.h>
namespace cuttlefish {
VsockFrameProvider::~VsockFrameProvider() { stop(); }
void VsockFrameProvider::start(
std::shared_ptr<cuttlefish::VsockConnection> connection, uint32_t width,
uint32_t height) {
stop();
running_ = true;
connection_ = connection;
reader_thread_ =
std::thread([this, width, height] { VsockReadLoop(width, height); });
}
void VsockFrameProvider::stop() {
running_ = false;
jpeg_pending_ = false;
if (reader_thread_.joinable()) {
reader_thread_.join();
}
connection_ = nullptr;
}
bool VsockFrameProvider::waitYUVFrame(unsigned int max_wait_ms) {
auto timeout = std::chrono::milliseconds(max_wait_ms);
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
std::unique_lock<std::mutex> lock(frame_mutex_);
return yuv_frame_updated_.wait_for(
lock, timeout, [this, now] { return timestamp_.load() > now; });
}
void VsockFrameProvider::requestJpeg() {
jpeg_pending_ = true;
Json::Value message;
message["event"] = "VIRTUAL_DEVICE_CAPTURE_IMAGE";
if (connection_) {
connection_->WriteMessage(message);
}
}
void VsockFrameProvider::cancelJpegRequest() { jpeg_pending_ = false; }
bool VsockFrameProvider::copyYUVFrame(uint32_t w, uint32_t h, YCbCrLayout dst) {
size_t y_size = w * h;
size_t cbcr_size = (w / 2) * (h / 2);
size_t total_size = y_size + 2 * cbcr_size;
std::lock_guard<std::mutex> lock(frame_mutex_);
if (frame_.size() < total_size) {
ALOGE("%s: %zu is too little for %ux%u frame", __FUNCTION__, frame_.size(),
w, h);
return false;
}
if (dst.y == nullptr) {
ALOGE("%s: Destination is nullptr!", __FUNCTION__);
return false;
}
YCbCrLayout src{.y = static_cast<void*>(frame_.data()),
.cb = static_cast<void*>(frame_.data() + y_size),
.cr = static_cast<void*>(frame_.data() + y_size + cbcr_size),
.yStride = w,
.cStride = w / 2,
.chromaStep = 1};
uint8_t* src_y = static_cast<uint8_t*>(src.y);
uint8_t* dst_y = static_cast<uint8_t*>(dst.y);
uint8_t* src_cb = static_cast<uint8_t*>(src.cb);
uint8_t* dst_cb = static_cast<uint8_t*>(dst.cb);
uint8_t* src_cr = static_cast<uint8_t*>(src.cr);
uint8_t* dst_cr = static_cast<uint8_t*>(dst.cr);
libyuv::CopyPlane(src_y, src.yStride, dst_y, dst.yStride, w, h);
if (dst.chromaStep == 1) {
// Planar
libyuv::CopyPlane(src_cb, src.cStride, dst_cb, dst.cStride, w / 2, h / 2);
libyuv::CopyPlane(src_cr, src.cStride, dst_cr, dst.cStride, w / 2, h / 2);
} else if (dst.chromaStep == 2 && dst_cr - dst_cb == 1) {
// Interleaved cb/cr planes starting with cb
libyuv::MergeUVPlane(src_cb, src.cStride, src_cr, src.cStride, dst_cb,
dst.cStride, w / 2, h / 2);
} else if (dst.chromaStep == 2 && dst_cb - dst_cr == 1) {
// Interleaved cb/cr planes starting with cr
libyuv::MergeUVPlane(src_cr, src.cStride, src_cb, src.cStride, dst_cr,
dst.cStride, w / 2, h / 2);
} else {
ALOGE("%s: Unsupported interleaved U/V layout", __FUNCTION__);
return false;
}
return true;
}
bool VsockFrameProvider::copyJpegData(uint32_t size, void* dst) {
std::lock_guard<std::mutex> lock(jpeg_mutex_);
auto jpeg_header_offset = size - sizeof(struct camera3_jpeg_blob);
if (cached_jpeg_.empty()) {
ALOGE("%s: No source data", __FUNCTION__);
return false;
} else if (dst == nullptr) {
ALOGE("%s: Destination is nullptr", __FUNCTION__);
return false;
} else if (jpeg_header_offset <= cached_jpeg_.size()) {
ALOGE("%s: %ubyte target buffer too small", __FUNCTION__, size);
return false;
}
std::memcpy(dst, cached_jpeg_.data(), cached_jpeg_.size());
struct camera3_jpeg_blob* blob = reinterpret_cast<struct camera3_jpeg_blob*>(
static_cast<char*>(dst) + jpeg_header_offset);
blob->jpeg_blob_id = CAMERA3_JPEG_BLOB_ID;
blob->jpeg_size = cached_jpeg_.size();
cached_jpeg_.clear();
return true;
}
bool VsockFrameProvider::isBlob(const std::vector<char>& blob) {
bool is_png = blob.size() > 4 && (blob[0] & 0xff) == 0x89 &&
(blob[1] & 0xff) == 0x50 && (blob[2] & 0xff) == 0x4e &&
(blob[3] & 0xff) == 0x47;
bool is_jpeg =
blob.size() > 2 && (blob[0] & 0xff) == 0xff && (blob[1] & 0xff) == 0xd8;
return is_png || is_jpeg;
}
bool VsockFrameProvider::framesizeMatches(uint32_t width, uint32_t height,
const std::vector<char>& data) {
return data.size() == 3 * width * height / 2;
}
void VsockFrameProvider::VsockReadLoop(uint32_t width, uint32_t height) {
jpeg_pending_ = false;
while (running_.load() && connection_->ReadMessage(next_frame_)) {
if (framesizeMatches(width, height, next_frame_)) {
std::lock_guard<std::mutex> lock(frame_mutex_);
timestamp_ = systemTime();
frame_.swap(next_frame_);
yuv_frame_updated_.notify_one();
} else if (isBlob(next_frame_)) {
std::lock_guard<std::mutex> lock(jpeg_mutex_);
bool was_pending = jpeg_pending_.exchange(false);
if (was_pending) {
cached_jpeg_.swap(next_frame_);
}
} else {
ALOGE("%s: Unexpected data of %zu bytes", __FUNCTION__,
next_frame_.size());
}
}
if (!connection_->IsConnected()) {
ALOGE("%s: Connection closed - exiting", __FUNCTION__);
running_ = false;
}
}
} // namespace cuttlefish