| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/client/jni/jni_frame_consumer.h" |
| |
| #include "base/android/jni_android.h" |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "remoting/base/util.h" |
| #include "remoting/client/frame_producer.h" |
| #include "remoting/client/jni/chromoting_jni_instance.h" |
| #include "remoting/client/jni/chromoting_jni_runtime.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" |
| #include "third_party/webrtc/modules/desktop_capture/desktop_region.h" |
| #include "ui/gfx/android/java_bitmap.h" |
| |
| namespace remoting { |
| |
| JniFrameConsumer::JniFrameConsumer( |
| ChromotingJniRuntime* jni_runtime, |
| scoped_refptr<ChromotingJniInstance> jni_instance) |
| : jni_runtime_(jni_runtime), |
| jni_instance_(jni_instance), |
| frame_producer_(nullptr) { |
| } |
| |
| JniFrameConsumer::~JniFrameConsumer() { |
| // The producer should now return any pending buffers. At this point, however, |
| // ReturnBuffer() tasks scheduled by the producer will not be delivered, |
| // so we free all the buffers once the producer's queue is empty. |
| base::WaitableEvent done_event(true, false); |
| frame_producer_->RequestReturnBuffers( |
| base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done_event))); |
| done_event.Wait(); |
| |
| STLDeleteElements(&buffers_); |
| } |
| |
| void JniFrameConsumer::set_frame_producer(FrameProducer* producer) { |
| frame_producer_ = producer; |
| } |
| |
| void JniFrameConsumer::ApplyBuffer(const webrtc::DesktopSize& view_size, |
| const webrtc::DesktopRect& clip_area, |
| webrtc::DesktopFrame* buffer, |
| const webrtc::DesktopRegion& region, |
| const webrtc::DesktopRegion& shape) { |
| DCHECK(jni_runtime_->display_task_runner()->BelongsToCurrentThread()); |
| |
| if (bitmap_->size().width() != buffer->size().width() || |
| bitmap_->size().height() != buffer->size().height()) { |
| // Drop the frame, since the data belongs to the previous generation, |
| // before SetSourceSize() called SetOutputSizeAndClip(). |
| FreeBuffer(buffer); |
| return; |
| } |
| |
| // Copy pixels from |buffer| into the Java Bitmap. |
| // TODO(lambroslambrou): Optimize away this copy by having the VideoDecoder |
| // decode directly into the Bitmap's pixel memory. This currently doesn't |
| // work very well because the VideoDecoder writes the decoded data in BGRA, |
| // and then the R/B channels are swapped in place (on the decoding thread). |
| // If a repaint is triggered from a Java event handler, the unswapped pixels |
| // can sometimes appear on the display. |
| uint8* dest_buffer = static_cast<uint8*>(bitmap_->pixels()); |
| webrtc::DesktopRect buffer_rect = webrtc::DesktopRect::MakeSize(view_size); |
| |
| for (webrtc::DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) { |
| const webrtc::DesktopRect& rect(i.rect()); |
| CopyRGB32Rect(buffer->data(), buffer->stride(), buffer_rect, dest_buffer, |
| bitmap_->stride(), buffer_rect, rect); |
| } |
| |
| // TODO(lambroslambrou): Optimize this by only repainting the changed pixels. |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| jni_runtime_->RedrawCanvas(); |
| jni_instance_->RecordPaintTime( |
| (base::TimeTicks::Now() - start_time).InMilliseconds()); |
| |
| // Supply |frame_producer_| with a buffer to render the next frame into. |
| frame_producer_->DrawBuffer(buffer); |
| } |
| |
| void JniFrameConsumer::ReturnBuffer(webrtc::DesktopFrame* buffer) { |
| DCHECK(jni_runtime_->display_task_runner()->BelongsToCurrentThread()); |
| FreeBuffer(buffer); |
| } |
| |
| void JniFrameConsumer::SetSourceSize(const webrtc::DesktopSize& source_size, |
| const webrtc::DesktopVector& dpi) { |
| DCHECK(jni_runtime_->display_task_runner()->BelongsToCurrentThread()); |
| |
| // We currently render the desktop 1:1 and perform pan/zoom scaling |
| // and cropping on the managed canvas. |
| clip_area_ = webrtc::DesktopRect::MakeSize(source_size); |
| frame_producer_->SetOutputSizeAndClip(source_size, clip_area_); |
| |
| // Allocate buffer and start drawing frames onto it. |
| AllocateBuffer(source_size); |
| } |
| |
| FrameConsumer::PixelFormat JniFrameConsumer::GetPixelFormat() { |
| return FORMAT_RGBA; |
| } |
| |
| void JniFrameConsumer::AllocateBuffer(const webrtc::DesktopSize& source_size) { |
| DCHECK(jni_runtime_->display_task_runner()->BelongsToCurrentThread()); |
| |
| webrtc::DesktopSize size(source_size.width(), source_size.height()); |
| |
| // Allocate a new Bitmap, store references here, and pass it to Java. |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| |
| // |bitmap_| must be deleted before |bitmap_global_ref_| is released. |
| bitmap_.reset(); |
| bitmap_global_ref_.Reset(env, jni_runtime_->NewBitmap(size).obj()); |
| bitmap_.reset(new gfx::JavaBitmap(bitmap_global_ref_.obj())); |
| jni_runtime_->UpdateFrameBitmap(bitmap_global_ref_.obj()); |
| |
| webrtc::DesktopFrame* buffer = new webrtc::BasicDesktopFrame(size); |
| buffers_.push_back(buffer); |
| frame_producer_->DrawBuffer(buffer); |
| } |
| |
| void JniFrameConsumer::FreeBuffer(webrtc::DesktopFrame* buffer) { |
| DCHECK(std::find(buffers_.begin(), buffers_.end(), buffer) != buffers_.end()); |
| |
| buffers_.remove(buffer); |
| delete buffer; |
| } |
| |
| } // namespace remoting |