/*
This file is part of KDE / aRts ( Noatun ) - xine integration
Copyright ( C ) 2002 - 2003 Ewald Snel < ewald @ rambo . its . tudelft . nl >
This library is free software ; you can redistribute it and / or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation ; either
version 2.1 of the License , or ( at your option ) any later version .
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <strings.h>
# include <math.h>
# include <sys/time.h>
# include <audiosubsys.h>
# include <convert.h>
# include <debug.h>
# include "xinePlayObject_impl.h"
# ifndef HAVE_XSHMGETEVENTBASE
extern " C " {
extern int XShmGetEventBase ( Display * ) ;
} ;
# endif
# define TIMEOUT 15 // 15 seconds
using namespace Arts ;
// Global xine pointer
static xine_t * xine_shared = NULL ;
static pthread_mutex_t xine_mutex = PTHREAD_MUTEX_INITIALIZER ;
static pthread_cond_t xine_cond = PTHREAD_COND_INITIALIZER ;
static int xineRefCount = 0 ;
static bool xineForceXShm = false ;
static void xine_init_routine ( )
{
const char * id ;
char cfgFileName [ 272 ] ;
xine_shared = ( xine_t * ) xine_new ( ) ;
snprintf ( cfgFileName , 272 , " %s/.xine/config " , getenv ( " HOME " ) ) ;
xine_config_load ( xine_shared , ( const char * ) cfgFileName ) ;
// Check default video output driver
id = xine_config_register_string ( xine_shared , " video.driver " ,
" auto " , " video driver to use " ,
NULL , 10 , NULL , NULL ) ;
xineForceXShm = ( id & & ! strcasecmp ( id , " XShm " ) ) ;
xine_init ( xine_shared ) ;
}
static void * xine_timeout_routine ( void * )
{
pthread_mutex_lock ( & xine_mutex ) ;
while ( xine_shared ! = 0 )
{
if ( xineRefCount = = 0 )
{
struct timespec ts ;
struct timeval tv ;
gettimeofday ( & tv , 0 ) ;
ts . tv_sec = tv . tv_sec ;
ts . tv_nsec = tv . tv_usec * 1000 ;
ts . tv_sec + = TIMEOUT ;
if ( pthread_cond_timedwait ( & xine_cond , & xine_mutex , & ts ) ! = 0 & &
xineRefCount = = 0 )
{
xine_exit ( xine_shared ) ;
xine_shared = NULL ;
break ;
}
}
else
{
pthread_cond_wait ( & xine_cond , & xine_mutex ) ;
}
}
pthread_mutex_unlock ( & xine_mutex ) ;
return NULL ;
}
static xine_t * xine_shared_init ( )
{
pthread_mutex_lock ( & xine_mutex ) ;
+ + xineRefCount ;
if ( xine_shared = = 0 )
{
pthread_t thread ;
xine_init_routine ( ) ;
if ( pthread_create ( & thread , NULL , xine_timeout_routine , NULL ) = = 0 )
{
pthread_detach ( thread ) ;
}
}
else
{
pthread_cond_signal ( & xine_cond ) ;
}
pthread_mutex_unlock ( & xine_mutex ) ;
return xine_shared ;
}
static void xine_shared_exit ( xine_t * )
{
pthread_mutex_lock ( & xine_mutex ) ;
if ( - - xineRefCount = = 0 )
{
pthread_cond_signal ( & xine_cond ) ;
}
pthread_mutex_unlock ( & xine_mutex ) ;
}
int ao_fifo_arts_delay ( )
{
return ( int ) ( 1000 * Arts : : AudioSubSystem : : the ( ) - > outputDelay ( ) ) ;
}
static int xine_play_object_x_errhandler ( Display * dpy , XErrorEvent * err ) {
if ( err - > error_code = = BadWindow ) {
return 0 ;
}
else if ( err - > error_code = = BadMatch & &
err - > request_code = = 42 /* X_SetInputFocus */ ) {
return 0 ;
}
char errstr [ 256 ] ;
XGetErrorText ( dpy , err - > error_code , errstr , 256 ) ;
arts_warning ( " X Error: %s %d \n "
" Major opcode: %d \n "
" Minor opcode: %d \n "
" Resource id: 0x%lx " ,
errstr , err - > error_code ,
err - > request_code ,
err - > minor_code ,
err - > resourceid ) ;
return 0 ;
}
static int xine_play_object_xio_errhandler ( Display * ) {
arts_fatal ( " Fatal IO error: client killed " ) ;
return 0 ;
}
xinePlayObject_impl : : xinePlayObject_impl ( bool audioOnly )
: mrl ( " " ) , xine ( 0 ) , stream ( 0 ) , queue ( 0 ) , ao_port ( 0 ) , vo_port ( 0 ) , audioOnly ( audioOnly )
{
if ( ! audioOnly ) {
XInitThreads ( ) ;
if ( ! ( display = XOpenDisplay ( NULL ) ) ) {
arts_fatal ( " could not open X11 display " ) ;
}
// Install default error handlers
XSetErrorHandler ( xine_play_object_x_errhandler ) ;
XSetIOErrorHandler ( xine_play_object_xio_errhandler ) ;
XFlush ( display ) ;
// Create a special window for uninterrupted X11 communication
xcomWindow = XCreateSimpleWindow ( display , DefaultRootWindow ( display ) , 0 , 0 , 1 , 1 , 0 , 0 , 0 ) ;
XSelectInput ( display , xcomWindow , ExposureMask ) ;
}
pthread_mutex_init ( & mutex , 0 ) ;
if ( ! audioOnly ) {
// Initialize X11 properties
xcomAtomQuit = XInternAtom ( display , " VPO_INTERNAL_EVENT " , False ) ;
xcomAtomResize = XInternAtom ( display , " VPO_RESIZE_NOTIFY " , False ) ;
screen = DefaultScreen ( display ) ;
shmCompletionType = ( XShmQueryExtension ( display ) = = True )
? XShmGetEventBase ( display ) + ShmCompletion : - 1 ;
width = 0 ;
height = 0 ;
dscbTimeOut = 0 ;
// Initialize xine visual structure
visual . display = display ;
visual . screen = screen ;
visual . d = xcomWindow ;
visual . dest_size_cb = & dest_size_cb ;
visual . frame_output_cb = & frame_output_cb ;
visual . user_data = this ;
}
// Initialize audio and video details
Arts : : SoundServerV2 server = Arts : : Reference ( " global:Arts_SoundServerV2 " ) ;
audio . sample_rate = 0 ;
audio . num_channels = 0 ;
audio . bits_per_sample = 0 ;
flpos = 0.0 ;
if ( ! audioOnly ) {
if ( pthread_create ( & thread , 0 , pthread_start_routine , this ) ) {
arts_fatal ( " could not create thread " ) ;
}
}
}
xinePlayObject_impl : : ~ xinePlayObject_impl ( )
{
XEvent event ;
halt ( ) ;
// Send stop event to thread (X11 client message)
memset ( & event , 0 , sizeof ( event ) ) ;
event . type = ClientMessage ;
event . xclient . window = xcomWindow ;
event . xclient . message_type = xcomAtomQuit ;
event . xclient . format = 32 ;
if ( ! audioOnly )
{
XSendEvent ( display , xcomWindow , True , 0 , & event ) ;
XFlush ( display ) ;
// Wait for the thread to die
pthread_join ( thread , 0 ) ;
}
// Destroy stream, xine and related resources
if ( stream ! = 0 )
{
halt ( ) ;
xine_event_dispose_queue ( queue ) ;
xine_dispose ( stream ) ;
xine_close_audio_driver ( xine , ao_port ) ;
xine_close_video_driver ( xine , vo_port ) ;
}
if ( xine ! = 0 )
{
xine_shared_exit ( xine ) ;
}
pthread_mutex_destroy ( & mutex ) ;
if ( ! audioOnly )
{
XSync ( display , False ) ;
XDestroyWindow ( display , xcomWindow ) ;
XCloseDisplay ( display ) ;
}
}
bool xinePlayObject_impl : : loadMedia ( const string & url )
{
bool result = false ;
pthread_mutex_lock ( & mutex ) ;
mrl = " " ;
if ( stream = = 0 )
{
if ( xine = = 0 )
{
xine = xine_shared_init ( ) ;
}
ao_port = init_audio_out_plugin ( xine , & audio , & ao_driver ) ;
if ( xineForceXShm & & ! audioOnly )
{
vo_port = xine_open_video_driver ( xine , " XShm " ,
XINE_VISUAL_TYPE_X11 ,
( void * ) & visual ) ;
}
if ( vo_port = = 0 & & ! audioOnly )
{
vo_port = xine_open_video_driver ( xine , " Xv " ,
XINE_VISUAL_TYPE_X11 ,
( void * ) & visual ) ;
}
if ( vo_port = = 0 & & ! audioOnly )
{
vo_port = xine_open_video_driver ( xine , " XShm " ,
XINE_VISUAL_TYPE_X11 ,
( void * ) & visual ) ;
}
if ( vo_port = = 0 & & ! audioOnly )
{
vo_port = xine_open_video_driver ( xine , " OpenGL " ,
XINE_VISUAL_TYPE_X11 ,
( void * ) & visual ) ;
}
if ( vo_port = = 0 )
{
vo_port = xine_open_video_driver ( xine , 0 ,
XINE_VISUAL_TYPE_NONE , 0 ) ;
}
if ( ao_port ! = 0 & & vo_port ! = 0 )
{
stream = xine_stream_new ( xine , ao_port , vo_port ) ;
if ( stream ! = 0 )
{
xine_set_param ( stream , XINE_PARAM_AUDIO_CHANNEL_LOGICAL , 0 ) ;
xine_set_param ( stream , XINE_PARAM_SPU_CHANNEL , - 1 ) ;
queue = xine_event_new_queue ( stream ) ;
xine_event_create_listener_thread ( queue , xine_handle_event , this ) ;
}
}
if ( stream = = 0 )
{
if ( ao_port ! = 0 )
{
xine_close_audio_driver ( xine , ao_port ) ;
ao_port = 0 ;
}
if ( vo_port ! = 0 )
{
xine_close_video_driver ( xine , vo_port ) ;
vo_port = 0 ;
}
}
}
if ( stream ! = 0 )
{
if ( xine_get_status ( stream ) = = XINE_STATUS_PLAY )
{
ao_fifo_clear ( ao_driver , 2 ) ;
xine_stop ( stream ) ;
clearWindow ( ) ;
}
if ( ( result = xine_open ( stream , url . c_str ( ) ) ) )
{
mrl = url ;
}
streamLength = 0 ;
streamPosition = 0 ;
width = 0 ;
height = 0 ;
}
pthread_mutex_unlock ( & mutex ) ;
return result ;
}
string xinePlayObject_impl : : description ( )
{
return " xine aRts plugin " ;
}
poTime xinePlayObject_impl : : currentTime ( )
{
poTime time ;
int pos_time ;
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 & & ! mrl . empty ( ) )
{
if ( xine_get_pos_length ( stream , 0 , & pos_time , 0 ) )
{
streamPosition = pos_time ;
}
else
{
pos_time = streamPosition ;
}
time . seconds = pos_time / 1000 ;
time . ms = pos_time % 1000 ;
}
else
{
time . seconds = 0 ;
time . ms = 0 ;
}
pthread_mutex_unlock ( & mutex ) ;
return time ;
}
poTime xinePlayObject_impl : : overallTime ( )
{
poTime time ;
int length_time ;
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 & & ! mrl . empty ( ) )
{
if ( xine_get_pos_length ( stream , 0 , 0 , & length_time ) )
{
streamLength = length_time ;
}
else
{
length_time = streamLength ;
}
if ( length_time < = 0 )
{
length_time = 1 ;
}
time . seconds = length_time / 1000 ;
time . ms = length_time % 1000 ;
}
else
{
time . seconds = 0 ;
time . ms = 1 ;
}
pthread_mutex_unlock ( & mutex ) ;
return time ;
}
poCapabilities xinePlayObject_impl : : capabilities ( )
{
int n ;
pthread_mutex_lock ( & mutex ) ;
n = ( stream = = 0 ) ? 0 : xine_get_stream_info ( stream , XINE_STREAM_INFO_SEEKABLE ) ;
pthread_mutex_unlock ( & mutex ) ;
return static_cast < poCapabilities > ( capPause | ( ( n = = 0 ) ? 0 : capSeek ) ) ;
}
string xinePlayObject_impl : : mediaName ( )
{
return mrl ;
}
poState xinePlayObject_impl : : state ( )
{
poState state ;
pthread_mutex_lock ( & mutex ) ;
if ( stream = = 0 | | xine_get_status ( stream ) ! = XINE_STATUS_PLAY )
state = posIdle ;
else if ( xine_get_param ( stream , XINE_PARAM_SPEED ) = = XINE_SPEED_PAUSE )
state = posPaused ;
else
state = posPlaying ;
pthread_mutex_unlock ( & mutex ) ;
return state ;
}
void xinePlayObject_impl : : play ( )
{
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 )
{
if ( xine_get_status ( stream ) = = XINE_STATUS_PLAY )
{
if ( xine_get_param ( stream , XINE_PARAM_SPEED ) = = XINE_SPEED_PAUSE )
{
xine_set_param ( stream , XINE_PARAM_SPEED , XINE_SPEED_NORMAL ) ;
}
}
else if ( ! mrl . empty ( ) )
{
xine_play ( stream , 0 , 0 ) ;
}
}
pthread_mutex_unlock ( & mutex ) ;
}
void xinePlayObject_impl : : halt ( )
{
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 & & xine_get_status ( stream ) = = XINE_STATUS_PLAY )
{
ao_fifo_clear ( ao_driver , 2 ) ;
xine_stop ( stream ) ;
clearWindow ( ) ;
streamLength = 0 ;
streamPosition = 0 ;
}
pthread_mutex_unlock ( & mutex ) ;
}
void xinePlayObject_impl : : seek ( const class poTime & t )
{
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 & & xine_get_status ( stream ) = = XINE_STATUS_PLAY )
{
int seekPosition = ( 1000 * t . seconds ) + t . ms ;
int paused = ( xine_get_param ( stream , XINE_PARAM_SPEED ) = = XINE_SPEED_PAUSE ) ;
ao_fifo_clear ( ao_driver , 1 ) ;
if ( xine_play ( stream , 0 , seekPosition ) )
{
if ( seekPosition > = 0 & & seekPosition < = streamLength )
{
streamPosition = seekPosition ;
}
}
if ( paused )
{
xine_set_param ( stream , XINE_PARAM_SPEED , XINE_SPEED_PAUSE ) ;
}
ao_fifo_clear ( ao_driver , 0 ) ;
}
pthread_mutex_unlock ( & mutex ) ;
}
void xinePlayObject_impl : : pause ( )
{
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 & & xine_get_status ( stream ) = = XINE_STATUS_PLAY )
{
ao_fifo_clear ( ao_driver , 1 ) ;
xine_set_param ( stream , XINE_PARAM_SPEED , XINE_SPEED_PAUSE ) ;
}
pthread_mutex_unlock ( & mutex ) ;
}
void xinePlayObject_impl : : calculateBlock ( unsigned long samples )
{
unsigned int skip , received = 0 , converted = 0 , xSamples = 0 ;
unsigned char * buffer ;
double speed = 1.0 ;
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 )
{
// Calculate resampling parameters
speed = ( double ) audio . sample_rate / samplingRateFloat ;
xSamples = ( unsigned int ) ( ( double ) samples * speed + 8.0 ) ;
received = ao_fifo_read ( ao_driver , & buffer , xSamples ) ;
}
pthread_mutex_unlock ( & mutex ) ;
// Convert samples and fill gaps with zeroes
if ( received )
{
converted = uni_convert_stereo_2float ( samples , buffer , received ,
audio . num_channels ,
audio . bits_per_sample ,
left , right , speed , flpos ) ;
flpos + = ( double ) converted * speed ;
skip = ( int ) floor ( flpos ) ;
skip = ( received < ( xSamples - 8 ) ) ? ( xSamples - 8 ) : skip ;
flpos = flpos - floor ( flpos ) ;
ao_fifo_flush ( ao_driver , skip ) ;
}
for ( unsigned long i = converted ; i < samples ; i + + )
{
left [ i ] = 0 ;
right [ i ] = 0 ;
}
}
void xinePlayObject_impl : : xineEvent ( const xine_event_t & event )
{
if ( event . type = = XINE_EVENT_UI_PLAYBACK_FINISHED )
{
clearWindow ( ) ;
}
}
void xinePlayObject_impl : : clearWindow ( )
{
if ( audioOnly ) return ;
Window root ;
unsigned int u , w , h ;
int x , y , screen ;
XLockDisplay ( display ) ;
screen = DefaultScreen ( display ) ;
XGetGeometry ( display , visual . d , & root , & x , & y , & w , & h , & u , & u ) ;
XSetForeground ( display , DefaultGC ( display , screen ) ,
BlackPixel ( display , screen ) ) ;
XFillRectangle ( display , visual . d ,
DefaultGC ( display , screen ) , x , y , w , h ) ;
XUnlockDisplay ( display ) ;
}
void xinePlayObject_impl : : frameOutput ( int & x , int & y ,
int & width , int & height , double & ratio ,
int displayWidth , int displayHeight ,
double displayPixelAspect , bool dscb )
{
if ( audioOnly ) return ;
Window child , root ;
unsigned int u ;
int n ;
XLockDisplay ( display ) ;
XGetGeometry ( display , visual . d , & root , & n , & n ,
( unsigned int * ) & width , ( unsigned int * ) & height , & u , & u ) ;
if ( ! dscb )
{
XTranslateCoordinates ( display , visual . d , root , 0 , 0 , & x , & y , & child ) ;
}
// Most displays use (nearly) square pixels
ratio = 1.0 ;
// Correct for display pixel aspect
if ( displayPixelAspect < 1.0 )
{
displayHeight = ( int ) ( ( displayHeight / displayPixelAspect ) + .5 ) ;
}
else
{
displayWidth = ( int ) ( ( displayWidth * displayPixelAspect ) + .5 ) ;
}
if ( dscb | | dscbTimeOut = = 0 | | - - dscbTimeOut = = 0 )
{
// Notify client of new display size
if ( displayWidth ! = this - > width | | displayHeight ! = this - > height )
{
this - > width = displayWidth ;
this - > height = displayHeight ;
resizeNotify ( ) ;
}
// Reset 'seen dest_size_cb' time out
if ( dscb )
{
dscbTimeOut = 25 ;
}
}
XUnlockDisplay ( display ) ;
}
void xinePlayObject_impl : : resizeNotify ( )
{
if ( audioOnly ) return ;
XEvent event ;
// Resize notify signal for front-ends
memset ( & event , 0 , sizeof ( event ) ) ;
event . type = ClientMessage ;
event . xclient . window = visual . d ;
event . xclient . message_type = xcomAtomResize ;
event . xclient . format = 32 ;
event . xclient . data . l [ 0 ] = width ;
event . xclient . data . l [ 1 ] = height ;
XSendEvent ( display , visual . d , True , 0 , & event ) ;
XFlush ( display ) ;
}
// FIXME
// Due to somewhat recent changes in XLib threading this had to be changed to a polling routine
// Specifically XNextEvent acquires a global XLib lock, preventing any other XLib methods (including those used in the Xine library) from executing
// Seems this is a known problem in other projects as well, with the only real option being a rewrite to use xcb natively (not sure if that is even possible here):
// http://mail-archives.apache.org/mod_mbox/harmony-dev/200905.mbox/%3C200905181317.n4IDHtGQ002008@d06av03.portsmouth.uk.ibm.com%3E
void xinePlayObject_impl : : eventLoop ( )
{
XEvent event ;
bool eventReceived = false ;
do {
if ( XPending ( display ) ) {
XNextEvent ( display , & event ) ;
eventReceived = true ;
if ( event . type = = Expose & & event . xexpose . count = = 0 & & event . xexpose . window = = visual . d ) {
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 ) {
xine_port_send_gui_data ( vo_port ,
XINE_GUI_SEND_EXPOSE_EVENT ,
& event ) ;
}
else {
clearWindow ( ) ;
}
pthread_mutex_unlock ( & mutex ) ;
}
else if ( event . type = = shmCompletionType ) {
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 ) {
xine_port_send_gui_data ( vo_port ,
XINE_GUI_SEND_COMPLETION_EVENT ,
& event ) ;
}
pthread_mutex_unlock ( & mutex ) ;
}
}
else {
usleep ( 50000 ) ;
eventReceived = false ;
}
}
while ( ! eventReceived | |
event . type ! = ClientMessage | |
event . xclient . message_type ! = xcomAtomQuit | |
event . xclient . window ! = xcomWindow ) ;
}
void xineVideoPlayObject_impl : : x11WindowId ( long window )
{
pthread_mutex_lock ( & mutex ) ;
if ( window = = - 1 )
{
window = xcomWindow ;
}
if ( ( Window ) window ! = visual . d )
{
XLockDisplay ( display ) ;
// Change window and set event mask of new window
visual . d = window ;
XSelectInput ( display , window , ExposureMask ) ;
if ( stream ! = 0 )
{
resizeNotify ( ) ;
xine_port_send_gui_data ( vo_port ,
XINE_GUI_SEND_DRAWABLE_CHANGED ,
( void * ) window ) ;
}
XUnlockDisplay ( display ) ;
}
pthread_mutex_unlock ( & mutex ) ;
}
long xineVideoPlayObject_impl : : x11WindowId ( )
{
return ( visual . d = = xcomWindow ) ? ( long ) - 1 : visual . d ;
}
long xineVideoPlayObject_impl : : x11Snapshot ( )
{
long pixmap = - 1 ;
pthread_mutex_lock ( & mutex ) ;
if ( stream ! = 0 & & xine_get_status ( stream ) = = XINE_STATUS_PLAY )
{
// FIXME: snapshot...
pixmap = ( long ) - 1 ;
}
pthread_mutex_unlock ( & mutex ) ;
return pixmap ;
}
REGISTER_IMPLEMENTATION ( xinePlayObject_impl ) ;
REGISTER_IMPLEMENTATION ( xineAudioPlayObject_impl ) ;
REGISTER_IMPLEMENTATION ( xineVideoPlayObject_impl ) ;