blob: 213c690c1d00be8181faa8c7e34feb1e5e716c15 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/desktop_capturer_android.h"
#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_bytebuffer.h"
#include "base/numerics/checked_math.h"
#include "third_party/webrtc/media/base/video_common.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/ScreenCapture_jni.h"
namespace content {
namespace {
class DesktopCapturerAndroidJni : public DesktopCapturerAndroidJniInterface {
public:
~DesktopCapturerAndroidJni() override = default;
base::android::ScopedJavaLocalRef<jobject> Create(JNIEnv* env,
jlong native_ptr) override {
return Java_ScreenCapture_create(env, native_ptr);
}
jboolean StartCapture(JNIEnv* env,
const base::android::JavaRef<jobject>& obj) override {
return Java_ScreenCapture_startCapture(env, obj);
}
void Destroy(JNIEnv* env,
const base::android::JavaRef<jobject>& obj) override {
Java_ScreenCapture_destroy(env, obj);
}
};
} // namespace
DesktopCapturerAndroid::PlaneInfo::PlaneInfo() = default;
DesktopCapturerAndroid::PlaneInfo::~PlaneInfo() = default;
DesktopCapturerAndroid::PlaneInfo::PlaneInfo(PlaneInfo&& other) = default;
DesktopCapturerAndroid::PlaneInfo& DesktopCapturerAndroid::PlaneInfo::operator=(
PlaneInfo&& other) = default;
DesktopCapturerAndroid::DesktopCapturerAndroid(
const webrtc::DesktopCaptureOptions& options)
: DesktopCapturerAndroid(options,
std::make_unique<DesktopCapturerAndroidJni>()) {}
DesktopCapturerAndroid::DesktopCapturerAndroid(
const webrtc::DesktopCaptureOptions& options,
std::unique_ptr<DesktopCapturerAndroidJniInterface> jni_interface)
: jni_interface_(std::move(jni_interface)) {}
DesktopCapturerAndroid::~DesktopCapturerAndroid() {
JNIEnv* env = base::android::AttachCurrentThread();
jni_interface_->Destroy(env, screen_capture_);
}
void DesktopCapturerAndroid::Start(Callback* callback) {
callback_ = callback;
JNIEnv* env = base::android::AttachCurrentThread();
screen_capture_.Reset(
jni_interface_->Create(env, reinterpret_cast<intptr_t>(this)));
if (!jni_interface_->StartCapture(env, screen_capture_)) {
// Error immediately if we can't start capture.
finishing_ = true;
}
}
void DesktopCapturerAndroid::SetSharedMemoryFactory(
std::unique_ptr<webrtc::SharedMemoryFactory> shared_memory_factory) {}
void DesktopCapturerAndroid::CaptureFrame() {
CHECK(callback_);
if (finishing_) {
callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::ERROR_PERMANENT,
nullptr);
return;
}
if (!next_frame_) {
callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::ERROR_TEMPORARY,
nullptr);
return;
}
callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
std::move(next_frame_));
next_frame_.reset();
}
bool DesktopCapturerAndroid::SelectSource(SourceId id) {
return true;
}
void DesktopCapturerAndroid::OnRgbaFrameAvailable(
JNIEnv* env,
const base::android::JavaRef<jobject>& release_cb,
jlong timestamp_ns,
const base::android::JavaRef<jobject>& buf,
jint unchecked_pixel_stride,
jint unchecked_row_stride,
jint unchecked_crop_left,
jint unchecked_crop_top,
jint unchecked_crop_right,
jint unchecked_crop_bottom) {
// Use unsigned checked arithmetic since our operations should never go
// negative.
PlaneInfo plane;
plane.release_cb = base::android::ScopedJavaGlobalRef(release_cb);
plane.buf = base::android::ScopedJavaGlobalRef(buf);
plane.pixel_stride = unchecked_pixel_stride;
plane.row_stride = unchecked_row_stride;
plane.crop_left = unchecked_crop_left;
plane.crop_top = unchecked_crop_top;
plane.crop_right = unchecked_crop_right;
plane.crop_bottom = unchecked_crop_bottom;
ProcessRgbaFrame(timestamp_ns, std::move(plane));
}
void DesktopCapturerAndroid::OnI420FrameAvailable(
JNIEnv* env,
const base::android::JavaRef<jobject>& release_cb,
jlong timestamp_ns,
const base::android::JavaRef<jobject>& y_buf,
jint y_unchecked_pixel_stride,
jint y_unchecked_row_stride,
const base::android::JavaRef<jobject>& u_buf,
jint u_unchecked_pixel_stride,
jint u_unchecked_row_stride,
const base::android::JavaRef<jobject>& v_buf,
jint v_unchecked_pixel_stride,
jint v_unchecked_row_stride,
jint unchecked_crop_left,
jint unchecked_crop_top,
jint unchecked_crop_right,
jint unchecked_crop_bottom) {
// TODO(crbug.com/352187279): Implement processing of I420 frames.
NOTREACHED();
}
void DesktopCapturerAndroid::OnStop(JNIEnv* env) {
Shutdown();
}
void DesktopCapturerAndroid::Shutdown() {
CHECK(!finishing_);
finishing_ = true;
}
void DesktopCapturerAndroid::ProcessRgbaFrame(int64_t timestamp_ns,
PlaneInfo plane) {
// Don't process frames if we are no longer doing anything.
if (finishing_) {
base::android::RunRunnableAndroid(plane.release_cb);
return;
}
const auto width = plane.crop_right - plane.crop_left;
const auto height = plane.crop_bottom - plane.crop_top;
const webrtc::DesktopSize size(width.ValueOrDie<int32_t>(),
height.ValueOrDie<int32_t>());
next_frame_ =
std::make_unique<webrtc::BasicDesktopFrame>(size, webrtc::FOURCC_ABGR);
// We don't have access to this information to Android, but this is only
// used for mouse cursor stuff, which we don't support currently.
next_frame_->set_top_left(webrtc::DesktopVector());
// We don't have damage information on Android, so damage the whole frame.
next_frame_->mutable_updated_region()->SetRect(
webrtc::DesktopRect::MakeSize(next_frame_->size()));
// TODO(crbug.com/352187279): Set DPI based on display.
next_frame_->set_dpi(webrtc::DesktopVector());
// TODO(crbug.com/352187279): The cursor is captured for screen capture but
// not for window capture. Currently there is no way to determine if we are
// doing screen or window capture on Android. If we can determine this and set
// it conditionally here we also need a way to get the cursor position by
// implementing `MouseCursorMonitor`.
next_frame_->set_may_contain_cursor(true);
// Calculate the time delta from the previous frame's timestamp. It does not
// seem guaranteed that the timestamp we get from Android is always monotonic,
// and there's no guarantee about how it is not monotonic (e.g. unsigned
// wrapping), so don't provide a timestamp in this case.
if (last_frame_time_ns_ == 0 || timestamp_ns <= last_frame_time_ns_) {
next_frame_->set_capture_time_ms(0);
} else {
next_frame_->set_capture_time_ms((timestamp_ns - last_frame_time_ns_) /
base::Time::kNanosecondsPerMillisecond);
}
last_frame_time_ns_ = timestamp_ns;
// TODO(crbug.com/352187279): Create `DesktopCapturerId` for Android.
next_frame_->set_capturer_id(webrtc::DesktopCapturerId::kUnknown);
// There is no way to get an ICC profile on Android.
next_frame_->set_icc_profile({});
JNIEnv* env = base::android::AttachCurrentThread();
const auto span = base::android::JavaByteBufferToSpan(env, plane.buf);
const auto offset =
plane.crop_top * plane.row_stride + plane.crop_left * plane.pixel_stride;
CHECK_EQ(
static_cast<int>(static_cast<uint32_t>(plane.pixel_stride.ValueOrDie())),
webrtc::DesktopFrame::kBytesPerPixel);
CHECK_LE(static_cast<uint32_t>((width * plane.pixel_stride).ValueOrDie()),
static_cast<uint32_t>(plane.row_stride.ValueOrDie()));
CHECK_LE(static_cast<uint32_t>(offset.ValueOrDie()), span.size_bytes());
// In the case that we have a crop rectangle, width will definitely be less
// than row_stride, so only look at the number of actual pixels for the last
// row. Also, even if there is no crop, it's conceptually possible for
// row_stride to be larger than width*pixel_stride but for the buffer not to
// be large enough to include the difference between row_stride and
// width*pixel_stride at the end.
CHECK_LE(static_cast<uint32_t>((offset + (height - 1) * plane.row_stride +
width * plane.pixel_stride)
.ValueOrDie()),
span.size_bytes());
// TODO(crbug.com/352187279): Extract to `SharedMemory` instead of copying if
// possible, or, use `ScreenCaptureFrameQueue` and `ResolutionTracker` to
// reuse frames.
next_frame_->CopyPixelsFrom(
span.get_at(offset.ValueOrDie()),
static_cast<uint32_t>(plane.row_stride.ValueOrDie()),
webrtc::DesktopRect::MakeSize(size));
base::android::RunRunnableAndroid(plane.release_cb);
}
} // namespace content