| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/test/video_frame_writer.h" |
| |
| #include <stdint.h> |
| |
| #include <vector> |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/span.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/logging.h" |
| #include "base/strings/stringprintf.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace { |
| |
| const base::FilePath::CharType kFrameFileName[] = |
| FILE_PATH_LITERAL("frame.png"); |
| const base::FilePath::CharType kRemotingFolder[] = |
| FILE_PATH_LITERAL("remoting"); |
| const base::FilePath::CharType kDumpFrameFolder[] = |
| FILE_PATH_LITERAL("dumped_images"); |
| |
| } // namespace |
| |
| namespace remoting::test { |
| |
| VideoFrameWriter::VideoFrameWriter() |
| : instance_creation_time_(base::Time::Now()), |
| frame_name_unique_number_(0) {} |
| |
| VideoFrameWriter::~VideoFrameWriter() = default; |
| |
| void VideoFrameWriter::WriteFrameToPath(const webrtc::DesktopFrame& frame, |
| const base::FilePath& image_path) { |
| CHECK_EQ(frame.pixel_format(), webrtc::FOURCC_ARGB); |
| uint8_t* frame_data = reinterpret_cast<unsigned char*>(frame.data()); |
| |
| std::optional<std::vector<uint8_t>> png_encoded_data = gfx::PNGCodec::Encode( |
| frame_data, gfx::PNGCodec::FORMAT_BGRA, |
| gfx::Size(frame.size().width(), frame.size().height()), frame.stride(), |
| true, std::vector<gfx::PNGCodec::Comment>()); |
| if (!png_encoded_data) { |
| LOG(WARNING) << "Failed to encode frame to PNG file"; |
| return; |
| } |
| |
| if (!base::WriteFile(image_path, png_encoded_data.value())) { |
| LOG(WARNING) << "Failed to write frame to disk"; |
| } |
| } |
| |
| // Save video frame to path named with the |instance_creation_time|. |
| void VideoFrameWriter::WriteFrameToDefaultPath( |
| const webrtc::DesktopFrame& frame) { |
| base::FilePath dump_frame_file_path; |
| if (!GetTempDir(&dump_frame_file_path)) { |
| LOG(WARNING) << "Failed to retrieve temporary directory path"; |
| return; |
| } |
| |
| dump_frame_file_path = dump_frame_file_path.Append(kRemotingFolder); |
| dump_frame_file_path = dump_frame_file_path.Append(kDumpFrameFolder); |
| if (!CreateDirectoryIfNotExists(dump_frame_file_path)) { |
| return; |
| } |
| |
| // Create a sub-folder using date and time to identify a particular test run. |
| dump_frame_file_path = AppendCreationDateAndTime(dump_frame_file_path); |
| if (!CreateDirectoryIfNotExists(dump_frame_file_path)) { |
| return; |
| } |
| |
| dump_frame_file_path = dump_frame_file_path.Append(kFrameFileName); |
| |
| dump_frame_file_path = dump_frame_file_path.InsertBeforeExtensionASCII( |
| base::StringPrintf("(%d)", ++frame_name_unique_number_)); |
| |
| LOG(INFO) << "Video frame dumped to: " << dump_frame_file_path.value(); |
| |
| WriteFrameToPath(frame, dump_frame_file_path); |
| } |
| |
| void VideoFrameWriter::HighlightRectInFrame(webrtc::DesktopFrame* frame, |
| const webrtc::DesktopRect& rect) { |
| if (rect.left() < 0 || rect.top() < 0 || |
| rect.right() >= frame->size().width() || |
| rect.bottom() >= frame->size().height()) { |
| LOG(ERROR) << "Highlight rect lies outside of the frame."; |
| return; |
| } |
| |
| // Draw vertical borders. |
| for (int y = rect.top(); y <= rect.bottom(); ++y) { |
| ShiftPixelColor(frame, rect.left(), y, 128); |
| ShiftPixelColor(frame, rect.right(), y, 128); |
| } |
| |
| // Draw horizontal borders. |
| for (int x = rect.left(); x <= rect.right(); ++x) { |
| ShiftPixelColor(frame, x, rect.top(), 128); |
| ShiftPixelColor(frame, x, rect.bottom(), 128); |
| } |
| } |
| |
| base::FilePath VideoFrameWriter::AppendCreationDateAndTime( |
| const base::FilePath& file_path) { |
| return file_path.AppendASCII(base::UnlocalizedTimeFormatWithPattern( |
| instance_creation_time_, "y-M-d_H-m-s")); |
| } |
| |
| bool VideoFrameWriter::CreateDirectoryIfNotExists( |
| const base::FilePath& file_path) { |
| if (!base::DirectoryExists(file_path) && !base::CreateDirectory(file_path)) { |
| LOG(WARNING) << "Failed to create directory: " << file_path.value(); |
| return false; |
| } |
| return true; |
| } |
| |
| void VideoFrameWriter::ShiftPixelColor(webrtc::DesktopFrame* frame, |
| int x, |
| int y, |
| int shift_amount) { |
| // SAFETY: No safe interface to `webrtc::DesktopFrame`. |
| UNSAFE_BUFFERS(base::span<uint8_t> pixel( |
| frame->data() + y * frame->stride() + |
| x * webrtc::DesktopFrame::kBytesPerPixel, |
| static_cast<size_t>(webrtc::DesktopFrame::kBytesPerPixel))); |
| |
| // Only shift RGB channels. |
| for (int i = 0; i < 3; ++i) { |
| pixel[i] += shift_amount; |
| } |
| } |
| |
| } // namespace remoting::test |