XMMS2
src/xmms/visualization/object.c
Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2011 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 #include <string.h>
00018 #include <stdio.h>
00019 #include <stdlib.h>
00020 
00021 #include "xmms/xmms_object.h"
00022 #include "xmmspriv/xmms_ipc.h"
00023 #include "xmmspriv/xmms_sample.h"
00024 
00025 #include "common.h"
00026 
00027 /** @defgroup Visualization Visualization
00028   * @ingroup XMMSServer
00029   * @brief Feeds playing data in various forms to the client.
00030   * @{
00031   */
00032 
00033 static xmms_visualization_t *vis = NULL;
00034 
00035 static int32_t xmms_visualization_client_query_version (xmms_visualization_t *vis, xmms_error_t *err);
00036 static int32_t xmms_visualization_client_register (xmms_visualization_t *vis, xmms_error_t *err);
00037 static int32_t xmms_visualization_client_init_shm (xmms_visualization_t *vis, int32_t id, const char *shmid, xmms_error_t *err);
00038 static int32_t xmms_visualization_client_init_udp (xmms_visualization_t *vis, int32_t id, xmms_error_t *err);
00039 static int32_t xmms_visualization_client_set_property (xmms_visualization_t *vis, int32_t id, const gchar *key, const gchar *value, xmms_error_t *err);
00040 static int32_t xmms_visualization_client_set_properties (xmms_visualization_t *vis, int32_t id, xmmsv_t *prop, xmms_error_t *err);
00041 static void xmms_visualization_client_shutdown (xmms_visualization_t *vis, int32_t id, xmms_error_t *err);
00042 static void xmms_visualization_destroy (xmms_object_t *object);
00043 
00044 #include "visualization/object_ipc.c"
00045 
00046 /* create an uninitialised vis client. don't use this method without mutex! */
00047 static int32_t
00048 create_client (void)
00049 {
00050     int32_t id;
00051 
00052     for (id = 0; id < vis->clientc; ++id) {
00053         if (!vis->clientv[id]) {
00054             break;
00055         }
00056     }
00057 
00058     if (id == vis->clientc) {
00059         vis->clientc++;
00060     }
00061 
00062     vis->clientv = g_renew (xmms_vis_client_t*, vis->clientv, vis->clientc);
00063     if (!vis->clientv || (!(vis->clientv[id] = g_new (xmms_vis_client_t, 1)))) {
00064         vis->clientc = 0;
00065         id = -1;
00066     }
00067 
00068     xmms_log_info ("Attached visualization client %d", id);
00069     return id;
00070 }
00071 
00072 xmms_vis_client_t *
00073 get_client (int32_t id)
00074 {
00075     if (id < 0 || id >= vis->clientc) {
00076         return NULL;
00077     }
00078 
00079     return vis->clientv[id];
00080 }
00081 
00082 /* delete a vis client. don't use this method without mutex! */
00083 void
00084 delete_client (int32_t id)
00085 {
00086     xmms_vis_client_t *c;
00087 
00088     if (id < 0 || id >= vis->clientc) {
00089         return;
00090     }
00091 
00092     c = vis->clientv[id];
00093     if (c == NULL) {
00094         return;
00095     }
00096 
00097     if (c->type == VIS_UNIXSHM) {
00098         cleanup_shm (&c->transport.shm);
00099     } else if (c->type == VIS_UDP) {
00100         cleanup_udp (&c->transport.udp, vis->socket);
00101     }
00102 
00103     g_free (c);
00104     vis->clientv[id] = NULL;
00105 
00106     xmms_log_info ("Removed visualization client %d", id);
00107 }
00108 
00109 /**
00110  * Initialize the Vis module.
00111  */
00112 xmms_visualization_t *
00113 xmms_visualization_new (xmms_output_t *output)
00114 {
00115     vis = xmms_object_new (xmms_visualization_t, xmms_visualization_destroy);
00116     vis->clientlock = g_mutex_new ();
00117     vis->clientc = 0;
00118     vis->output = output;
00119 
00120     xmms_object_ref (output);
00121 
00122     xmms_visualization_register_ipc_commands (XMMS_OBJECT (vis));
00123 
00124     xmms_socket_invalidate (&vis->socket);
00125 
00126     return vis;
00127 }
00128 
00129 /**
00130  * Free all resoures used by visualization module.
00131  * TODO: Fill this in properly, unregister etc!
00132  */
00133 
00134 static void
00135 xmms_visualization_destroy (xmms_object_t *object)
00136 {
00137     xmms_object_unref (vis->output);
00138 
00139     /* TODO: assure that the xform is already dead! */
00140     g_mutex_free (vis->clientlock);
00141     xmms_log_debug ("starting cleanup of %d vis clients", vis->clientc);
00142     for (; vis->clientc > 0; --vis->clientc) {
00143         delete_client (vis->clientc - 1);
00144     }
00145 
00146     if (xmms_socket_valid (vis->socket)) {
00147         /* it seems there is no way to remove the watch */
00148         g_io_channel_shutdown (vis->socketio, FALSE, NULL);
00149         xmms_socket_close (vis->socket);
00150     }
00151 
00152     xmms_visualization_unregister_ipc_commands ();
00153 }
00154 
00155 static int32_t
00156 xmms_visualization_client_query_version (xmms_visualization_t *vis, xmms_error_t *err)
00157 {
00158     /* if there is a way to disable visualization support on the server side,
00159        we could return 0 here, or we could return an error? */
00160 
00161     return XMMS_VISPACKET_VERSION;
00162 }
00163 
00164 static void
00165 properties_init (xmmsc_vis_properties_t *p)
00166 {
00167     p->type = VIS_PCM;
00168     p->stereo = 1;
00169     p->pcm_hardwire = 0;
00170 }
00171 
00172 static gboolean
00173 property_set (xmmsc_vis_properties_t *p, const gchar* key, const gchar* data)
00174 {
00175 
00176     if (!g_strcasecmp (key, "type")) {
00177         if (!g_strcasecmp (data, "pcm")) {
00178             p->type = VIS_PCM;
00179         } else if (!g_strcasecmp (data, "spectrum")) {
00180             p->type = VIS_SPECTRUM;
00181         } else if (!g_strcasecmp (data, "peak")) {
00182             p->type = VIS_PEAK;
00183         } else {
00184             return FALSE;
00185         }
00186     } else if (!g_strcasecmp (key, "stereo")) {
00187         p->stereo = (atoi (data) > 0);
00188     } else if (!g_strcasecmp (key, "pcm.hardwire")) {
00189         p->pcm_hardwire = (atoi (data) > 0);
00190     /* TODO: all the stuff following */
00191     } else if (!g_strcasecmp (key, "timeframe")) {
00192         p->timeframe = g_strtod (data, NULL);
00193         if (p->timeframe == 0.0) {
00194             return FALSE;
00195         }
00196     } else {
00197         return FALSE;
00198     }
00199     return TRUE;
00200 }
00201 
00202 static int32_t
00203 xmms_visualization_client_register (xmms_visualization_t *vis, xmms_error_t *err)
00204 {
00205     int32_t id;
00206     xmms_vis_client_t *c;
00207 
00208     g_mutex_lock (vis->clientlock);
00209     id = create_client ();
00210     if (id < 0) {
00211         xmms_error_set (err, XMMS_ERROR_OOM, "could not allocate dataset");
00212     } else {
00213         /* do necessary initialisations here */
00214         c = get_client (id);
00215         c->type = VIS_NONE;
00216         c->format = 0;
00217         properties_init (&c->prop);
00218     }
00219     g_mutex_unlock (vis->clientlock);
00220     return id;
00221 }
00222 
00223 
00224 static int32_t
00225 xmms_visualization_client_set_property (xmms_visualization_t *vis, int32_t id, const gchar* key, const gchar* value, xmms_error_t *err)
00226 {
00227     xmms_vis_client_t *c;
00228 
00229     x_fetch_client (id);
00230 
00231     if (!property_set (&c->prop, key, value)) {
00232         xmms_error_set (err, XMMS_ERROR_INVAL, "property could not be set!");
00233     }
00234 
00235     x_release_client ();
00236 
00237     /* the format identifier (between client and server) changes. so the client can recognize the first packet
00238        which is built using the new format according to the newly set property */
00239     return (++c->format);
00240 }
00241 
00242 static int32_t
00243 xmms_visualization_client_set_properties (xmms_visualization_t *vis, int32_t id, xmmsv_t* prop, xmms_error_t *err)
00244 {
00245     xmms_vis_client_t *c;
00246     xmmsv_dict_iter_t *it;
00247     const gchar *key, *valstr;
00248     xmmsv_t *value;
00249 
00250     x_fetch_client (id);
00251 
00252     if (!xmmsv_get_type (prop) == XMMSV_TYPE_DICT) {
00253         xmms_error_set (err, XMMS_ERROR_INVAL, "properties must be sent as a dict!");
00254     } else {
00255         /* record every pair */
00256         xmmsv_get_dict_iter (prop, &it);
00257         while (xmmsv_dict_iter_valid (it)) {
00258             if (!xmmsv_dict_iter_pair (it, &key, &value)) {
00259                 xmms_error_set (err, XMMS_ERROR_INVAL, "key-value property pair could not be read!");
00260             } else if (!xmmsv_get_string (value, &valstr)) {
00261                 xmms_error_set (err, XMMS_ERROR_INVAL, "property value could not be read!");
00262             } else if (!property_set (&c->prop, key, valstr)) {
00263                 xmms_error_set (err, XMMS_ERROR_INVAL, "property could not be set!");
00264             }
00265             xmmsv_dict_iter_next (it);
00266         }
00267         /* TODO: propagate new format to xform! */
00268     }
00269 
00270     x_release_client ();
00271 
00272     return (++c->format);
00273 }
00274 
00275 static int32_t
00276 xmms_visualization_client_init_shm (xmms_visualization_t *vis, int32_t id, const char *shmidstr, xmms_error_t *err)
00277 {
00278     int shmid;
00279 
00280     XMMS_DBG ("Trying to init shm!");
00281 
00282     if (sscanf (shmidstr, "%d", &shmid) != 1) {
00283         xmms_error_set (err, XMMS_ERROR_INVAL, "couldn't parse shmid");
00284         return -1;
00285     }
00286     return init_shm (vis, id, shmid, err);
00287 }
00288 
00289 static int32_t
00290 xmms_visualization_client_init_udp (xmms_visualization_t *vis, int32_t id, xmms_error_t *err)
00291 {
00292     XMMS_DBG ("Trying to init udp!");
00293     return init_udp (vis, id, err);
00294 }
00295 
00296 static void
00297 xmms_visualization_client_shutdown (xmms_visualization_t *vis, int32_t id, xmms_error_t *err)
00298 {
00299     g_mutex_lock (vis->clientlock);
00300     delete_client (id);
00301     g_mutex_unlock (vis->clientlock);
00302 }
00303 
00304 static gboolean
00305 package_write (xmms_vis_client_t *c, int32_t id, struct timeval *time, int channels, int size, short *buf)
00306 {
00307     if (c->type == VIS_UNIXSHM) {
00308         return write_shm (&c->transport.shm, c, id, time, channels, size, buf);
00309     } else if (c->type == VIS_UDP) {
00310         return write_udp (&c->transport.udp, c, id, time, channels, size, buf, vis->socket);
00311     }
00312     return FALSE;
00313 }
00314 
00315 void
00316 send_data (int channels, int size, short *buf)
00317 {
00318     int i;
00319     struct timeval time;
00320     guint32 latency;
00321 
00322     if (!vis) {
00323         return;
00324     }
00325 
00326     latency = xmms_output_latency (vis->output);
00327 
00328     fft_init ();
00329 
00330     gettimeofday (&time, NULL);
00331     time.tv_sec += (latency / 1000);
00332     time.tv_usec += (latency % 1000) * 1000;
00333     if (time.tv_usec > 1000000) {
00334         time.tv_sec++;
00335         time.tv_usec -= 1000000;
00336     }
00337 
00338     g_mutex_lock (vis->clientlock);
00339     for (i = 0; i < vis->clientc; ++i) {
00340         if (vis->clientv[i]) {
00341             package_write (vis->clientv[i], i, &time, channels, size, buf);
00342         }
00343     }
00344     g_mutex_unlock (vis->clientlock);
00345 }
00346 
00347 /** @} */