blob: 91fa933e0d62bf03856971d532e093e2fbc60cf7 [file] [log] [blame]
/*
* Copyright (c) 2004 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
// Implementation of GtkVideoRenderer
#include "webrtc/media/devices/gtkvideorenderer.h"
#include <gdk/gdk.h>
#include <glib.h>
#include <gtk/gtk.h>
#include "libyuv/convert_argb.h"
#include "webrtc/media/engine/webrtcvideoframe.h"
namespace cricket {
class ScopedGdkLock {
public:
ScopedGdkLock() {
gdk_threads_enter();
}
~ScopedGdkLock() {
gdk_threads_leave();
}
};
GtkVideoRenderer::GtkVideoRenderer(int x, int y)
: window_(NULL),
draw_area_(NULL),
initial_x_(x),
initial_y_(y),
width_(0),
height_(0) {
g_type_init();
// g_thread_init API is deprecated since glib 2.31.0, see release note:
// http://mail.gnome.org/archives/gnome-announce-list/2011-October/msg00041.html
#if !GLIB_CHECK_VERSION(2, 31, 0)
g_thread_init(NULL);
#endif
gdk_threads_init();
}
GtkVideoRenderer::~GtkVideoRenderer() {
if (window_) {
ScopedGdkLock lock;
gtk_widget_destroy(window_);
// Run the Gtk main loop to tear down the window.
Pump();
}
// Don't need to destroy draw_area_ because it is not top-level, so it is
// implicitly destroyed by the above.
}
bool GtkVideoRenderer::SetSize(int width, int height) {
ScopedGdkLock lock;
// If the dimension is the same, no-op.
if (width_ == width && height_ == height) {
return true;
}
// For the first frame, initialize the GTK window
if ((!window_ && !Initialize(width, height)) || IsClosed()) {
return false;
}
image_.reset(new uint8_t[width * height * 4]);
gtk_widget_set_size_request(draw_area_, width, height);
width_ = width;
height_ = height;
return true;
}
void GtkVideoRenderer::OnFrame(const VideoFrame& video_frame) {
const cricket::WebRtcVideoFrame frame(
webrtc::I420Buffer::Rotate(video_frame.video_frame_buffer(),
video_frame.rotation()),
webrtc::kVideoRotation_0, video_frame.timestamp_us());
// Need to set size as the frame might be rotated.
if (!SetSize(frame.width(), frame.height())) {
return;
}
// convert I420 frame to ABGR format, which is accepted by GTK
rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer(
frame.video_frame_buffer());
libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(),
buffer->DataU(), buffer->StrideU(),
buffer->DataV(), buffer->StrideV(),
image_.get(), frame.width() * 4,
buffer->width(), buffer->height());
ScopedGdkLock lock;
if (IsClosed()) {
return;
}
// draw the ABGR image
gdk_draw_rgb_32_image(draw_area_->window,
draw_area_->style->fg_gc[GTK_STATE_NORMAL],
0,
0,
frame.width(),
frame.height(),
GDK_RGB_DITHER_MAX,
image_.get(),
frame.width() * 4);
// Run the Gtk main loop to refresh the window.
Pump();
}
bool GtkVideoRenderer::Initialize(int width, int height) {
gtk_init(NULL, NULL);
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
draw_area_ = gtk_drawing_area_new();
if (!window_ || !draw_area_) {
return false;
}
gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
gtk_window_set_title(GTK_WINDOW(window_), "Video Renderer");
gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
gtk_widget_set_size_request(draw_area_, width, height);
gtk_container_add(GTK_CONTAINER(window_), draw_area_);
gtk_widget_show_all(window_);
gtk_window_move(GTK_WINDOW(window_), initial_x_, initial_y_);
image_.reset(new uint8_t[width * height * 4]);
return true;
}
void GtkVideoRenderer::Pump() {
while (gtk_events_pending()) {
gtk_main_iteration();
}
}
bool GtkVideoRenderer::IsClosed() const {
if (!window_) {
// Not initialized yet, so hasn't been closed.
return false;
}
if (!GTK_IS_WINDOW(window_) || !GTK_IS_DRAWING_AREA(draw_area_)) {
return true;
}
return false;
}
} // namespace cricket