1 /*
2  * Copyright (C) 2000-2019 the xine project
3  *
4  * This file is part of xine, a free video player.
5  *
6  * xine is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * xine is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
19  *
20  * GOOM post plugin.
21  *
22  * first version by Mark Thomas
23  * ported to post plugin architecture by Miguel Freitas
24  * real work by goom author, JC Hoelt <jeko@free.fr>.
25  */
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <time.h>
34 
35 #define LOG_MODULE "goom"
36 #define LOG_VERBOSE
37 /*
38 #define LOG
39 */
40 
41 /* TJ. this is what my 32bit Linux running on a AMD Athlon II X2 240e (2 * 2800Mhz)
42   performed @ 1280x720, 19 fps:
43   goom: csc_method 0 min 8257 us avg 8308 us
44   goom: csc_method 1 min 16312 us avg 16413 us
45   goom: csc_method 2 min 5019 us avg 5134 us
46   Seems gcc 4.5 has a very nice 64bit math emulation :-)
47 */
48 #define BENCHMARK 1
49 
50 #include <xine/xine_internal.h>
51 #include <xine/xineutils.h>
52 #include <xine/post.h>
53 
54 #include "goom.h"
55 
56 #define NUMSAMPLES  512 /* hardcoded into goom api */
57 #define FPS          14
58 
59 #define GOOM_WIDTH  320
60 #define GOOM_HEIGHT 240
61 
62 /* colorspace conversion methods */
63 static const char* const goom_csc_methods[]={
64   "Fast but not photorealistic",
65   "Slow but looks better",
66   "Mostly fast and good quality",
67   NULL
68 };
69 
70 typedef struct post_plugin_goom_s post_plugin_goom_t;
71 
72 typedef struct post_class_goom_s post_class_goom_t;
73 
74 struct post_class_goom_s {
75   post_class_t class;
76 
77   xine_t *xine;
78   int width, height;
79   int fps;
80   int csc_method;
81 };
82 
83 struct post_plugin_goom_s {
84   post_plugin_t post;
85 
86   /* private data */
87   xine_video_port_t *vo_port;
88   post_out_t         video_output;
89 
90   post_class_goom_t *class;
91 
92   /* private metronom for syncing the video */
93   metronom_t        *metronom;
94 
95   /* goom context */
96   PluginInfo        *goom;
97 
98   int data_idx;
99   gint16 data [2][NUMSAMPLES];
100   audio_buffer_t buf;   /* dummy buffer just to hold a copy of audio data */
101 
102   int channels;
103   int sample_rate;
104   int samples_per_frame;
105   int width_back, height_back;
106   double ratio;
107   int csc_method;
108 
109 
110   int do_samples_skip; /* true = skipping samples, false reading samples*/
111   int left_to_read; /* data to read before switching modes*/
112 
113 
114   yuv_planes_t yuv;
115   rgb2yuy2_t *rgb2yuy2;
116 
117   /* frame skipping */
118   int skip_frame;
119 
120 #ifdef BENCHMARK
121   int benchmark_frames, benchmark_min, benchmark_time;
122 #endif
123 };
124 
125 
126 /* plugin class functions */
127 static post_plugin_t *goom_open_plugin(post_class_t *class_gen, int inputs,
128 					 xine_audio_port_t **audio_target,
129 					 xine_video_port_t **video_target);
130 static void           goom_class_dispose(post_class_t *class_gen);
131 
132 /* plugin instance functions */
133 static void           goom_dispose(post_plugin_t *this_gen);
134 
135 /* rewire function */
136 static int            goom_rewire_video(xine_post_out_t *output, void *data);
137 
138 static int goom_port_open(xine_audio_port_t *this, xine_stream_t *stream,
139 		   uint32_t bits, uint32_t rate, int mode);
140 
141 static void goom_port_close(xine_audio_port_t *this, xine_stream_t *stream );
142 
143 static void goom_port_put_buffer (xine_audio_port_t *this, audio_buffer_t *buf, xine_stream_t *stream);
144 
fps_changed_cb(void * data,xine_cfg_entry_t * cfg)145 static void fps_changed_cb(void *data, xine_cfg_entry_t *cfg) {
146   post_class_goom_t *class = (post_class_goom_t*) data;
147   class->fps = cfg->num_value < 1 ? 1
148              : cfg->num_value > 50 ? 50
149              : cfg->num_value;
150 }
151 
width_changed_cb(void * data,xine_cfg_entry_t * cfg)152 static void width_changed_cb(void *data, xine_cfg_entry_t *cfg) {
153   post_class_goom_t *class = (post_class_goom_t*) data;
154   class->width = cfg->num_value;
155 }
156 
height_changed_cb(void * data,xine_cfg_entry_t * cfg)157 static void height_changed_cb(void *data, xine_cfg_entry_t *cfg) {
158   post_class_goom_t *class = (post_class_goom_t*) data;
159   class->height = cfg->num_value;
160 }
161 
csc_method_changed_cb(void * data,xine_cfg_entry_t * cfg)162 static void csc_method_changed_cb(void *data, xine_cfg_entry_t *cfg) {
163   post_class_goom_t *class = (post_class_goom_t*) data;
164   class->csc_method = cfg->num_value;
165 }
166 
goom_init_plugin(xine_t * xine,const void * data)167 static void *goom_init_plugin (xine_t *xine, const void *data) {
168   config_values_t   *cfg;
169   post_class_goom_t *this = calloc (1, sizeof (*this));
170   if (!this)
171     return NULL;
172 
173   (void)data;
174 
175   this->class.open_plugin     = goom_open_plugin;
176   this->class.identifier      = "goom";
177   this->class.description     = N_("What a GOOM");
178   this->class.dispose         = goom_class_dispose;
179   this->xine                  = xine;
180 
181   cfg = xine->config;
182 
183   this->fps = cfg->register_num (cfg, "effects.goom.fps", FPS,
184     _("frames per second to generate"),
185     _("With more frames per second, the animation will get "
186       "smoother and faster, but will also require more CPU power."),
187     10, fps_changed_cb, this);
188   this->fps = this->fps < 1 ? 1
189             : this->fps > 50 ? 50
190             : this->fps;
191 
192   this->width = cfg->register_num (cfg, "effects.goom.width", GOOM_WIDTH,
193     _("goom image width"),
194     _("The width in pixels of the image to be generated."),
195     10, width_changed_cb, this);
196 
197   this->height = cfg->register_num (cfg, "effects.goom.height", GOOM_HEIGHT,
198     _("goom image height"),
199     _("The height in pixels of the image to be generated."),
200     10, height_changed_cb, this);
201 
202   this->csc_method = cfg->register_enum (cfg, "effects.goom.csc_method", 0,
203     (char **)goom_csc_methods,
204     _("colour space conversion method"),
205     _("You can choose the colour space conversion method used by goom.\n"
206       "The available selections should be self-explaining."),
207     20, csc_method_changed_cb, this);
208 
209   return &this->class;
210 }
211 
212 
goom_open_plugin(post_class_t * class_gen,int inputs,xine_audio_port_t ** audio_target,xine_video_port_t ** video_target)213 static post_plugin_t *goom_open_plugin(post_class_t *class_gen, int inputs,
214 					 xine_audio_port_t **audio_target,
215 					 xine_video_port_t **video_target)
216 {
217   post_plugin_goom_t *this  = calloc(1, sizeof(post_plugin_goom_t));
218   post_class_goom_t  *class = (post_class_goom_t*) class_gen;
219   post_in_t          *input;
220   post_out_t         *output;
221   post_out_t         *outputv;
222   post_audio_port_t  *port;
223 
224   if (!this || !video_target || !video_target[0] || !audio_target || !audio_target[0]) {
225     free(this);
226     return NULL;
227   }
228 
229   (void)inputs;
230 
231   _x_post_init(&this->post, 1, 0);
232 
233   this->class = class;
234   this->vo_port = video_target[0];
235 
236   this->metronom = _x_metronom_init(1, 0, class->xine);
237 
238   lprintf("goom_open_plugin\n");
239 
240   this->width_back  = class->width;
241   this->height_back = class->height;
242 
243   srand((unsigned int)time((time_t *)NULL));
244   this->goom = goom_init (this->width_back, this->height_back);
245 
246   this->ratio = (double)this->width_back/(double)this->height_back;
247 
248   this->buf.mem = NULL;
249   this->buf.mem_size = 0;
250 
251   port = _x_post_intercept_audio_port(&this->post, audio_target[0], &input, &output);
252   port->new_port.open       = goom_port_open;
253   port->new_port.close      = goom_port_close;
254   port->new_port.put_buffer = goom_port_put_buffer;
255 
256   outputv                  = &this->video_output;
257   outputv->xine_out.name   = "generated video";
258   outputv->xine_out.type   = XINE_POST_DATA_VIDEO;
259   outputv->xine_out.data   = (xine_video_port_t **)&this->vo_port;
260   outputv->xine_out.rewire = goom_rewire_video;
261   outputv->post            = &this->post;
262   xine_list_push_back(this->post.output, outputv);
263 
264   this->post.xine_post.audio_input[0] = &port->new_port;
265 
266   this->post.dispose = goom_dispose;
267 
268 #ifdef __BIG_ENDIAN__
269   this->rgb2yuy2 = rgb2yuy2_alloc (10, "argb");
270 #else
271   this->rgb2yuy2 = rgb2yuy2_alloc (10, "bgra");
272 #endif
273 #ifdef BENCHMARK
274   this->benchmark_frames = 200 - 1;
275   this->benchmark_min = 10000000;
276   this->benchmark_time = 0;
277 #endif
278 
279   return &this->post;
280 }
281 
goom_class_dispose(post_class_t * class_gen)282 static void goom_class_dispose(post_class_t *class_gen)
283 {
284   post_class_goom_t  *this = (post_class_goom_t*) class_gen;
285 
286   this->xine->config->unregister_callbacks (this->xine->config, NULL, NULL, this, sizeof (*this));
287 
288   free(class_gen);
289 }
290 
291 
goom_dispose(post_plugin_t * this_gen)292 static void goom_dispose(post_plugin_t *this_gen)
293 {
294   post_plugin_goom_t *this   = (post_plugin_goom_t *)this_gen;
295 
296   if (_x_post_dispose(this_gen)) {
297 
298     rgb2yuy2_free (this->rgb2yuy2);
299 
300     goom_close(this->goom);
301 
302     this->metronom->exit(this->metronom);
303 
304     if(this->buf.mem)
305       free(this->buf.mem);
306     free(this);
307   }
308 }
309 
310 
goom_rewire_video(xine_post_out_t * output_gen,void * data)311 static int goom_rewire_video(xine_post_out_t *output_gen, void *data)
312 {
313   post_out_t *output = (post_out_t *)output_gen;
314   xine_video_port_t *old_port = *(xine_video_port_t **)output_gen->data;
315   xine_video_port_t *new_port = (xine_video_port_t *)data;
316   post_plugin_goom_t *this = (post_plugin_goom_t *)output->post;
317 
318   if (!data)
319     return 0;
320   /* register our stream at the new output port */
321   old_port->close(old_port, XINE_ANON_STREAM);
322   (new_port->open) (new_port, XINE_ANON_STREAM);
323   /* reconnect ourselves */
324   this->vo_port = new_port;
325   return 1;
326 }
327 
goom_port_open(xine_audio_port_t * port_gen,xine_stream_t * stream,uint32_t bits,uint32_t rate,int mode)328 static int goom_port_open(xine_audio_port_t *port_gen, xine_stream_t *stream,
329 		   uint32_t bits, uint32_t rate, int mode) {
330 
331   post_audio_port_t  *port = (post_audio_port_t *)port_gen;
332   post_plugin_goom_t *this = (post_plugin_goom_t *)port->post;
333 
334   _x_post_rewire(&this->post);
335   _x_post_inc_usage(port);
336 
337   port->stream = stream;
338   port->bits = bits;
339   port->rate = rate;
340   port->mode = mode;
341 
342   this->channels = _x_ao_mode2channels(mode);
343   this->sample_rate = rate;
344   this->samples_per_frame = rate / this->class->fps;
345   this->data_idx = 0;
346   init_yuv_planes(&this->yuv, this->width_back, this->height_back);
347   this->skip_frame = 0;
348 
349   this->do_samples_skip = 0;
350   this->left_to_read = NUMSAMPLES;
351 
352   (this->vo_port->open) (this->vo_port, XINE_ANON_STREAM);
353   this->metronom->set_master(this->metronom, stream->metronom);
354 
355 #ifdef BENCHMARK
356   this->benchmark_frames = 200 - 1;
357   this->benchmark_min = 10000000;
358   this->benchmark_time = 0;
359 #endif
360 
361   return (port->original_port->open) (port->original_port, stream, bits, rate, mode );
362 }
363 
goom_port_close(xine_audio_port_t * port_gen,xine_stream_t * stream)364 static void goom_port_close(xine_audio_port_t *port_gen, xine_stream_t *stream ) {
365 
366   post_audio_port_t  *port = (post_audio_port_t *)port_gen;
367   post_plugin_goom_t *this = (post_plugin_goom_t *)port->post;
368 
369   free_yuv_planes(&this->yuv);
370 
371   port->stream = NULL;
372 
373   this->vo_port->close(this->vo_port, XINE_ANON_STREAM);
374   this->metronom->set_master(this->metronom, NULL);
375 
376   port->original_port->close(port->original_port, stream );
377 
378   _x_post_dec_usage(port);
379 }
380 
381 #ifdef BENCHMARK
now(void)382 static int now (void) {
383   struct timeval tv;
384   gettimeofday (&tv, NULL);
385   return tv.tv_sec * 1000000 + tv.tv_usec;
386 }
387 #endif
388 
goom_port_put_buffer(xine_audio_port_t * port_gen,audio_buffer_t * buf,xine_stream_t * stream)389 static void goom_port_put_buffer (xine_audio_port_t *port_gen,
390                              audio_buffer_t *buf, xine_stream_t *stream) {
391 
392   post_audio_port_t  *port = (post_audio_port_t *)port_gen;
393   post_plugin_goom_t *this = (post_plugin_goom_t *)port->post;
394   vo_frame_t         *frame;
395   uint8_t *goom_frame, *goom_frame_end;
396   int16_t *data;
397   int8_t *data8;
398   int64_t pts = buf->vpts;
399   int i, j;
400   uint8_t *dest_ptr;
401   int width, height;
402 
403   int current_sample = 0;
404 
405   /* make a copy of buf data for private use */
406   if( this->buf.mem_size < buf->mem_size ) {
407     this->buf.mem = realloc(this->buf.mem, buf->mem_size);
408     this->buf.mem_size = buf->mem_size;
409   }
410   memcpy(this->buf.mem, buf->mem,
411          buf->num_frames*this->channels*((port->bits == 8)?1:2));
412   this->buf.num_frames = buf->num_frames;
413 
414   /* pass data to original port */
415   port->original_port->put_buffer(port->original_port, buf, stream);
416 
417   /* we must not use original data anymore, it should have already being moved
418    * to the fifo of free audio buffers. just use our private copy instead.
419    */
420   buf = &this->buf;
421   j = (this->channels >= 2) ? 1 : 0;
422 
423 
424   while (current_sample < buf->num_frames) {
425 
426   if (this->do_samples_skip) {
427     if (current_sample + this->left_to_read > buf->num_frames) {
428       this->left_to_read -= (buf->num_frames-current_sample);
429       break;
430     } else {
431       current_sample+=this->left_to_read;
432       this->left_to_read = NUMSAMPLES;
433       this->do_samples_skip = 0;
434 
435     }
436   } else {
437 
438     if( port->bits == 8 ) {
439       data8 = (int8_t *)buf->mem;
440       data8 += current_sample * this->channels;
441 
442       /* scale 8 bit data to 16 bits and convert to signed as well */
443       for ( i=current_sample ; this->data_idx < NUMSAMPLES && i < buf->num_frames;
444         i++, this->data_idx++,data8 += this->channels) {
445 
446         this->data[0][this->data_idx] = ((int16_t)data8[0] << 8) - 0x8000;
447         this->data[1][this->data_idx] = ((int16_t)data8[j] << 8) - 0x8000;
448       }
449     } else {
450       data = buf->mem;
451       data += current_sample * this->channels;
452 
453       for ( i=current_sample ; this->data_idx < NUMSAMPLES && i < buf->num_frames;
454         i++, this->data_idx++,data += this->channels) {
455 
456         this->data[0][this->data_idx] = data[0];
457         this->data[1][this->data_idx] = data[j];
458       }
459     }
460 
461     if (this->data_idx < NUMSAMPLES) {
462       this->left_to_read = NUMSAMPLES - this->data_idx;
463       break;
464     } else {
465       _x_assert(this->data_idx == NUMSAMPLES);
466       this->data_idx = 0;
467 
468       if (this->samples_per_frame > NUMSAMPLES) {
469         current_sample += NUMSAMPLES;
470         this->do_samples_skip = 1;
471         this->left_to_read = this->samples_per_frame - NUMSAMPLES;
472       } else {
473         current_sample += this->samples_per_frame;
474         this->left_to_read = NUMSAMPLES;
475       }
476 
477       frame = this->vo_port->get_frame (this->vo_port, this->width_back, this->height_back,
478                 this->ratio, XINE_IMGFMT_YUY2,
479                 VO_BOTH_FIELDS);
480 
481       frame->extra_info->invalid = 1;
482 
483       frame->duration = 90000 * this->samples_per_frame / this->sample_rate;
484       frame->pts = pts;
485       this->metronom->got_video_frame(this->metronom, frame);
486 
487       if (!this->skip_frame) {
488 #ifdef BENCHMARK
489         int elapsed = 0;
490 #endif
491         /* Try to be fast */
492         goom_frame = (uint8_t *)goom_update (this->goom, this->data, 0, 0, NULL, NULL);
493 
494         dest_ptr = frame -> base[0];
495         goom_frame_end = goom_frame + 4 * (this->width_back * this->height_back);
496 
497 #ifdef BENCHMARK
498         if (this->benchmark_frames >= 0)
499           elapsed = -now ();
500 #endif
501 
502         this->csc_method = this->class->csc_method;
503         if (this->csc_method == 2) {
504 
505           if (!frame->proc_slice || (frame->height & 15)) {
506             /* do all at once */
507             rgb2yuy2_slice (this->rgb2yuy2, goom_frame, 4 * this->width_back,
508               frame->base[0], frame->pitches[0], this->width_back, this->height_back);
509           } else {
510             /* sliced. This is a double edged sword.
511                On one hand, it is faster by using cache more effective.
512                On the flipside, when vo loop drops this frame, all this goes in vain */
513             uint8_t *sptr[1];
514             int     y, h = 16, p = 4 * this->width_back;
515             for (y = 0; y < this->height_back; y += 16) {
516               if (y + 16 > this->height_back)
517                 h = this->height_back & 15;
518               sptr[0] = frame->base[0] + y * frame->pitches[0];
519               rgb2yuy2_slice (this->rgb2yuy2, goom_frame + y * p, p,
520                 sptr[0], frame->pitches[0], this->width_back, h);
521               frame->proc_slice (frame, sptr);
522             }
523           }
524         }
525 #if defined(ARCH_X86)
526         else if ((this->csc_method == 1) &&
527             (xine_mm_accel() & MM_ACCEL_X86_MMX)) {
528           int plane_ptr = 0;
529 
530           while (goom_frame < goom_frame_end) {
531             uint8_t r, g, b;
532 
533             /* don't take endianness into account since MMX is only available
534              * on Intel processors */
535             b = *goom_frame; goom_frame++;
536             g = *goom_frame; goom_frame++;
537             r = *goom_frame; goom_frame += 2;
538 
539             this->yuv.y[plane_ptr] = COMPUTE_Y(r, g, b);
540             this->yuv.u[plane_ptr] = COMPUTE_U(r, g, b);
541             this->yuv.v[plane_ptr] = COMPUTE_V(r, g, b);
542             plane_ptr++;
543           }
544 
545           yuv444_to_yuy2(&this->yuv, frame->base[0], frame->pitches[0]);
546         }
547 #endif /* ARCH_X86 */
548         else {
549 
550           while (goom_frame < goom_frame_end) {
551             uint8_t r1, g1, b1, r2, g2, b2;
552 
553 #ifdef __BIG_ENDIAN__
554             goom_frame ++;
555             r1 = *goom_frame; goom_frame++;
556             g1 = *goom_frame; goom_frame++;
557             b1 = *goom_frame; goom_frame += 2;
558             r2 = *goom_frame; goom_frame++;
559             g2 = *goom_frame; goom_frame++;
560             b2 = *goom_frame; goom_frame++;
561 #else
562             b1 = *goom_frame; goom_frame++;
563             g1 = *goom_frame; goom_frame++;
564             r1 = *goom_frame; goom_frame += 2;
565             b2 = *goom_frame; goom_frame++;
566             g2 = *goom_frame; goom_frame++;
567             r2 = *goom_frame; goom_frame += 2;
568 #endif
569 
570             *dest_ptr = COMPUTE_Y(r1, g1, b1);
571             dest_ptr++;
572             *dest_ptr = COMPUTE_U(r1, g1, b1);
573             dest_ptr++;
574             *dest_ptr = COMPUTE_Y(r2, g2, b2);
575             dest_ptr++;
576             *dest_ptr = COMPUTE_V(r2, g2, b2);
577             dest_ptr++;
578           }
579         }
580 
581 #ifdef BENCHMARK
582         if (this->benchmark_frames >= 0) {
583           elapsed += now ();
584           this->benchmark_time += elapsed;
585           if (elapsed < this->benchmark_min)
586             this->benchmark_min = elapsed;
587           if (--this->benchmark_frames < 0) {
588             xprintf (this->class->xine, XINE_VERBOSITY_DEBUG,
589               "goom: csc_method %d min %d us avg %d us\n",
590               this->csc_method, this->benchmark_min, this->benchmark_time / 200);
591           }
592         }
593 #endif
594 
595         this->skip_frame = frame->draw(frame, XINE_ANON_STREAM);
596       } else {
597         frame->bad_frame = 1;
598         frame->draw(frame, XINE_ANON_STREAM);
599 
600         _x_assert(this->skip_frame>0);
601         this->skip_frame--;
602       }
603 
604       frame->free(frame);
605 
606       width  = this->class->width;
607       height = this->class->height;
608       if ((width != this->width_back) || (height != this->height_back)) {
609         goom_close(this->goom);
610         this->goom = goom_init (width, height);
611         this->width_back = width;
612         this->height_back = height;
613         this->ratio = (double)width/(double)height;
614         free_yuv_planes(&this->yuv);
615         init_yuv_planes(&this->yuv, width, height);
616       }
617     }
618   }
619   }
620 }
621 
622 /* plugin catalog information */
623 static const post_info_t goom_special_info = {
624   .type = XINE_POST_TYPE_AUDIO_VISUALIZATION,
625 };
626 
627 const plugin_info_t xine_plugin_info[] EXPORTED = {
628   /* type, API, "name", version, special_info, init_function */
629   { PLUGIN_POST | PLUGIN_MUST_PRELOAD, 10, "goom", XINE_VERSION_CODE, &goom_special_info, &goom_init_plugin },
630   { PLUGIN_NONE, 0, NULL, 0, NULL, NULL }
631 };
632