OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/tools/player_x11/x11_video_renderer.h" | 5 #include "media/tools/player_x11/x11_video_renderer.h" |
6 | 6 |
7 #include <dlfcn.h> | 7 #include <dlfcn.h> |
8 #include <X11/Xutil.h> | 8 #include <X11/Xutil.h> |
9 #include <X11/extensions/Xrender.h> | 9 #include <X11/extensions/Xrender.h> |
10 #include <X11/extensions/Xcomposite.h> | 10 #include <X11/extensions/Xcomposite.h> |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
52 | 52 |
53 return pictformat; | 53 return pictformat; |
54 } | 54 } |
55 | 55 |
56 X11VideoRenderer::X11VideoRenderer(Display* display, Window window) | 56 X11VideoRenderer::X11VideoRenderer(Display* display, Window window) |
57 : display_(display), | 57 : display_(display), |
58 window_(window), | 58 window_(window), |
59 image_(NULL), | 59 image_(NULL), |
60 new_frame_(false), | 60 new_frame_(false), |
61 picture_(0), | 61 picture_(0), |
62 use_render_(false), | 62 use_render_(false) { |
63 use_gl_(false), | |
64 gl_context_(NULL) { | |
65 // Save the instance of the video renderer. | |
66 CHECK(!instance_); | |
67 instance_ = this; | |
68 } | 63 } |
69 | 64 |
70 X11VideoRenderer::~X11VideoRenderer() { | 65 X11VideoRenderer::~X11VideoRenderer() { |
71 CHECK(instance_); | |
72 instance_ = NULL; | |
73 } | 66 } |
74 | 67 |
75 // static | 68 // static |
76 bool X11VideoRenderer::IsMediaFormatSupported( | 69 bool X11VideoRenderer::IsMediaFormatSupported( |
77 const media::MediaFormat& media_format) { | 70 const media::MediaFormat& media_format) { |
78 int width = 0; | 71 int width = 0; |
79 int height = 0; | 72 int height = 0; |
80 return ParseMediaFormat(media_format, &width, &height); | 73 return ParseMediaFormat(media_format, &width, &height); |
81 } | 74 } |
82 | 75 |
83 void X11VideoRenderer::OnStop() { | 76 void X11VideoRenderer::OnStop() { |
84 if (use_gl_) { | |
85 glXMakeCurrent(display_, 0, NULL); | |
86 glXDestroyContext(display_, gl_context_); | |
87 } | |
88 if (image_) { | 77 if (image_) { |
89 XDestroyImage(image_); | 78 XDestroyImage(image_); |
90 } | 79 } |
91 if (use_render_) { | 80 XRenderFreePicture(display_, picture_); |
92 XRenderFreePicture(display_, picture_); | |
93 } | |
94 } | 81 } |
95 | 82 |
96 static GLXContext InitGLContext(Display* display, Window window) { | |
97 // Some versions of NVIDIA's GL libGL.so include a broken version of | |
98 // dlopen/dlsym, and so linking it into chrome breaks it. So we dynamically | |
99 // load it, and use glew to dynamically resolve symbols. | |
100 // See http://code.google.com/p/chromium/issues/detail?id=16800 | |
101 void* handle = dlopen("libGL.so.1", RTLD_LAZY | RTLD_GLOBAL); | |
102 if (!handle) { | |
103 LOG(ERROR) << "Could not find libGL.so.1"; | |
104 return NULL; | |
105 } | |
106 if (glxewInit() != GLEW_OK) { | |
107 LOG(ERROR) << "GLXEW failed initialization"; | |
108 return NULL; | |
109 } | |
110 | |
111 XWindowAttributes attributes; | |
112 XGetWindowAttributes(display, window, &attributes); | |
113 XVisualInfo visual_info_template; | |
114 visual_info_template.visualid = XVisualIDFromVisual(attributes.visual); | |
115 int visual_info_count = 0; | |
116 XVisualInfo* visual_info_list = XGetVisualInfo(display, VisualIDMask, | |
117 &visual_info_template, | |
118 &visual_info_count); | |
119 GLXContext context = NULL; | |
120 for (int i = 0; i < visual_info_count && !context; ++i) { | |
121 context = glXCreateContext(display, visual_info_list + i, 0, | |
122 True /* Direct rendering */); | |
123 } | |
124 | |
125 XFree(visual_info_list); | |
126 if (!context) { | |
127 return NULL; | |
128 } | |
129 | |
130 if (!glXMakeCurrent(display, window, context)) { | |
131 glXDestroyContext(display, context); | |
132 return NULL; | |
133 } | |
134 | |
135 if (glewInit() != GLEW_OK) { | |
136 LOG(ERROR) << "GLEW failed initialization"; | |
137 glXDestroyContext(display, context); | |
138 return NULL; | |
139 } | |
140 | |
141 if (!glewIsSupported("GL_VERSION_2_0")) { | |
142 LOG(ERROR) << "GL implementation doesn't support GL version 2.0"; | |
143 glXDestroyContext(display, context); | |
144 return NULL; | |
145 } | |
146 | |
147 return context; | |
148 } | |
149 | |
150 // Matrix used for the YUV to RGB conversion. | |
151 static const float kYUV2RGB[9] = { | |
152 1.f, 0.f, 1.403f, | |
153 1.f, -.344f, -.714f, | |
154 1.f, 1.772f, 0.f, | |
155 }; | |
156 | |
157 // Vertices for a full screen quad. | |
158 static const float kVertices[8] = { | |
159 -1.f, 1.f, | |
160 -1.f, -1.f, | |
161 1.f, 1.f, | |
162 1.f, -1.f, | |
163 }; | |
164 | |
165 // Texture Coordinates mapping the entire texture. | |
166 static const float kTextureCoords[8] = { | |
167 0, 0, | |
168 0, 1, | |
169 1, 0, | |
170 1, 1, | |
171 }; | |
172 | |
173 // Pass-through vertex shader. | |
174 static const char kVertexShader[] = | |
175 "varying vec2 interp_tc;\n" | |
176 "\n" | |
177 "attribute vec4 in_pos;\n" | |
178 "attribute vec2 in_tc;\n" | |
179 "\n" | |
180 "void main() {\n" | |
181 " interp_tc = in_tc;\n" | |
182 " gl_Position = in_pos;\n" | |
183 "}\n"; | |
184 | |
185 // YUV to RGB pixel shader. Loads a pixel from each plane and pass through the | |
186 // matrix. | |
187 static const char kFragmentShader[] = | |
188 "varying vec2 interp_tc;\n" | |
189 "\n" | |
190 "uniform sampler2D y_tex;\n" | |
191 "uniform sampler2D u_tex;\n" | |
192 "uniform sampler2D v_tex;\n" | |
193 "uniform mat3 yuv2rgb;\n" | |
194 "\n" | |
195 "void main() {\n" | |
196 " float y = texture2D(y_tex, interp_tc).x;\n" | |
197 " float u = texture2D(u_tex, interp_tc).r - .5;\n" | |
198 " float v = texture2D(v_tex, interp_tc).r - .5;\n" | |
199 " vec3 rgb = yuv2rgb * vec3(y, u, v);\n" | |
200 " gl_FragColor = vec4(rgb, 1);\n" | |
201 "}\n"; | |
202 | |
203 // Buffer size for compile errors. | |
204 static const unsigned int kErrorSize = 4096; | |
205 | |
206 bool X11VideoRenderer::OnInitialize(media::VideoDecoder* decoder) { | 83 bool X11VideoRenderer::OnInitialize(media::VideoDecoder* decoder) { |
207 if (!ParseMediaFormat(decoder->media_format(), &width_, &height_)) | 84 if (!ParseMediaFormat(decoder->media_format(), &width_, &height_)) |
208 return false; | 85 return false; |
209 | 86 |
| 87 LOG(INFO) << "Initializing X11 Renderer..."; |
| 88 |
210 // Resize the window to fit that of the video. | 89 // Resize the window to fit that of the video. |
211 XResizeWindow(display_, window_, width_, height_); | 90 XResizeWindow(display_, window_, width_, height_); |
212 | 91 |
213 gl_context_ = InitGLContext(display_, window_); | 92 // Testing XRender support. We'll use the very basic of XRender |
214 use_gl_ = (gl_context_ != NULL); | 93 // so if it presents it is already good enough. We don't need |
| 94 // to check its version. |
| 95 int dummy; |
| 96 use_render_ = XRenderQueryExtension(display_, &dummy, &dummy); |
215 | 97 |
216 if (use_gl_) { | 98 if (use_render_) { |
217 glMatrixMode(GL_MODELVIEW); | 99 // If we are using XRender, we'll create a picture representing the |
218 glLoadIdentity(); | 100 // window. |
219 glViewport(0, 0, width_, height_); | 101 XWindowAttributes attr; |
| 102 XGetWindowAttributes(display_, window_, &attr); |
220 | 103 |
221 // Create 3 textures, one for each plane, and bind them to different | 104 XRenderPictFormat* pictformat = XRenderFindVisualFormat( |
222 // texture units. | 105 display_, |
223 glGenTextures(media::VideoSurface::kNumYUVPlanes, textures_); | 106 attr.visual); |
224 glActiveTexture(GL_TEXTURE0); | 107 CHECK(pictformat) << "XRENDER does not support default visual"; |
225 glBindTexture(GL_TEXTURE_2D, textures_[0]); | |
226 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
227 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
228 glEnable(GL_TEXTURE_2D); | |
229 | 108 |
230 glActiveTexture(GL_TEXTURE1); | 109 picture_ = XRenderCreatePicture(display_, window_, pictformat, 0, NULL); |
231 glBindTexture(GL_TEXTURE_2D, textures_[1]); | 110 CHECK(picture_) << "Backing picture not created"; |
232 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 111 } |
233 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
234 glEnable(GL_TEXTURE_2D); | |
235 | 112 |
236 glActiveTexture(GL_TEXTURE2); | 113 // Initialize the XImage to store the output of YUV -> RGB conversion. |
237 glBindTexture(GL_TEXTURE_2D, textures_[2]); | 114 image_ = XCreateImage(display_, |
238 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 115 DefaultVisual(display_, DefaultScreen(display_)), |
239 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 116 DefaultDepth(display_, DefaultScreen(display_)), |
240 glEnable(GL_TEXTURE_2D); | 117 ZPixmap, |
| 118 0, |
| 119 static_cast<char*>(malloc(width_ * height_ * 4)), |
| 120 width_, |
| 121 height_, |
| 122 32, |
| 123 width_ * 4); |
| 124 DCHECK(image_); |
241 | 125 |
242 GLuint program_ = glCreateProgram(); | 126 // Save this instance. |
243 | 127 DCHECK(!instance_); |
244 // Create our YUV->RGB shader. | 128 instance_ = this; |
245 GLuint vertex_shader_ = glCreateShader(GL_VERTEX_SHADER); | |
246 const char* vs_source = kVertexShader; | |
247 int vs_size = sizeof(kVertexShader); | |
248 glShaderSource(vertex_shader_, 1, &vs_source, &vs_size); | |
249 glCompileShader(vertex_shader_); | |
250 int result = GL_FALSE; | |
251 glGetShaderiv(vertex_shader_, GL_COMPILE_STATUS, &result); | |
252 if (!result) { | |
253 char log[kErrorSize]; | |
254 int len; | |
255 glGetShaderInfoLog(vertex_shader_, kErrorSize - 1, &len, log); | |
256 log[kErrorSize - 1] = 0; | |
257 LOG(FATAL) << log; | |
258 } | |
259 glAttachShader(program_, vertex_shader_); | |
260 | |
261 GLuint fragment_shader_ = glCreateShader(GL_FRAGMENT_SHADER); | |
262 const char* ps_source = kFragmentShader; | |
263 int ps_size = sizeof(kFragmentShader); | |
264 glShaderSource(fragment_shader_, 1, &ps_source, &ps_size); | |
265 glCompileShader(fragment_shader_); | |
266 result = GL_FALSE; | |
267 glGetShaderiv(fragment_shader_, GL_COMPILE_STATUS, &result); | |
268 if (!result) { | |
269 char log[kErrorSize]; | |
270 int len; | |
271 glGetShaderInfoLog(fragment_shader_, kErrorSize - 1, &len, log); | |
272 log[kErrorSize - 1] = 0; | |
273 LOG(FATAL) << log; | |
274 } | |
275 glAttachShader(program_, fragment_shader_); | |
276 | |
277 glLinkProgram(program_); | |
278 result = GL_FALSE; | |
279 glGetProgramiv(program_, GL_LINK_STATUS, &result); | |
280 if (!result) { | |
281 char log[kErrorSize]; | |
282 int len; | |
283 glGetProgramInfoLog(program_, kErrorSize - 1, &len, log); | |
284 log[kErrorSize - 1] = 0; | |
285 LOG(FATAL) << log; | |
286 } | |
287 glUseProgram(program_); | |
288 | |
289 // Bind parameters. | |
290 glUniform1i(glGetUniformLocation(program_, "y_tex"), 0); | |
291 glUniform1i(glGetUniformLocation(program_, "u_tex"), 1); | |
292 glUniform1i(glGetUniformLocation(program_, "v_tex"), 2); | |
293 int yuv2rgb_location = glGetUniformLocation(program_, "yuv2rgb"); | |
294 glUniformMatrix3fv(yuv2rgb_location, 1, GL_TRUE, kYUV2RGB); | |
295 | |
296 int pos_location = glGetAttribLocation(program_, "in_pos"); | |
297 glEnableVertexAttribArray(pos_location); | |
298 glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices); | |
299 | |
300 int tc_location = glGetAttribLocation(program_, "in_tc"); | |
301 glEnableVertexAttribArray(tc_location); | |
302 glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0, | |
303 kTextureCoords); | |
304 | |
305 // We are getting called on a thread. Release the context so that it can be | |
306 // made current on the main thread. | |
307 glXMakeCurrent(display_, 0, NULL); | |
308 } else { | |
309 // Testing XRender support. We'll use the very basic of XRender | |
310 // so if it presents it is already good enough. We don't need | |
311 // to check its version. | |
312 int dummy; | |
313 use_render_ = XRenderQueryExtension(display_, &dummy, &dummy); | |
314 | |
315 if (use_render_) { | |
316 // If we are using XRender, we'll create a picture representing the | |
317 // window. | |
318 XWindowAttributes attr; | |
319 XGetWindowAttributes(display_, window_, &attr); | |
320 | |
321 XRenderPictFormat* pictformat = XRenderFindVisualFormat( | |
322 display_, | |
323 attr.visual); | |
324 CHECK(pictformat) << "XRENDER does not support default visual"; | |
325 | |
326 picture_ = XRenderCreatePicture(display_, window_, pictformat, 0, NULL); | |
327 CHECK(picture_) << "Backing picture not created"; | |
328 } | |
329 | |
330 // Initialize the XImage to store the output of YUV -> RGB conversion. | |
331 image_ = XCreateImage(display_, | |
332 DefaultVisual(display_, DefaultScreen(display_)), | |
333 DefaultDepth(display_, DefaultScreen(display_)), | |
334 ZPixmap, | |
335 0, | |
336 static_cast<char*>(malloc(width_ * height_ * 4)), | |
337 width_, | |
338 height_, | |
339 32, | |
340 width_ * 4); | |
341 DCHECK(image_); | |
342 } | |
343 return true; | 129 return true; |
344 } | 130 } |
345 | 131 |
346 void X11VideoRenderer::OnFrameAvailable() { | 132 void X11VideoRenderer::OnFrameAvailable() { |
347 AutoLock auto_lock(lock_); | 133 AutoLock auto_lock(lock_); |
348 new_frame_ = true; | 134 new_frame_ = true; |
349 } | 135 } |
350 | 136 |
351 void X11VideoRenderer::Paint() { | 137 void X11VideoRenderer::Paint() { |
352 // Use |new_frame_| to prevent overdraw since Paint() is called more | 138 // Use |new_frame_| to prevent overdraw since Paint() is called more |
353 // often than needed. It is OK to lock only this flag and we don't | 139 // often than needed. It is OK to lock only this flag and we don't |
354 // want to lock the whole function because this method takes a long | 140 // want to lock the whole function because this method takes a long |
355 // time to complete. | 141 // time to complete. |
356 { | 142 { |
357 AutoLock auto_lock(lock_); | 143 AutoLock auto_lock(lock_); |
358 if (!new_frame_) | 144 if (!new_frame_) |
359 return; | 145 return; |
360 new_frame_ = false; | 146 new_frame_ = false; |
361 } | 147 } |
362 | 148 |
363 scoped_refptr<media::VideoFrame> video_frame; | 149 scoped_refptr<media::VideoFrame> video_frame; |
364 GetCurrentFrame(&video_frame); | 150 GetCurrentFrame(&video_frame); |
365 | 151 |
366 if ((!use_gl_ && !image_) || !video_frame) | 152 if (!image_ ||!video_frame) |
367 return; | 153 return; |
368 | 154 |
369 // Convert YUV frame to RGB. | 155 // Convert YUV frame to RGB. |
370 media::VideoSurface frame_in; | 156 media::VideoSurface frame_in; |
371 if (video_frame->Lock(&frame_in)) { | 157 if (video_frame->Lock(&frame_in)) { |
372 DCHECK(frame_in.format == media::VideoSurface::YV12 || | 158 DCHECK(frame_in.format == media::VideoSurface::YV12 || |
373 frame_in.format == media::VideoSurface::YV16); | 159 frame_in.format == media::VideoSurface::YV16); |
374 DCHECK(frame_in.strides[media::VideoSurface::kUPlane] == | 160 DCHECK(frame_in.strides[media::VideoSurface::kUPlane] == |
375 frame_in.strides[media::VideoSurface::kVPlane]); | 161 frame_in.strides[media::VideoSurface::kVPlane]); |
376 DCHECK(frame_in.planes == media::VideoSurface::kNumYUVPlanes); | 162 DCHECK(frame_in.planes == media::VideoSurface::kNumYUVPlanes); |
377 | 163 |
378 if (use_gl_) { | 164 DCHECK(image_->data); |
379 if (glXGetCurrentContext() != gl_context_ || | 165 media::YUVType yuv_type = (frame_in.format == media::VideoSurface::YV12) ? |
380 glXGetCurrentDrawable() != window_) { | 166 media::YV12 : media::YV16; |
381 glXMakeCurrent(display_, window_, gl_context_); | 167 media::ConvertYUVToRGB32(frame_in.data[media::VideoSurface::kYPlane], |
382 } | 168 frame_in.data[media::VideoSurface::kUPlane], |
383 for (unsigned int i = 0; i < media::VideoSurface::kNumYUVPlanes; ++i) { | 169 frame_in.data[media::VideoSurface::kVPlane], |
384 unsigned int width = (i == media::VideoSurface::kYPlane) ? | 170 (uint8*)image_->data, |
385 frame_in.width : frame_in.width / 2; | 171 frame_in.width, |
386 unsigned int height = (i == media::VideoSurface::kYPlane || | 172 frame_in.height, |
387 frame_in.format == media::VideoSurface::YV16) ? | 173 frame_in.strides[media::VideoSurface::kYPlane], |
388 frame_in.height : frame_in.height / 2; | 174 frame_in.strides[media::VideoSurface::kUPlane], |
389 glActiveTexture(GL_TEXTURE0 + i); | 175 image_->bytes_per_line, |
390 glPixelStorei(GL_UNPACK_ROW_LENGTH, frame_in.strides[i]); | 176 yuv_type); |
391 glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, | |
392 GL_LUMINANCE, GL_UNSIGNED_BYTE, frame_in.data[i]); | |
393 } | |
394 } else { | |
395 DCHECK(image_->data); | |
396 media::YUVType yuv_type = (frame_in.format == media::VideoSurface::YV12) ? | |
397 media::YV12 : media::YV16; | |
398 media::ConvertYUVToRGB32(frame_in.data[media::VideoSurface::kYPlane], | |
399 frame_in.data[media::VideoSurface::kUPlane], | |
400 frame_in.data[media::VideoSurface::kVPlane], | |
401 (uint8*)image_->data, | |
402 frame_in.width, | |
403 frame_in.height, | |
404 frame_in.strides[media::VideoSurface::kYPlane], | |
405 frame_in.strides[media::VideoSurface::kUPlane], | |
406 image_->bytes_per_line, | |
407 yuv_type); | |
408 } | |
409 video_frame->Unlock(); | 177 video_frame->Unlock(); |
410 } else { | 178 } else { |
411 NOTREACHED(); | 179 NOTREACHED(); |
412 } | 180 } |
413 | 181 |
414 if (use_gl_) { | |
415 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
416 glXSwapBuffers(display_, window_); | |
417 return; | |
418 } | |
419 | |
420 if (use_render_) { | 182 if (use_render_) { |
421 // If XRender is used, we'll upload the image to a pixmap. And then | 183 // If XRender is used, we'll upload the image to a pixmap. And then |
422 // creats a picture from the pixmap and composite the picture over | 184 // creats a picture from the pixmap and composite the picture over |
423 // the picture represending the window. | 185 // the picture represending the window. |
424 | 186 |
425 // Creates a XImage. | 187 // Creates a XImage. |
426 XImage image; | 188 XImage image; |
427 memset(&image, 0, sizeof(image)); | 189 memset(&image, 0, sizeof(image)); |
428 image.width = width_; | 190 image.width = width_; |
429 image.height = height_; | 191 image.height = height_; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
468 // If XRender is not used, simply put the image to the server. | 230 // If XRender is not used, simply put the image to the server. |
469 // This will have a tearing effect but this is OK. | 231 // This will have a tearing effect but this is OK. |
470 // TODO(hclam): Upload the image to a pixmap and do XCopyArea() | 232 // TODO(hclam): Upload the image to a pixmap and do XCopyArea() |
471 // to the window. | 233 // to the window. |
472 GC gc = XCreateGC(display_, window_, 0, NULL); | 234 GC gc = XCreateGC(display_, window_, 0, NULL); |
473 XPutImage(display_, window_, gc, image_, | 235 XPutImage(display_, window_, gc, image_, |
474 0, 0, 0, 0, width_, height_); | 236 0, 0, 0, 0, width_, height_); |
475 XFlush(display_); | 237 XFlush(display_); |
476 XFreeGC(display_, gc); | 238 XFreeGC(display_, gc); |
477 } | 239 } |
OLD | NEW |