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.
kmplayer/src/xvplayer.cpp

705 lines
25 KiB

/* This file is part of the KMPlayer application
Copyright (C) 2004 Koos Vriezen <koos.vriezen@xs4all.nl>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <config.h>
#include <dcopclient.h>
#include <tqcstring.h>
#include <tqtimer.h>
#include <tqfile.h>
#include <tqurl.h>
#include <tqthread.h>
#include <tqmutex.h>
#include <tqdom.h>
#include "kmplayer_backend.h"
#include "kmplayer_callback_stub.h"
#include "kmplayer_callback.h"
#include "xvplayer.h"
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/Xvlib.h>
static char configfile[2048];
static Display *display;
static KXVideoPlayer *xvapp;
static KMPlayer::Callback_stub * callback;
static Window wid;
static GC gc;
static bool window_created = true;
static bool wants_config;
static bool verbose;
static bool running;
static bool have_freq;
static bool xv_success;
static bool reset_xv_autopaint_colorkey;
static bool reset_xv_mute;
static int xvport;
static int xv_encoding = -1;
static TQString xv_norm;
static int xv_frequency;
static int screen;
static int movie_width;
static int movie_height;
static int current_volume;
static int tmp_volume;
static const int start_vol_timeout = 100;
static const int inc_vol_timeout = 20;
Atom xv_enc_atom;
Atom xv_hue_atom;
Atom xv_saturation_atom;
Atom xv_brightness_atom;
Atom xv_contrast_atom;
Atom xv_freq_atom;
Atom xv_volume_atom;
Atom xv_mute_atom;
Atom xv_autopaint_colorkey_atom;
enum {
limit_volume = 0,
limit_hue, limit_contrast, limit_brightness, limit_saturation,
limit_last
};
static struct Limit { int min; int max; } xv_limits [limit_last];
static TQString elmentry ("entry");
static TQString elmitem ("item");
static TQString attname ("name");
static TQString atttype ("type");
static TQString attdefault ("DEFAULT");
static TQString attvalue ("value");
//static TQString attstart ("START");
//static TQString attend ("END");
//static TQString valrange ("range");
//static TQString valnum ("num");
//static TQString valbool ("bool");
//static TQString valenum ("enum");
//static TQString valstring ("string");
static TQString valtree ("tree");
static TQByteArray config_buf;
extern "C" {
} // extern "C"
static void putVideo () {
XWindowAttributes attr;
XGetWindowAttributes (display, wid, &attr);
XvPutVideo (display, xvport, wid, gc, 0, 0, attr.width, attr.height, 0, 0, attr.width, attr.height);
}
using namespace KMPlayer;
Backend::Backend ()
: DCOPObject (TQCString ("Backend")) {
}
Backend::~Backend () {}
void Backend::setURL (TQString) {
}
void Backend::setSubTitleURL (TQString) {
}
void Backend::play (int) {
xvapp->play ();
}
void Backend::stop () {
TQTimer::singleShot (0, xvapp, TQT_SLOT (stop ()));
}
void Backend::pause () {
}
void Backend::seek (int, bool /*absolute*/) {
}
void Backend::hue (int h, bool) {
if (xv_limits[limit_hue].max > xv_limits[limit_hue].min)
xvapp->hue ((h + 100) * (xv_limits[limit_hue].max - xv_limits[limit_hue].min)/200 + xv_limits[limit_hue].min);
}
void Backend::saturation (int s, bool) {
if (xv_limits[limit_saturation].max > xv_limits[limit_saturation].min)
xvapp->saturation ((s + 100) * (xv_limits[limit_saturation].max - xv_limits[limit_saturation].min)/200 + xv_limits[limit_saturation].min);
}
void Backend::contrast (int c, bool) {
if (xv_limits[limit_contrast].max > xv_limits[limit_contrast].min)
xvapp->contrast ((c + 100)*(xv_limits[limit_contrast].max - xv_limits[limit_contrast].min)/200 + xv_limits[limit_contrast].min);
}
void Backend::brightness (int b, bool) {
if (xv_limits[limit_brightness].max > xv_limits[limit_brightness].min)
xvapp->brightness ((b + 100)*(xv_limits[limit_brightness].max - xv_limits[limit_brightness].min)/200 + xv_limits[limit_brightness].min);
}
void Backend::volume (int v, bool) {
if (xv_limits[limit_volume].max > xv_limits[limit_volume].min)
xvapp->volume (v*(xv_limits[limit_volume].max - xv_limits[limit_volume].min)/100 + xv_limits[limit_volume].min);
}
void Backend::frequency (int f) {
xvapp->frequency (f);
}
void Backend::setAudioLang (int, TQString) {
}
void Backend::setSubtitle (int, TQString) {
}
void Backend::quit () {
delete callback;
callback = 0L;
if (running)
stop ();
else
TQTimer::singleShot (0, tqApp, TQT_SLOT (quit ()));
}
bool updateConfigEntry (const TQString & name, const TQString & value) {
fprintf (stderr, "%s=%s\n", name.ascii (), (const char *) value.local8Bit ());
return true;
}
void Backend::setConfig (TQByteArray data) {
TQString err;
int line, column;
TQDomDocument dom;
if (dom.setContent (data, false, &err, &line, &column)) {
if (dom.childNodes().length() == 1) {
for (TQDomNode node = dom.firstChild().firstChild();
!node.isNull ();
node = node.nextSibling ()) {
TQDomNamedNodeMap attr = node.attributes ();
updateConfigEntry (attr.namedItem (attname).nodeValue (),
attr.namedItem (attvalue).nodeValue ());
}
} else
err = TQString ("invalid data");
}
if (callback)
callback->errorMessage (0, err);
}
bool Backend::isPlaying () {
return running;
}
KXVideoPlayer::KXVideoPlayer (int _argc, char ** _argv)
: TQApplication (_argc, _argv, false), mute_timer (0) {
}
void KXVideoPlayer::init () {
int xpos = 0;
int ypos = 0;
int width = 320;
int height = 200;
XLockDisplay(display);
if (window_created)
wid = XCreateSimpleWindow(display, XDefaultRootWindow(display),
xpos, ypos, width, height, 1, 0, 0);
if (!callback)
TQTimer::singleShot (10, this, TQT_SLOT (play ()));
XSelectInput (display, wid,
(PointerMotionMask | ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask)); // | SubstructureNotifyMask));
XvAdaptorInfo * ai;
unsigned int adaptors;
xv_success = true;
TQDomDocument doc;
TQDomElement root = doc.createElement (TQString ("document"));
if (XvQueryAdaptors (display, XDefaultRootWindow (display), &adaptors, &ai) == Success) {
TQDomElement elm = doc.createElement (elmentry);
elm.setAttribute (attname, TQString ("XVideo"));
elm.setAttribute (atttype, valtree);
for (unsigned i = 0; i < adaptors; i++) {
if ((ai[i].type & XvInputMask) &&
(ai[i].type & XvVideoMask) &&
ai[i].base_id > 0) {
int port = ai[i].base_id;
fprintf (stderr, "xvport %d\n", port);
bool freq_found = false;
XvAttribute *attributes = 0L;
int nr_attr, cur_val;
attributes = XvQueryPortAttributes (display, port, &nr_attr);
if (attributes) {
for (int i = 0; i < nr_attr; i++) {
if (!strcmp (attributes[i].name, "XV_FREQ"))
freq_found = true;
Atom atom = XInternAtom (display, attributes[i].name, false);
fprintf (stderr, "%s[%d] (%d .. %d)", attributes[i].name, ( int ) atom, attributes[i].min_value, attributes[i].max_value);
if ((attributes[i].flags & XvGettable) && XvGetPortAttribute (display, port, atom, &cur_val) == Success)
fprintf (stderr, " current: %d", cur_val);
fprintf (stderr, "\n");
}
XFree(attributes);
}
if (!xvport && ((xv_frequency > 0) == freq_found)) {
fprintf (stderr, "using xvport %d\n", port);
xvport = port;
}
if (xvport == port)
have_freq = freq_found;
XvEncodingInfo * encodings = 0L;
unsigned nr_encode;
XvQueryEncodings (display, port, &nr_encode, &encodings);
if (encodings) {
TQDomElement port_item = doc.createElement (TQString("Port"));
port_item.setAttribute (attvalue, TQString::number (port));
if (freq_found)
port_item.setAttribute (TQString("FREQ"), TQString("1"));
for (unsigned i = 0; i < nr_encode; i++) {
if (strcmp (encodings[i].name, "XV_IMAGE")) {
if (xvport == port && xv_encoding < 0 && !xv_norm.isEmpty () && TQString (encodings[i].name).lower ().startsWith(xv_norm.lower ()))
xv_encoding = encodings[i].encoding_id;
if (port == xvport && encodings[i].encoding_id == xv_encoding) {
movie_width = encodings[i].width;
movie_height = encodings[i].height;
}
TQDomElement item = doc.createElement (TQString ("Input"));
item.setAttribute (attvalue, TQString::number (encodings[i].encoding_id));
item.setAttribute (attname, TQString (encodings[i].name));
port_item.appendChild (item);
fprintf (stderr, " encoding: %d %s\n", ( int ) encodings[i].encoding_id, encodings[i].name);
}
}
elm.appendChild (port_item);
XvFreeEncodingInfo (encodings);
}
}
}
root.appendChild (elm);
XvFreeAdaptorInfo(ai);
}
doc.appendChild (root);
TQCString exp = doc.toCString ();
config_buf = exp;
//fprintf (stderr, "%s\n", (const char *)exp);
config_buf.resize (exp.length ()); // strip terminating \0
if (xvport <= 0) {
fprintf (stderr, "no valid xvport found\n");
xv_success = false;
return;
}
if (window_created) {
fprintf (stderr, "map %lu\n", wid);
if (movie_width > 0 && movie_height > 0)
XResizeWindow (display, wid, movie_width, movie_height);
XMapRaised(display, wid);
XSync(display, False);
}
XUnlockDisplay(display);
if (!xv_success)
fprintf (stderr, "Failed to init %d port\n", xvport);
}
KXVideoPlayer::~KXVideoPlayer () {
if (window_created) {
XLockDisplay (display);
fprintf (stderr, "unmap %lu\n", wid);
XUnmapWindow (display, wid);
XDestroyWindow(display, wid);
XSync (display, False);
XUnlockDisplay (display);
}
xvapp = 0L;
}
void getConfigEntries (TQByteArray & buf) {
TQDomDocument doc;
TQDomElement root = doc.createElement (TQString ("document"));
doc.appendChild (root);
TQCString exp = doc.toCString ();
buf = exp;
buf.resize (exp.length ()); // strip terminating \0
}
void KXVideoPlayer::play () {
fprintf (stderr, "play xv://%d:%d/%d\n", xvport, xv_encoding, xv_frequency);
if (!xv_success)
return;
if (callback && movie_width > 0 && movie_height > 0)
callback->movieParams (0, movie_width, movie_height, 1.0*movie_width/movie_height, TQStringList (), TQStringList ());
XLockDisplay (display);
if (!running && XvGrabPort (display, xvport, CurrentTime) == Success) {
gc = XCreateGC (display, wid, 0, NULL);
XvSelectPortNotify (display, xvport, 1);
XvSelectVideoNotify (display, wid, 1);
int nr, cur_val;
if (XvGetPortAttribute (display, xvport, xv_autopaint_colorkey_atom, &cur_val) == Success && cur_val == 0) {
fprintf (stderr, "XV_AUTOPAINT_COLORKEY is 0\n");
XvSetPortAttribute (display, xvport, xv_autopaint_colorkey_atom, 1);
reset_xv_autopaint_colorkey = true;
}
XvAttribute *attributes = XvQueryPortAttributes (display, xvport, &nr);
if (attributes) {
for (int i = 0; i < nr; i++) {
Limit * limit = 0;
Atom atom = XInternAtom (display, attributes[i].name, false);
if (atom == xv_volume_atom) {
limit = xv_limits + limit_volume;
XvGetPortAttribute (display, xvport,
xv_volume_atom, &current_volume);
} else if (atom == xv_hue_atom) {
limit = xv_limits + limit_hue;
} else if (atom == xv_saturation_atom) {
limit = xv_limits + limit_saturation;
} else if (atom == xv_brightness_atom) {
limit = xv_limits + limit_brightness;
} else if (atom == xv_contrast_atom) {
limit = xv_limits + limit_contrast;
} else
continue;
limit->min = attributes[i].min_value;
limit->max = attributes[i].max_value;
}
XFree (attributes);
}
if (xv_frequency > 0)
XvSetPortAttribute (display, xvport, xv_freq_atom, int (1.0*xv_frequency/62.5));
if (xv_encoding >= 0)
XvSetPortAttribute (display, xvport, xv_enc_atom, xv_encoding);
if (XvGetPortAttribute (display, xvport, xv_mute_atom, &cur_val) ==
Success && cur_val == 1) {
fprintf (stderr, "XV_MUTE is 1\n");
if (xv_limits[limit_volume].min != xv_limits[limit_volume].max) {
tmp_volume = xv_limits[limit_volume].min;
XvSetPortAttribute(display, xvport, xv_volume_atom, tmp_volume);
mute_timer = startTimer (start_vol_timeout);
}
XvSetPortAttribute (display, xvport, xv_mute_atom, 0);
reset_xv_mute = true;
}
//XvGetVideo (..
running = true;
}
if (running) {
putVideo ();
if (callback) {
callback->playing ();
callback->statusMessage ((int) KMPlayer::Callback::stat_hasvideo, TQString ());
}
}
XUnlockDisplay (display);
}
void KXVideoPlayer::stop () {
if (mute_timer) {
killTimer (mute_timer);
mute_timer = 0;
}
if (running) {
running = false;
XLockDisplay (display);
XvStopVideo (display, xvport, wid);
if (reset_xv_autopaint_colorkey) {
XvSetPortAttribute (display, xvport, xv_autopaint_colorkey_atom, 0);
reset_xv_autopaint_colorkey = false;
}
if (reset_xv_mute) {
XvSetPortAttribute (display, xvport, xv_mute_atom, 1);
reset_xv_mute = false;
}
XvUngrabPort (display, xvport, CurrentTime);
XFreeGC (display, gc);
XClearArea (display, wid, 0, 0, 0, 0, true);
XUnlockDisplay (display);
}
if (callback)
callback->finished ();
else
TQTimer::singleShot (0, tqApp, TQT_SLOT (quit ()));
}
void KXVideoPlayer::finished () {
TQTimer::singleShot (10, this, TQT_SLOT (stop ()));
}
void KXVideoPlayer::saturation (int val) {
XLockDisplay(display);
XvSetPortAttribute (display, xvport, xv_saturation_atom, val);
XFlush (display);
XUnlockDisplay(display);
}
void KXVideoPlayer::hue (int val) {
XLockDisplay(display);
XvSetPortAttribute (display, xvport, xv_hue_atom, val);
XFlush (display);
XUnlockDisplay(display);
}
void KXVideoPlayer::contrast (int val) {
XLockDisplay(display);
XvSetPortAttribute (display, xvport, xv_contrast_atom, val);
XFlush (display);
XUnlockDisplay(display);
}
void KXVideoPlayer::brightness (int val) {
XLockDisplay(display);
XvSetPortAttribute (display, xvport, xv_brightness_atom, val);
XFlush (display);
XUnlockDisplay(display);
}
void KXVideoPlayer::volume (int val) {
current_volume = val;
if (mute_timer)
return;
XLockDisplay(display);
XvSetPortAttribute (display, xvport, xv_volume_atom, val);
XFlush (display);
XUnlockDisplay(display);
}
void KXVideoPlayer::frequency (int val) {
// this doesn't work, changing frequency kills audio for me
if (mute_timer) {
killTimer (mute_timer);
mute_timer = 0;
}
xv_frequency = val;
if (running && have_freq) {
XLockDisplay(display);
if (xv_limits[limit_volume].min != xv_limits[limit_volume].max) {
tmp_volume = xv_limits[limit_volume].min;
XvSetPortAttribute (display, xvport, xv_volume_atom, tmp_volume);
mute_timer = startTimer (start_vol_timeout);
XFlush (display);
XvSetPortAttribute (display, xvport, xv_mute_atom, 0);
}
XvSetPortAttribute (display, xvport, xv_freq_atom, int (1.0*val/6.25));
XFlush (display);
XUnlockDisplay(display);
}
}
void KXVideoPlayer::saveState (TQSessionManager & sm) {
if (callback)
sm.setRestartHint (TQSessionManager::RestartNever);
}
void KXVideoPlayer::timerEvent (TQTimerEvent * e) {
if (e->timerId () == mute_timer) {
int step = (current_volume - xv_limits[limit_volume].min) / 20;
if (step > 0 && tmp_volume == xv_limits[limit_volume].min) {
killTimer (mute_timer);
mute_timer = startTimer (inc_vol_timeout);
}
tmp_volume += step;
if (tmp_volume >= current_volume || step <= 0) {
tmp_volume = current_volume;
killTimer (mute_timer);
mute_timer = 0;
}
XLockDisplay(display);
XvSetPortAttribute (display, xvport, xv_volume_atom, tmp_volume);
XFlush (display);
XUnlockDisplay(display);
} else
killTimer (e->timerId ());
}
class XEventThread : public TQThread {
protected:
void run () {
Time prev_click_time = 0;
int prev_click_x = 0;
int prev_click_y = 0;
while (true) {
XEvent xevent;
XNextEvent(display, &xevent);
switch(xevent.type) {
case ClientMessage:
if (xevent.xclient.format == 8 &&
!strncmp(xevent.xclient.data.b, "quit_now", 8)) {
fprintf(stderr, "request quit\n");
return;
}
break;
case KeyPress: {
XKeyEvent kevent;
KeySym ksym;
char kbuf[256];
int len;
kevent = xevent.xkey;
XLockDisplay(display);
len = XLookupString(&kevent, kbuf, sizeof(kbuf), &ksym, NULL);
XUnlockDisplay(display);
fprintf(stderr, "keypressed 0x%x 0x%x\n", ( int ) kevent.keycode, ( int ) ksym);
switch (ksym) {
case XK_q:
case XK_Q:
xvapp->lock ();
xvapp->stop ();
xvapp->unlock ();
break;
}
break;
}
case Expose:
if(xevent.xexpose.count != 0 || xevent.xexpose.window != wid)
break;
break;
case ConfigureNotify:
if (::running)
putVideo ();
break;
case XvVideoNotify:
fprintf (stderr, "xvevent %lu\n", ((XvEvent*)&xevent)->xvvideo.reason);
break;
case ButtonPress: {
XButtonEvent *bev = (XButtonEvent *) &xevent;
int dx = prev_click_x - bev->x;
int dy = prev_click_y - bev->y;
if (bev->time - prev_click_time < 400 &&
(dx * dx + dy * dy) < 25) {
xvapp->lock ();
if (callback)
callback->toggleFullScreen ();
xvapp->unlock ();
}
prev_click_time = bev->time;
prev_click_x = bev->x;
prev_click_y = bev->y;
break;
}
default:
if (xevent.type < LASTEvent) {
//fprintf (stderr, "event %d\n", xevent.type);
}
}
}
}
};
int main(int argc, char **argv) {
if (!XInitThreads ()) {
fprintf (stderr, "XInitThreads () failed\n");
return 1;
}
display = XOpenDisplay(NULL);
screen = XDefaultScreen(display);
unsigned int ver, rel, req, evb, err;
if (XvQueryExtension (display, &ver, &rel, &req, &evb, &err) != Success) {
fprintf (stderr, "XVideo not supported on display\n");
XCloseDisplay (display);
return 1;
}
xv_enc_atom = XInternAtom (display, "XV_ENCODING", false);
xv_hue_atom = XInternAtom (display, "XV_HUE", false);
xv_saturation_atom = XInternAtom (display, "XV_SATURATION", false);
xv_brightness_atom = XInternAtom (display, "XV_BRIGHTNESS", false);
xv_contrast_atom = XInternAtom (display, "XV_CONTRAST", false);
xv_freq_atom = XInternAtom (display, "XV_FREQ", false);
xv_volume_atom = XInternAtom (display, "XV_VOLUME", false);
xv_mute_atom = XInternAtom (display, "XV_MUTE", false);
xv_autopaint_colorkey_atom = XInternAtom (display, "XV_AUTOPAINT_COLORKEY", false);
xvapp = new KXVideoPlayer (argc, argv);
for(int i = 1; i < argc; i++) {
if (!strcmp (argv [i], "-port")) {
xvport = strtol (argv [++i], 0L, 10);
} else if (!strcmp (argv [i], "-wid") || !strcmp (argv [i], "-window-id")) {
wid = atol (argv [++i]);
window_created = false;
} else if (!strcmp (argv [i], "-root")) {
wid = XDefaultRootWindow (display);
window_created = false;
} else if (!strcmp (argv [i], "-window")) {
;
} else if (!strcmp (argv [i], "-v")) {
verbose = true;
} else if (!strcmp (argv [i], "-c")) {
wants_config = true;
} else if (!strcmp (argv [i], "-f") && i < argc - 1) {
strncpy (configfile, argv [++i], sizeof (configfile));
configfile[sizeof (configfile) - 1] = 0;
} else if (!strcmp (argv [i], "-cb")) {
TQString str = argv [++i];
int pos = str.find ('/');
if (pos > -1) {
fprintf (stderr, "callback is %s %s\n", str.left (pos).ascii (), str.mid (pos + 1).ascii ());
callback = new KMPlayer::Callback_stub
(str.left (pos).ascii (), str.mid (pos + 1).ascii ());
}
} else if (!strcmp (argv [i], "-enc")) {
xv_encoding = strtol (argv [++i], 0L, 10);
} else if (!strcmp (argv [i], "-norm")) {
xv_norm = argv [++i];
} else if (!strcmp (argv [i], "-freq")) {
xv_frequency = strtol (argv [++i], 0L, 10);
} else {
fprintf (stderr, "usage: %s [-port <xv port>] [-enc <encoding>] [-freq <frequency>] [-f <config file>] [-v] [(-wid|-window-id) <window>] [(-root|-window)] [-cb <DCOP callback name> [-c]]\n", argv[0]);
delete xvapp;
return 1;
}
}
DCOPClient dcopclient;
dcopclient.registerAs ("kxvideoplayer");
Backend player;
XEventThread * eventThread = new XEventThread;
eventThread->start ();
xvapp->init ();
if (callback)
callback->started (dcopclient.appId (), config_buf);
xvapp->exec ();
XLockDisplay(display);
XClientMessageEvent ev = {
ClientMessage, 0, true, display, wid,
XInternAtom (display, "XVIDEO", false), 8, {"quit_now"}
};
XSendEvent (display, wid, false, StructureNotifyMask, (XEvent *) & ev);
XFlush (display);
XUnlockDisplay(display);
eventThread->wait (500);
delete eventThread;
xvapp->stop ();
delete xvapp;
fprintf (stderr, "closing display\n");
XCloseDisplay (display);
fprintf (stderr, "done\n");
return 0;
}
#include "xvplayer.moc"