| /* |
| * Copyright (C) 2024 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 "host/frontend/webrtc/screenshot_handler.h" |
| |
| #include <filesystem> |
| #include <fstream> |
| |
| #include <SkData.h> |
| #include <SkImage.h> |
| #include <SkJpegEncoder.h> |
| #include <SkPngEncoder.h> |
| #include <SkRefCnt.h> |
| #include <SkStream.h> |
| #include <SkWebpEncoder.h> |
| #include <libyuv.h> |
| |
| namespace cuttlefish { |
| namespace { |
| |
| Result<sk_sp<SkImage>> GetSkImage( |
| const webrtc_streaming::VideoFrameBuffer& frame) { |
| const int w = frame.width(); |
| const int h = frame.height(); |
| |
| sk_sp<SkData> rgba_data = SkData::MakeUninitialized(w * h * 4); |
| const int rgba_stride = w * 4; |
| |
| int ret = libyuv::I420ToABGR( |
| frame.DataY(), frame.StrideY(), // |
| frame.DataU(), frame.StrideU(), // |
| frame.DataV(), frame.StrideV(), // |
| reinterpret_cast<uint8_t*>(rgba_data->writable_data()), rgba_stride, // |
| w, h); |
| CF_EXPECT_EQ(ret, 0, "Failed to convert input frame to RGBA."); |
| |
| const SkImageInfo& image_info = |
| SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kOpaque_SkAlphaType); |
| |
| sk_sp<SkImage> image = |
| SkImages::RasterFromData(image_info, rgba_data, rgba_stride); |
| CF_EXPECT(image != nullptr, "Failed to raster RGBA data."); |
| |
| return image; |
| } |
| |
| } // namespace |
| |
| Result<void> ScreenshotHandler::Screenshot(std::uint32_t display_number, |
| const std::string& screenshot_path) { |
| SharedFrameFuture frame_future; |
| { |
| std::lock_guard<std::mutex> lock(pending_screenshot_displays_mutex_); |
| |
| auto [it, inserted] = pending_screenshot_displays_.emplace( |
| display_number, SharedFramePromise{}); |
| if (!inserted) { |
| return CF_ERRF("Screenshot already pending for display {}", |
| display_number); |
| } |
| |
| frame_future = it->second.get_future().share(); |
| } |
| |
| static constexpr const int kScreenshotTimeoutSeconds = 5; |
| auto result = |
| frame_future.wait_for(std::chrono::seconds(kScreenshotTimeoutSeconds)); |
| CF_EXPECT(result == std::future_status::ready, |
| "Failed to get screenshot from webrtc display handler within " |
| << kScreenshotTimeoutSeconds << " seconds."); |
| |
| SharedFrame frame = frame_future.get(); |
| |
| sk_sp<SkImage> screenshot_image = |
| CF_EXPECT(GetSkImage(*frame), "Failed to get skia image from raw frame."); |
| |
| sk_sp<SkData> screenshot_data; |
| if (screenshot_path.ends_with(".jpg")) { |
| screenshot_data = |
| SkJpegEncoder::Encode(nullptr, screenshot_image.get(), {}); |
| CF_EXPECT(screenshot_data != nullptr, "Failed to encode to JPEG."); |
| } else if (screenshot_path.ends_with(".png")) { |
| screenshot_data = SkPngEncoder::Encode(nullptr, screenshot_image.get(), {}); |
| CF_EXPECT(screenshot_data != nullptr, "Failed to encode to PNG."); |
| } else if (screenshot_path.ends_with(".webp")) { |
| screenshot_data = |
| SkWebpEncoder::Encode(nullptr, screenshot_image.get(), {}); |
| CF_EXPECT(screenshot_data != nullptr, "Failed to encode to WEBP."); |
| } else { |
| return CF_ERR("Unsupport file format: " << screenshot_path); |
| } |
| |
| SkFILEWStream screenshot_file(screenshot_path.c_str()); |
| CF_EXPECT(screenshot_file.isValid(), |
| "Failed to open " << screenshot_path << " for writing."); |
| |
| CF_EXPECT( |
| screenshot_file.write(screenshot_data->data(), screenshot_data->size()), |
| "Failed to fully write png content to " << screenshot_path << "."); |
| |
| return {}; |
| } |
| |
| void ScreenshotHandler::OnFrame(std::uint32_t display_number, |
| SharedFrame& frame) { |
| std::lock_guard<std::mutex> lock(pending_screenshot_displays_mutex_); |
| |
| auto pending_screenshot_it = |
| pending_screenshot_displays_.find(display_number); |
| if (pending_screenshot_it == pending_screenshot_displays_.end()) { |
| return; |
| } |
| SharedFramePromise& frame_promise = pending_screenshot_it->second; |
| |
| frame_promise.set_value(frame); |
| |
| pending_screenshot_displays_.erase(pending_screenshot_it); |
| } |
| |
| } // namespace cuttlefish |