mirror of
https://github.com/tuxbox-neutrino/libstb-hal.git
synced 2025-08-26 23:13:16 +02:00
615 lines
18 KiB
C++
615 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 <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();
|
|
startThread();
|
|
while (!mInitDone)
|
|
usleep(1);
|
|
}
|
|
|
|
GLFramebuffer::~GLFramebuffer()
|
|
{
|
|
mShutDown = true;
|
|
joinThread();
|
|
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());
|
|
}
|