You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/libkopete/avdevice/qvideostream.cpp

732 lines
18 KiB

/*
*
* Copyright (C) 2002 George Staikos <staikos@kde.org>
* 2004 Dirk Ziegelmeier <dziegel@gmx.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "qvideostream.h"
#include <tqevent.h>
#include <tqimage.h>
#include <tqtimer.h>
#include <kdebug.h>
#include "kxv.h"
#include <sys/types.h>
#include <X11/Xutil.h>
#ifdef HAVE_XSHM
extern "C" {
#include <sys/shm.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
}
#endif
#ifdef HAVE_GL
class QVideoStreamGLWidget : public TQGLWidget
{
public:
QVideoStreamGLWidget(TQWidget* parent = 0, const char* name = 0);
virtual ~QVideoStreamGLWidget();
void setInputSize(const TQSize& sz);
void display(const unsigned char *const img, int x, int y, int sw, int sh);
private:
virtual void resizeGL(int w, int h);
void initializeGL();
virtual bool eventFilter( TQObject *o, TQEvent *e );
void calc(TQPoint& p, TQPoint& v);
TQSize _inputSize;
GLuint _tex;
int _tw, _th;
TQWidget* _w;
int _maxGL;
TQSize _sz;
bool _glfun;
TQPoint _ul, _ur, _ll, _lr;
TQPoint _vul, _vur, _vll, _vlr;
TQTimer* _glfunTimer;
};
#endif
class QVideoStreamPrivate
{
public:
QVideoStreamPrivate();
~QVideoStreamPrivate();
KXv *xvHandle;
KXvDevice *xvdev;
XImage *xim;
GC gc;
#ifdef HAVE_GL
QVideoStreamGLWidget* glwidget;
#endif
#ifdef HAVE_XSHM
XShmSegmentInfo shmh;
#endif
};
QVideoStreamPrivate::QVideoStreamPrivate()
{
xvHandle = 0;
xim = 0;
}
QVideoStreamPrivate::~QVideoStreamPrivate()
{
delete xvHandle;
}
QVideoStream::QVideoStream(TQWidget *widget, const char* name)
: TQObject(widget, name),
d(new QVideoStreamPrivate),
_w(widget),
_methods(METHOD_NONE),
_method(METHOD_NONE),
_format(FORMAT_NONE),
_init(false)
{
int dummy;
unsigned int dummy2;
findDisplayProperties(_xFormat, dummy, dummy2, dummy);
_methods = (VideoMethod)(_methods | METHOD_X11);
#ifdef HAVE_XSHM
if (XShmQueryExtension(_w->x11Display())) {
_methods = (VideoMethod)(_methods | METHOD_XSHM);
}
#endif
if (KXv::haveXv()) {
_methods = (VideoMethod)(_methods | METHOD_XV);
#ifdef HAVE_XSHM
_methods = (VideoMethod)(_methods | METHOD_XVSHM);
#endif
}
#ifdef HAVE_GL
if (TQGLFormat::hasOpenGL()) {
_methods = (VideoMethod)(_methods | METHOD_GL);
}
#endif
d->gc = XCreateGC(_w->x11Display(), _w->winId(), 0, NULL);
}
QVideoStream::~QVideoStream()
{
deInit();
XFreeGC(_w->x11Display(), d->gc);
delete d;
}
void QVideoStream::deInit()
{
if (!_init)
return;
_init = false;
_format = FORMAT_NONE;
Q_ASSERT(_methods & _method);
if (!(_methods & _method))
return;
switch (_method) {
case METHOD_XSHM:
#ifdef HAVE_XSHM
XShmDetach(_w->x11Display(), &(d->shmh));
XDestroyImage(d->xim);
d->xim = 0;
shmdt(d->shmh.shmaddr);
#endif
break;
case METHOD_X11:
delete[] d->xim->data;
d->xim->data = 0;
XDestroyImage(d->xim);
d->xim = 0;
break;
case METHOD_XVSHM:
case METHOD_XV:
delete d->xvHandle;
d->xvHandle = 0;
break;
case METHOD_GL:
#ifdef HAVE_GL
delete d->glwidget;
#endif
break;
default:
Q_ASSERT(0);
return;
}
}
void QVideoStream::init()
{
Q_ASSERT(_methods & _method);
if (!(_methods & _method))
return;
switch (_method) {
case METHOD_XSHM:
{
#ifdef HAVE_XSHM
if ( !_inputSize.isValid() ) {
kdWarning() << "QVideoStream::init() (XSHM): Unable to initialize due to invalid input size." << endl;
return;
}
memset(&(d->shmh), 0, sizeof(XShmSegmentInfo));
d->xim = XShmCreateImage(_w->x11Display(),
(Visual*)_w->x11Visual(),
_w->x11Depth(),
ZPixmap, 0, &(d->shmh),
_inputSize.width(),
_inputSize.height());
d->shmh.shmid = shmget(IPC_PRIVATE,
d->xim->bytes_per_line*d->xim->height,
IPC_CREAT|0600);
d->shmh.shmaddr = (char *)shmat(d->shmh.shmid, 0, 0);
d->xim->data = (char*)d->shmh.shmaddr;
d->shmh.readOnly = False;
Status s = XShmAttach(_w->x11Display(), &(d->shmh));
if (s) {
XSync(_w->x11Display(), False);
shmctl(d->shmh.shmid, IPC_RMID, 0);
_format = _xFormat;
_init = true;
} else {
kdWarning() << "XShmAttach failed!" << endl;
XDestroyImage(d->xim);
d->xim = 0;
shmdt(d->shmh.shmaddr);
}
#endif
}
break;
case METHOD_X11:
if ( !_inputSize.isValid() ) {
kdWarning() << "QVideoStream::init() (X11): Unable to initialize due to invalid input size." << endl;
return;
}
d->xim = XCreateImage(_w->x11Display(),
(Visual*)_w->x11Visual(),
_w->x11Depth(),
ZPixmap, 0, 0,
_inputSize.width(),
_inputSize.height(),
32, 0);
d->xim->data = new char[d->xim->bytes_per_line*_inputSize.height()];
_format = _xFormat;
_init = true;
break;
case METHOD_XVSHM:
case METHOD_XV:
{
if (d->xvHandle)
delete d->xvHandle;
d->xvHandle = KXv::connect(_w->winId());
KXvDeviceList& xvdl(d->xvHandle->devices());
KXvDevice *xvdev = NULL;
for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) {
if (xvdev->isImageBackend() &&
xvdev->supportsWidget(_w)) {
d->xvdev = xvdev;
d->xvdev->useShm(_method == METHOD_XVSHM);
_format = FORMAT_YUYV;
_init = true;
break;
}
}
if (!_init) {
delete d->xvHandle;
d->xvHandle = 0;
}
}
break;
case METHOD_GL:
#ifdef HAVE_GL
d->glwidget = new QVideoStreamGLWidget(_w, "QVideoStreamGLWidget");
d->glwidget->resize(_w->width(), _w->height());
d->glwidget->show();
_format = FORMAT_BGR24;
_init = true;
#endif
break;
default:
Q_ASSERT(0);
return;
}
}
bool QVideoStream::haveMethod(VideoMethod method) const
{
return _methods & method;
}
QVideo::VideoMethod QVideoStream::method() const
{
return _method;
}
QVideo::VideoMethod QVideoStream::setMethod(VideoMethod method)
{
if (_methods & method) {
deInit();
_method = method;
init();
}
return _method;
}
TQSize QVideoStream::maxSize() const
{
return _size;
}
int QVideoStream::maxWidth() const
{
return _size.width();
}
int QVideoStream::maxHeight() const
{
return _size.height();
}
TQSize QVideoStream::size() const
{
return _size;
}
int QVideoStream::width() const
{
return _size.width();
}
int QVideoStream::height() const
{
return _size.height();
}
TQSize QVideoStream::setSize(const TQSize& sz)
{
_size = sz;
return _size;
}
int QVideoStream::setWidth(int width)
{
if (width < 0)
width = 0;
if (width > maxWidth())
width = maxWidth();
_size.setWidth(width);
return _size.width();
}
int QVideoStream::setHeight(int height)
{
if (height < 0)
height = 0;
if (height > maxHeight())
height = maxHeight();
_size.setHeight(height);
return _size.height();
}
TQSize QVideoStream::inputSize() const
{
return _inputSize;
}
int QVideoStream::inputWidth() const
{
return _inputSize.width();
}
int QVideoStream::inputHeight() const
{
return _inputSize.height();
}
TQSize QVideoStream::setInputSize(const TQSize& sz)
{
if (sz == _inputSize)
return _inputSize;
_inputSize = sz;
if (_method & (METHOD_XSHM | METHOD_X11)) {
deInit();
init();
}
#ifdef HAVE_GL
if (_method & METHOD_GL) {
d->glwidget->setInputSize(_inputSize);
}
#endif
return _inputSize;
}
int QVideoStream::setInputWidth(int width)
{
if (width == _inputSize.width())
return _inputSize.width();
_inputSize.setWidth(width);
if (_method & (METHOD_XSHM | METHOD_X11)) {
deInit();
init();
}
#ifdef HAVE_GL
if (_method & METHOD_GL) {
d->glwidget->setInputSize(_inputSize);
}
#endif
return _inputSize.width();
}
int QVideoStream::setInputHeight(int height)
{
if (height == _inputSize.height())
return _inputSize.height();
_inputSize.setHeight(height);
if (_method & (METHOD_XSHM | METHOD_X11)) {
deInit();
init();
}
#ifdef HAVE_GL
if (_method & METHOD_GL) {
d->glwidget->setInputSize(_inputSize);
}
#endif
return _inputSize.height();
}
bool QVideoStream::supportsFormat(VideoMethod method, ImageFormat format)
{
return (bool)(formatsForMethod(method) & format);
}
QVideo::ImageFormat QVideoStream::formatsForMethod(VideoMethod method)
{
switch(method) {
case METHOD_XSHM:
case METHOD_X11:
return _xFormat;
case METHOD_XV:
case METHOD_XVSHM:
return FORMAT_YUYV;
case METHOD_GL:
return FORMAT_BGR24;
default:
return FORMAT_NONE;
}
}
QVideo::ImageFormat QVideoStream::format() const
{
return _format;
}
bool QVideoStream::setFormat(ImageFormat format)
{
if(supportsFormat(_method, format)) {
_format = format;
return true;
} else {
return false;
}
}
int QVideoStream::displayFrame(const unsigned char *const img)
{
return displayFrame(img, 0, 0, _inputSize.width(), _inputSize.height());
}
int QVideoStream::displayFrame(const unsigned char *const img, int x, int y, int sw, int sh)
{
Q_ASSERT(_init);
if (!_init)
return -1;
Q_ASSERT(_methods & _method);
if (!(_methods & _method))
return -1;
switch (_method) {
case METHOD_XV:
case METHOD_XVSHM:
return d->xvdev->displayImage(_w, img,
_inputSize.width(), _inputSize.height(), x, y, sw, sh,
_size.width(), _size.height());
break;
case METHOD_XSHM:
#ifdef HAVE_XSHM
memcpy(d->xim->data,img,d->xim->bytes_per_line*d->xim->height);
XShmPutImage(_w->x11Display(), _w->winId(), d->gc, d->xim,
x, y,
0, 0,
sw, sh,
0);
XSync(_w->x11Display(), False);
break;
#else
return -1;
#endif
case METHOD_X11:
memcpy(d->xim->data, img, d->xim->bytes_per_line*d->xim->height);
XPutImage(_w->x11Display(), _w->winId(), d->gc, d->xim,
x, y,
0, 0,
sw, sh);
XSync(_w->x11Display(), False);
break;
case METHOD_GL:
#ifdef HAVE_GL
d->glwidget->display(img, x, y, sw, sh);
#endif
break;
default:
Q_ASSERT(0);
return -1;
}
return 0;
}
QVideoStream& QVideoStream::operator<<(const unsigned char *const img)
{
displayFrame(img);
return *this;
}
// ---------------------------------------------------------------------------------------
#ifdef HAVE_GL
QVideoStreamGLWidget::QVideoStreamGLWidget(TQWidget* parent, const char* name)
: TQGLWidget(TQGLFormat(TQGL::DoubleBuffer | TQGL::Rgba | TQGL::DirectRendering), parent, name),
_tex(0),
_w(parent),
_glfun(false)
{
kdDebug() << "QVideoStreamGLWidget::QVideoStreamGLWidget()" << endl;
connect(_w, TQT_SIGNAL(resized(int, int)),
this, TQT_SLOT(resize(int, int)));
topLevelWidget()->installEventFilter(this);
_glfunTimer = new TQTimer();
}
QVideoStreamGLWidget::~QVideoStreamGLWidget()
{
kdDebug() << "QVideoStreamGLWidget::~QVideoStreamGLWidget()" << endl;
delete _glfunTimer;
makeCurrent();
if(_tex != 0) {
glDeleteTextures(1, &_tex);
}
}
bool QVideoStreamGLWidget::eventFilter(TQObject*, TQEvent* e)
{
// For some reason, KeyPress does not work (yields 2), TQEvent::KeyPress is unknown... What the f...????
// I am too lazy to scan the header files for the reason.
if(e->type() == 6) {
TQKeyEvent* ke = static_cast<TQKeyEvent*>(e);
if(ke->key() == TQt::Key_Pause) {
_glfunTimer->start(500, true);
} else if (_glfunTimer->isActive() && (ke->key() == TQt::Key_Escape)) {
_glfun = !_glfun;
}
}
return false;
}
void QVideoStreamGLWidget::initializeGL()
{
kdDebug() << "QVideoStreamGLWidget::initializeGL()" << endl;
setAutoBufferSwap(false);
TQGLFormat f = format();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &_maxGL);
kdDebug() << "OpenGL capabilities (* = required):" << endl;
kdDebug() << " Valid context*: " << isValid() << endl;
kdDebug() << " DoubleBuffer*: " << f.doubleBuffer() << endl;
kdDebug() << " Depth: " << f.depth() << endl;
kdDebug() << " RGBA*: " << f.rgba() << endl;
kdDebug() << " Alpha: " << f.alpha() << endl;
kdDebug() << " Accum: " << f.accum() << endl;
kdDebug() << " Stencil: " << f.stencil() << endl;
kdDebug() << " Stereo: " << f.stereo() << endl;
kdDebug() << " DirectRendering*: " << f.directRendering() << endl;
kdDebug() << " Overlay: " << f.hasOverlay() << endl;
kdDebug() << " Plane: " << f.plane() << endl;
kdDebug() << " MAX_TEXTURE_SIZE: " << _maxGL << endl;
qglClearColor(TQt::black);
glShadeModel(GL_FLAT);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
_vul = TQPoint( 4, 10);
_vur = TQPoint(-8, 4);
_vll = TQPoint(10, -4);
_vlr = TQPoint(-8, -10);
}
void QVideoStreamGLWidget::resizeGL(int w, int h)
{
_sz = TQSize(w, h);
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, w, 0.0, h, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
_ul = TQPoint(0, 0);
_ur = TQPoint(w, 0);
_ll = TQPoint(0, h);
_lr = TQPoint(w, h);
}
void QVideoStreamGLWidget::setInputSize(const TQSize& sz)
{
makeCurrent();
_inputSize = sz;
int iw = _inputSize.width();
int ih = _inputSize.height();
if ( (iw > _maxGL) || (ih > _maxGL) ) {
kdWarning() << "QVideoStreamGLWidget::setInputSize(): Texture too large! maxGL: " << _maxGL << endl;
return;
}
// textures have power-of-two x,y dimensions
int i;
for (i = 0; iw >= (1 << i); i++)
;
_tw = (1 << i);
for (i = 0; ih >= (1 << i); i++)
;
_th = (1 << i);
// Generate texture
if(_tex != 0) {
glDeleteTextures(1, &_tex);
}
glGenTextures(1, &_tex);
glBindTexture(GL_TEXTURE_2D, _tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Blank texture
char* dummy = new char[_tw*_th*4];
memset(dummy, 128, _tw*_th*4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _tw, _th, 0,
GL_RGB, GL_UNSIGNED_BYTE, dummy);
delete[] dummy;
}
void QVideoStreamGLWidget::display(const unsigned char *const img, int x, int y, int sw, int sh)
{
makeCurrent();
// FIXME: Endianess - also support GL_RGB
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _inputSize.width(), _inputSize.height(),
GL_BGR, GL_UNSIGNED_BYTE, img);
// upper right coords
float ur_x = (float)(x + sw) / _tw;
float ur_y = (float)(y + sh) / _th;
// lower left coords
float ll_x = (float)(x) / _tw;
float ll_y = (float)(y) / _th;
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glBindTexture(GL_TEXTURE_2D, _tex);
if (!_glfun) {
glBegin(GL_QUADS);
glTexCoord2f(ll_x, ur_y); glVertex2i(0, 0 );
glTexCoord2f(ll_x, ll_y); glVertex2i(0, _sz.height());
glTexCoord2f(ur_x, ll_y); glVertex2i(_sz.width(), _sz.height());
glTexCoord2f(ur_x, ur_y); glVertex2i(_sz.width(), 0 );
glEnd();
} else {
calc(_ul, _vul);
calc(_ur, _vur);
calc(_ll, _vll);
calc(_lr, _vlr);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glTexCoord2f(0, y); glVertex2i(_ul.x(), _ul.y());
glTexCoord2f(0, 0); glVertex2i(_ll.x(), _ll.y());
glTexCoord2f(x, 0); glVertex2i(_lr.x(), _lr.y());
glTexCoord2f(x, y); glVertex2i(_ur.x(), _ur.y());
glEnd();
}
swapBuffers();
glDisable(GL_TEXTURE_2D);
}
void QVideoStreamGLWidget::calc(TQPoint& p, TQPoint& v)
{
p += v;
if(p.x() < 0) {
p.setX(-p.x());
v.setX(-v.x());
}
if(p.y() < 0) {
p.setY(-p.y());
v.setY(-v.y());
}
if(p.x() > _sz.width()) {
p.setX(_sz.width() - (p.x() - _sz.width()));
v.setX(-v.x());
}
if(p.y() > _sz.height()) {
p.setY(_sz.height() - (p.y() - _sz.height()));
v.setY(-v.y());
}
}
#endif
#include "qvideostream.moc"