/* This file is part of KDE/aRts (Noatun) - xine integration Copyright (C) 2002-2003 Ewald Snel 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 #endif #include #include #include #include #include #include #include #include #include #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( 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);