mirror of
https://github.com/tuxbox-neutrino/libstb-hal.git
synced 2025-08-26 15:02:58 +02:00
Add a framebuffer implementation based on clutter instead of "raw" OpenGL. The performance is slightly worse, but it might bring some platform abstraction and other benefits for the future.
616 lines
18 KiB
C++
616 lines
18 KiB
C++
/*
|
|
Copyright 2010 Carsten Juttner <carjay@gmx.net>
|
|
Copyright 2012,2013 Stefan Seyfried <seife@tuxboxcvs.slipkontur.de>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
openGL based framebuffer implementation
|
|
based on Carjay's neutrino-hd-dvbapi work, see
|
|
http://gitorious.org/neutrino-hd/neutrino-hd-dvbapi
|
|
|
|
TODO: AV-Sync code is "experimental" at best
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <vector>
|
|
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <linux/input.h>
|
|
#include "glfb.h"
|
|
#include <GL/glx.h>
|
|
#include "video_lib.h"
|
|
#include "audio_lib.h"
|
|
|
|
#include "lt_debug.h"
|
|
|
|
#define lt_debug_c(args...) _lt_debug(HAL_DEBUG_INIT, NULL, args)
|
|
#define lt_info_c(args...) _lt_info(HAL_DEBUG_INIT, NULL, args)
|
|
#define lt_debug(args...) _lt_debug(HAL_DEBUG_INIT, this, args)
|
|
#define lt_info(args...) _lt_info(HAL_DEBUG_INIT, this, args)
|
|
|
|
|
|
extern cVideo *videoDecoder;
|
|
extern cAudio *audioDecoder;
|
|
|
|
static GLFramebuffer *gThiz = 0; /* GLUT does not allow for an arbitrary argument to the render func */
|
|
|
|
GLFramebuffer::GLFramebuffer(int x, int y): mReInit(true), mShutDown(false), mInitDone(false)
|
|
{
|
|
mState.width = x;
|
|
mState.height = y;
|
|
mX = &_mX[0];
|
|
mY = &_mY[0];
|
|
*mX = x;
|
|
*mY = y;
|
|
av_reduce(&mOA.num, &mOA.den, x, y, INT_MAX);
|
|
mVA = mOA; /* initial aspect ratios are from the FB resolution, those */
|
|
_mVA = mVA; /* will be updated by the videoDecoder functions anyway */
|
|
mVAchanged = true;
|
|
mCrop = DISPLAY_AR_MODE_PANSCAN;
|
|
zoom = 1.0;
|
|
xscale = 1.0;
|
|
const char *tmp = getenv("GLFB_FULLSCREEN");
|
|
mFullscreen = !!(tmp);
|
|
|
|
mState.blit = true;
|
|
last_apts = 0;
|
|
|
|
/* linux framebuffer compat mode */
|
|
screeninfo.bits_per_pixel = 32;
|
|
screeninfo.xres = mState.width;
|
|
screeninfo.xres_virtual = screeninfo.xres;
|
|
screeninfo.yres = mState.height;
|
|
screeninfo.yres_virtual = screeninfo.yres;
|
|
screeninfo.blue.length = 8;
|
|
screeninfo.blue.offset = 0;
|
|
screeninfo.green.length = 8;
|
|
screeninfo.green.offset = 8;
|
|
screeninfo.red.length = 8;
|
|
screeninfo.red.offset = 16;
|
|
screeninfo.transp.length = 8;
|
|
screeninfo.transp.offset = 24;
|
|
|
|
unlink("/tmp/neutrino.input");
|
|
mkfifo("/tmp/neutrino.input", 0600);
|
|
input_fd = open("/tmp/neutrino.input", O_RDWR|O_CLOEXEC|O_NONBLOCK);
|
|
if (input_fd < 0)
|
|
lt_info("%s: could not open /tmp/neutrino.input FIFO: %m\n", __func__);
|
|
initKeys();
|
|
OpenThreads::Thread::start();
|
|
while (!mInitDone)
|
|
usleep(1);
|
|
}
|
|
|
|
GLFramebuffer::~GLFramebuffer()
|
|
{
|
|
mShutDown = true;
|
|
OpenThreads::Thread::join();
|
|
if (input_fd >= 0)
|
|
close(input_fd);
|
|
}
|
|
|
|
void GLFramebuffer::initKeys()
|
|
{
|
|
mSpecialMap[GLUT_KEY_UP] = KEY_UP;
|
|
mSpecialMap[GLUT_KEY_DOWN] = KEY_DOWN;
|
|
mSpecialMap[GLUT_KEY_LEFT] = KEY_LEFT;
|
|
mSpecialMap[GLUT_KEY_RIGHT] = KEY_RIGHT;
|
|
|
|
mSpecialMap[GLUT_KEY_F1] = KEY_RED;
|
|
mSpecialMap[GLUT_KEY_F2] = KEY_GREEN;
|
|
mSpecialMap[GLUT_KEY_F3] = KEY_YELLOW;
|
|
mSpecialMap[GLUT_KEY_F4] = KEY_BLUE;
|
|
|
|
mSpecialMap[GLUT_KEY_F5] = KEY_RECORD;
|
|
mSpecialMap[GLUT_KEY_F6] = KEY_PLAY;
|
|
mSpecialMap[GLUT_KEY_F7] = KEY_PAUSE;
|
|
mSpecialMap[GLUT_KEY_F8] = KEY_STOP;
|
|
|
|
mSpecialMap[GLUT_KEY_F9] = KEY_FORWARD;
|
|
mSpecialMap[GLUT_KEY_F10] = KEY_REWIND;
|
|
mSpecialMap[GLUT_KEY_F11] = KEY_NEXT;
|
|
mSpecialMap[GLUT_KEY_F12] = KEY_PREVIOUS;
|
|
|
|
mSpecialMap[GLUT_KEY_PAGE_UP] = KEY_PAGEUP;
|
|
mSpecialMap[GLUT_KEY_PAGE_DOWN] = KEY_PAGEDOWN;
|
|
|
|
mKeyMap[0x0d] = KEY_OK;
|
|
mKeyMap[0x1b] = KEY_EXIT;
|
|
|
|
mKeyMap['0'] = KEY_0;
|
|
mKeyMap['1'] = KEY_1;
|
|
mKeyMap['2'] = KEY_2;
|
|
mKeyMap['3'] = KEY_3;
|
|
mKeyMap['4'] = KEY_4;
|
|
mKeyMap['5'] = KEY_5;
|
|
mKeyMap['6'] = KEY_6;
|
|
mKeyMap['7'] = KEY_7;
|
|
mKeyMap['8'] = KEY_8;
|
|
mKeyMap['9'] = KEY_9;
|
|
|
|
mKeyMap['+'] = KEY_VOLUMEUP;
|
|
mKeyMap['-'] = KEY_VOLUMEDOWN;
|
|
mKeyMap['.'] = KEY_MUTE;
|
|
mKeyMap['a'] = KEY_AUDIO;
|
|
mKeyMap['e'] = KEY_EPG;
|
|
// ['f'] is reserved to toggle fullscreen;
|
|
mKeyMap['g'] = KEY_GAMES;
|
|
mKeyMap['h'] = KEY_HELP;
|
|
mKeyMap['i'] = KEY_INFO;
|
|
mKeyMap['m'] = KEY_MENU;
|
|
mKeyMap['p'] = KEY_POWER;
|
|
mKeyMap['r'] = KEY_RADIO;
|
|
mKeyMap['s'] = KEY_SUBTITLE;
|
|
mKeyMap['t'] = KEY_TV;
|
|
mKeyMap['v'] = KEY_VIDEO;
|
|
mKeyMap['z'] = KEY_SLEEP;
|
|
|
|
/* shift keys */
|
|
mKeyMap['F'] = KEY_FAVORITES;
|
|
mKeyMap['M'] = KEY_MODE;
|
|
mKeyMap['S'] = KEY_SAT;
|
|
mKeyMap['T'] = KEY_TEXT;
|
|
mKeyMap['W'] = KEY_WWW;
|
|
}
|
|
|
|
void GLFramebuffer::run()
|
|
{
|
|
setupCtx();
|
|
setupOSDBuffer();
|
|
mInitDone = true; /* signal that setup is finished */
|
|
|
|
/* init the good stuff */
|
|
GLenum err = glewInit();
|
|
if(err == GLEW_OK)
|
|
{
|
|
if((!GLEW_VERSION_1_5)||(!GLEW_EXT_pixel_buffer_object)||(!GLEW_ARB_texture_non_power_of_two))
|
|
{
|
|
lt_info("GLFB: Sorry, your graphics card is not supported. "
|
|
"Needs at least OpenGL 1.5, pixel buffer objects and NPOT textures.\n");
|
|
lt_info("incompatible graphics card: %m");
|
|
_exit(1); /* Life is hard */
|
|
}
|
|
else
|
|
{
|
|
gThiz = this;
|
|
glutSetCursor(GLUT_CURSOR_NONE);
|
|
glutDisplayFunc(GLFramebuffer::rendercb);
|
|
glutKeyboardFunc(GLFramebuffer::keyboardcb);
|
|
glutSpecialFunc(GLFramebuffer::specialcb);
|
|
glutReshapeFunc(GLFramebuffer::resizecb);
|
|
setupGLObjects(); /* needs GLEW prototypes */
|
|
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
|
|
glutMainLoop();
|
|
releaseGLObjects();
|
|
}
|
|
}
|
|
else
|
|
lt_info("GLFB: error initializing glew: %d\n", err);
|
|
lt_info("GLFB: GL thread stopping\n");
|
|
}
|
|
|
|
|
|
void GLFramebuffer::setupCtx()
|
|
{
|
|
int argc = 1;
|
|
/* some dummy commandline for GLUT to be happy */
|
|
char const *argv[2] = { "neutrino", 0 };
|
|
lt_info("GLFB: GL thread starting\n");
|
|
glutInit(&argc, const_cast<char **>(argv));
|
|
glutInitWindowSize(mX[0], mY[0]);
|
|
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
|
|
glutCreateWindow("Neutrino");
|
|
GLWinID = glXGetCurrentDrawable(); // this was the holy grail to get the right window handle for gstreamer :D
|
|
}
|
|
|
|
void GLFramebuffer::setupOSDBuffer()
|
|
{ /* the OSD buffer size can be decoupled from the actual
|
|
window size since the GL can blit-stretch with no
|
|
trouble at all, ah, the luxury of ignorance... */
|
|
// mMutex.lock();
|
|
if (mState.width && mState.height)
|
|
{
|
|
/* 32bit FB depth, *2 because tuxtxt uses a shadow buffer */
|
|
int fbmem = mState.width * mState.height * 4 * 2;
|
|
mOSDBuffer.resize(fbmem);
|
|
lt_info("GLFB: OSD buffer set to %d bytes\n", fbmem);
|
|
}
|
|
}
|
|
|
|
void GLFramebuffer::setupGLObjects()
|
|
{
|
|
unsigned char buf[4] = { 0, 0, 0, 0 }; /* 1 black pixel */
|
|
glGenTextures(1, &mState.osdtex);
|
|
glGenTextures(1, &mState.displaytex);
|
|
glBindTexture(GL_TEXTURE_2D, mState.osdtex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mState.width, mState.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, mState.displaytex); /* we do not yet know the size so will set that inline */
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
glGenBuffers(1, &mState.pbo);
|
|
glGenBuffers(1, &mState.displaypbo);
|
|
|
|
/* hack to start with black video buffer instead of white */
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo);
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(buf), buf, GL_STREAM_DRAW_ARB);
|
|
glBindTexture(GL_TEXTURE_2D, mState.displaytex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
|
|
|
|
void GLFramebuffer::releaseGLObjects()
|
|
{
|
|
glDeleteBuffers(1, &mState.pbo);
|
|
glDeleteBuffers(1, &mState.displaypbo);
|
|
glDeleteTextures(1, &mState.osdtex);
|
|
glDeleteTextures(1, &mState.displaytex);
|
|
}
|
|
|
|
|
|
/* static */ void GLFramebuffer::rendercb()
|
|
{
|
|
gThiz->render();
|
|
}
|
|
|
|
|
|
/* static */ void GLFramebuffer::keyboardcb(unsigned char key, int /*x*/, int /*y*/)
|
|
{
|
|
lt_debug_c("GLFB::%s: 0x%x\n", __func__, key);
|
|
struct input_event ev;
|
|
if (key == 'f')
|
|
{
|
|
lt_info_c("GLFB::%s: toggle fullscreen %s\n", __func__, gThiz->mFullscreen?"off":"on");
|
|
gThiz->mFullscreen = !(gThiz->mFullscreen);
|
|
gThiz->mReInit = true;
|
|
return;
|
|
}
|
|
std::map<unsigned char, int>::const_iterator i = gThiz->mKeyMap.find(key);
|
|
if (i == gThiz->mKeyMap.end())
|
|
return;
|
|
ev.code = i->second;
|
|
ev.value = 1; /* key own */
|
|
ev.type = EV_KEY;
|
|
gettimeofday(&ev.time, NULL);
|
|
lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code);
|
|
write(gThiz->input_fd, &ev, sizeof(ev));
|
|
ev.value = 0; /* neutrino is stupid, so push key up directly after key down */
|
|
write(gThiz->input_fd, &ev, sizeof(ev));
|
|
}
|
|
|
|
/* static */ void GLFramebuffer::specialcb(int key, int /*x*/, int /*y*/)
|
|
{
|
|
lt_debug_c("GLFB::%s: 0x%x\n", __func__, key);
|
|
struct input_event ev;
|
|
std::map<int, int>::const_iterator i = gThiz->mSpecialMap.find(key);
|
|
if (i == gThiz->mSpecialMap.end())
|
|
return;
|
|
ev.code = i->second;
|
|
ev.value = 1;
|
|
ev.type = EV_KEY;
|
|
gettimeofday(&ev.time, NULL);
|
|
lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code);
|
|
write(gThiz->input_fd, &ev, sizeof(ev));
|
|
ev.value = 0;
|
|
write(gThiz->input_fd, &ev, sizeof(ev));
|
|
}
|
|
|
|
int sleep_us = 30000;
|
|
|
|
void GLFramebuffer::render()
|
|
{
|
|
if(mShutDown)
|
|
glutLeaveMainLoop();
|
|
|
|
mReInitLock.lock();
|
|
if (mReInit)
|
|
{
|
|
int xoff = 0;
|
|
int yoff = 0;
|
|
mVAchanged = true;
|
|
mReInit = false;
|
|
mX = &_mX[mFullscreen];
|
|
mY = &_mY[mFullscreen];
|
|
if (mFullscreen) {
|
|
int x = glutGet(GLUT_SCREEN_WIDTH);
|
|
int y = glutGet(GLUT_SCREEN_HEIGHT);
|
|
*mX = x;
|
|
*mY = y;
|
|
AVRational a = { x, y };
|
|
if (av_cmp_q(a, mOA) < 0)
|
|
*mY = x * mOA.den / mOA.num;
|
|
else if (av_cmp_q(a, mOA) > 0)
|
|
*mX = y * mOA.num / mOA.den;
|
|
xoff = (x - *mX) / 2;
|
|
yoff = (y - *mY) / 2;
|
|
glutFullScreen();
|
|
} else
|
|
*mX = *mY * mOA.num / mOA.den;
|
|
lt_info("%s: reinit mX:%d mY:%d xoff:%d yoff:%d fs %d\n",
|
|
__func__, *mX, *mY, xoff, yoff, mFullscreen);
|
|
glViewport(xoff, yoff, *mX, *mY);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
float aspect = static_cast<float>(*mX)/ *mY;
|
|
float osdaspect = static_cast<float>(mOA.den) / mOA.num;
|
|
|
|
glOrtho(aspect*-osdaspect, aspect*osdaspect, -1.0, 1.0, -1.0, 1.0 );
|
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
mReInitLock.unlock();
|
|
if (!mFullscreen && (*mX != glutGet(GLUT_WINDOW_WIDTH) || *mY != glutGet(GLUT_WINDOW_HEIGHT)))
|
|
glutReshapeWindow(*mX, *mY);
|
|
|
|
bltDisplayBuffer(); /* decoded video stream */
|
|
if (mState.blit) {
|
|
/* only blit manually after fb->blit(), this helps to find missed blit() calls */
|
|
mState.blit = false;
|
|
lt_debug("GLFB::%s blit!\n", __func__);
|
|
bltOSDBuffer(); /* OSD */
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, mState.osdtex);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
if (mVAchanged)
|
|
{
|
|
mVAchanged = false;
|
|
zoom = 1.0;
|
|
xscale = 1.0;
|
|
int cmp = (mCrop == DISPLAY_AR_MODE_NONE) ? 0 : av_cmp_q(mVA, mOA);
|
|
const AVRational a149 = { 14, 9 };
|
|
switch (cmp) {
|
|
default:
|
|
case INT_MIN: /* invalid */
|
|
case 0: /* identical */
|
|
lt_debug("%s: mVA == mOA (or fullscreen mode :-)\n", __func__);
|
|
break;
|
|
case 1: /* mVA > mOA -- video is wider than display */
|
|
lt_debug("%s: mVA > mOA\n", __func__);
|
|
xscale = av_q2d(mVA) / av_q2d(mOA);
|
|
switch (mCrop) {
|
|
case DISPLAY_AR_MODE_PANSCAN:
|
|
break;
|
|
case DISPLAY_AR_MODE_LETTERBOX:
|
|
zoom = av_q2d(mOA) / av_q2d(mVA);
|
|
break;
|
|
case DISPLAY_AR_MODE_PANSCAN2:
|
|
zoom = av_q2d(mOA) / av_q2d(a149);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case -1: /* mVA < mOA -- video is taller than display */
|
|
lt_debug("%s: mVA < mOA\n", __func__);
|
|
xscale = av_q2d(mVA) / av_q2d(mOA);
|
|
switch (mCrop) {
|
|
case DISPLAY_AR_MODE_LETTERBOX:
|
|
break;
|
|
case DISPLAY_AR_MODE_PANSCAN2:
|
|
if (av_cmp_q(a149, mOA) < 0) {
|
|
zoom = av_q2d(mVA) * av_q2d(a149) / av_q2d(mOA);
|
|
break;
|
|
}
|
|
/* fallthrough for output format 14:9 */
|
|
case DISPLAY_AR_MODE_PANSCAN:
|
|
zoom = av_q2d(mOA) / av_q2d(mVA);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
glBindTexture(GL_TEXTURE_2D, mState.displaytex);
|
|
drawSquare(zoom, xscale);
|
|
glBindTexture(GL_TEXTURE_2D, mState.osdtex);
|
|
drawSquare(1.0, -100);
|
|
|
|
glFlush();
|
|
glutSwapBuffers();
|
|
|
|
GLuint err = glGetError();
|
|
if (err != 0)
|
|
lt_info("GLFB::%s: GLError:%d 0x%04x\n", __func__, err, err);
|
|
if (sleep_us > 0)
|
|
usleep(sleep_us);
|
|
glutPostRedisplay();
|
|
}
|
|
|
|
/* static */ void GLFramebuffer::resizecb(int w, int h)
|
|
{
|
|
gThiz->checkReinit(w, h);
|
|
}
|
|
|
|
void GLFramebuffer::checkReinit(int x, int y)
|
|
{
|
|
static int last_x = 0, last_y = 0;
|
|
|
|
mReInitLock.lock();
|
|
if (!mFullscreen && !mReInit && (x != *mX || y != *mY)) {
|
|
if (x != *mX && abs(x - last_x) > 2) {
|
|
*mX = x;
|
|
*mY = *mX * mOA.den / mOA.num;
|
|
} else if (y != *mY && abs(y - last_y) > 2) {
|
|
*mY = y;
|
|
*mX = *mY * mOA.num / mOA.den;
|
|
}
|
|
mReInit = true;
|
|
}
|
|
mReInitLock.unlock();
|
|
last_x = x;
|
|
last_y = y;
|
|
}
|
|
|
|
void GLFramebuffer::drawSquare(float size, float x_factor)
|
|
{
|
|
GLfloat vertices[] = {
|
|
1.0f, 1.0f,
|
|
-1.0f, 1.0f,
|
|
-1.0f, -1.0f,
|
|
1.0f, -1.0f,
|
|
};
|
|
|
|
GLubyte indices[] = { 0, 1, 2, 3 };
|
|
|
|
GLfloat texcoords[] = {
|
|
1.0, 0.0,
|
|
0.0, 0.0,
|
|
0.0, 1.0,
|
|
1.0, 1.0,
|
|
};
|
|
if (x_factor > -99.0) { /* x_factor == -100 => OSD */
|
|
if (videoDecoder &&
|
|
videoDecoder->pig_x > 0 && videoDecoder->pig_y > 0 &&
|
|
videoDecoder->pig_w > 0 && videoDecoder->pig_h > 0) {
|
|
/* these calculations even consider cropping and panscan mode
|
|
* maybe this could be done with some clever opengl tricks? */
|
|
double w2 = (double)mState.width * 0.5l;
|
|
double h2 = (double)mState.height * 0.5l;
|
|
double x = (double)(videoDecoder->pig_x - w2) / w2 / x_factor / size;
|
|
double y = (double)(h2 - videoDecoder->pig_y) / h2 / size;
|
|
double w = (double)videoDecoder->pig_w / w2;
|
|
double h = (double)videoDecoder->pig_h / h2;
|
|
x += ((1.0l - x_factor * size) / 2.0l) * w / x_factor / size;
|
|
y += ((size - 1.0l) / 2.0l) * h / size;
|
|
vertices[0] = x + w; /* top right x */
|
|
vertices[1] = y; /* top right y */
|
|
vertices[2] = x; /* top left x */
|
|
vertices[3] = y; /* top left y */
|
|
vertices[4] = x; /* bottom left x */
|
|
vertices[5] = y - h; /* bottom left y */
|
|
vertices[6] = vertices[0]; /* bottom right x */
|
|
vertices[7] = vertices[5]; /* bottom right y */
|
|
}
|
|
} else
|
|
x_factor = 1.0; /* OSD */
|
|
|
|
glPushMatrix();
|
|
glScalef(size * x_factor, size, size);
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glVertexPointer(2, GL_FLOAT, 0, vertices);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
|
|
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, indices);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glPopMatrix();
|
|
}
|
|
|
|
|
|
void GLFramebuffer::bltOSDBuffer()
|
|
{
|
|
/* FIXME: copy each time */
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.pbo);
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER, mOSDBuffer.size(), &mOSDBuffer[0], GL_STREAM_DRAW_ARB);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, mState.osdtex);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mState.width, mState.height, GL_BGRA, GL_UNSIGNED_BYTE, 0);
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
|
|
void GLFramebuffer::bltDisplayBuffer()
|
|
{
|
|
if (!videoDecoder) /* cannot start yet */
|
|
return;
|
|
static bool warn = true;
|
|
cVideo::SWFramebuffer *buf = videoDecoder->getDecBuf();
|
|
if (!buf) {
|
|
if (warn)
|
|
lt_debug("GLFB::%s did not get a buffer...\n", __func__);
|
|
warn = false;
|
|
return;
|
|
}
|
|
warn = true;
|
|
int w = buf->width(), h = buf->height();
|
|
if (w == 0 || h == 0)
|
|
return;
|
|
|
|
AVRational a = buf->AR();
|
|
if (a.den != 0 && a.num != 0 && av_cmp_q(a, _mVA)) {
|
|
_mVA = a;
|
|
/* _mVA is the raw buffer's aspect, mVA is the real scaled output aspect */
|
|
av_reduce(&mVA.num, &mVA.den, w * a.num, h * a.den, INT_MAX);
|
|
mVAchanged = true;
|
|
}
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo);
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER, buf->size(), &(*buf)[0], GL_STREAM_DRAW_ARB);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, mState.displaytex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
/* "rate control" mechanism starts here...
|
|
* this implementation is pretty naive and not working too well, but
|
|
* better this than nothing... :-) */
|
|
int64_t apts = 0;
|
|
/* 18000 is the magic value for A/V sync in my libao->pulseaudio->intel_hda setup */
|
|
int64_t vpts = buf->pts() + 18000;
|
|
if (audioDecoder)
|
|
apts = audioDecoder->getPts();
|
|
if (apts != last_apts) {
|
|
int rate, dummy1, dummy2;
|
|
if (apts < vpts)
|
|
sleep_us = (sleep_us * 2 + (vpts - apts)*10/9) / 3;
|
|
else if (sleep_us > 1000)
|
|
sleep_us -= 1000;
|
|
last_apts = apts;
|
|
videoDecoder->getPictureInfo(dummy1, dummy2, rate);
|
|
if (rate > 0)
|
|
rate = 2000000 / rate; /* limit to half the frame rate */
|
|
else
|
|
rate = 50000; /* minimum 20 fps */
|
|
if (sleep_us > rate)
|
|
sleep_us = rate;
|
|
else if (sleep_us < 1)
|
|
sleep_us = 1;
|
|
}
|
|
lt_debug("vpts: 0x%" PRIx64 " apts: 0x%" PRIx64 " diff: %6.3f sleep_us %d buf %d\n",
|
|
buf->pts(), apts, (buf->pts() - apts)/90000.0, sleep_us, videoDecoder->buf_num);
|
|
}
|
|
|
|
void GLFramebuffer::clear()
|
|
{
|
|
/* clears front and back buffer */
|
|
memset(&mOSDBuffer[0], 0, mOSDBuffer.size());
|
|
}
|