[MPlayer-dev-eng] Re: [PATCH] Queue VOB subtitle packets to eliminate dropouts

Tobias Diedrich td at sim.uni-hannover.de
Sun Feb 16 05:03:37 CET 2003


Tomasz Farkas wrote:
> I have seen problems where DVD subtitles don't display
> at the right time and sometimes they don't appear at
> all.  The problem stems from the fact that subtitle
> command packets are being applied as soon as they are
> read and assembled from the input stream.

/me wrote:
> I have recently bought the Korean DVD "My Sassy Girl" and was surprised
> that the subtitles would not show up in mplayer. Further research showed
> that they are being processed, but the alpha values are always 0 because
> the packet commands look like this:

This is basically the same problem, but was not solved by your patch.
I rewrote most of it so that now commands are parsed immediately and packets
containing the relevant changes (start_pts, end_pts, alpha, palette...)
are queued. The rendering code now uses the data from the queued packets,
so that it works for both cases now.

Comments?
OK to apply?

-- 
Tobias								PGP: 0x9AC7E0BC
This mail is made of 100% recycled bits
Now playing: Kato Tokiko: Studio Ghibli Songs - Sakuranbo no Minoru Koro (KnB)
-------------- next part --------------
Index: spudec.c
===================================================================
RCS file: /cvsroot/mplayer/main/spudec.c,v
retrieving revision 1.39
diff -u -r1.39 spudec.c
--- spudec.c	16 Feb 2003 01:23:10 -0000	1.39
+++ spudec.c	16 Feb 2003 04:06:22 -0000
@@ -42,22 +42,29 @@
 
 typedef struct packet_t packet_t;
 struct packet_t {
-  unsigned char *data;
-  unsigned int process_pts;   /* When to process the packet */
-  size_t reserve;             /* size of the memory pointed to by packet */
-  unsigned int offset;        /* end of the currently assembled fragment */
-  unsigned int size;          /* size of the packet once all fragments are assembled */
-  unsigned int fragment_pts;  /* PTS of the last fragment for this packet */
-  unsigned int control_start; /* index of start of control data */
+  unsigned char *packet;
+  unsigned int palette[4];
+  unsigned int alpha[4];
+  unsigned int control_start;	/* index of start of control data */
+  unsigned int current_nibble[2]; /* next data nibble (4 bits) to be
+                                     processed (for RLE decoding) for
+                                     even and odd lines */
+  int deinterlace_oddness;	/* 0 or 1, index into current_nibble */
+  unsigned int height, start_row;
+  unsigned int start_pts, end_pts;
   packet_t *next;
 };
 
 typedef struct {
-  packet_t *packets;       /* Linked list of packets sorted by process_pts */
-  packet_t *last_packet;   /* Last packet in linked list */
-
+  packet_t *queue_head;
+  packet_t *queue_tail;
   unsigned int global_palette[16];
   unsigned int orig_frame_width, orig_frame_height;
+  unsigned char* packet;
+  size_t packet_reserve;	/* size of the memory pointed to by packet */
+  unsigned int packet_offset;	/* end of the currently assembled fragment */
+  unsigned int packet_size;	/* size of the packet once all fragments are assembled */
+  unsigned int packet_pts;	/* PTS for this packet */
   unsigned int palette[4];
   unsigned int alpha[4];
   unsigned int cuspal[4];
@@ -67,10 +74,6 @@
   unsigned int start_col, end_col;
   unsigned int start_row, end_row;
   unsigned int width, height, stride;
-  unsigned int current_nibble[2]; /* next data nibble (4 bits) to be
-                                     processed (for RLE decoding) for
-                                     even and odd lines */
-  int deinterlace_oddness;	/* 0 or 1, index into current_nibble */
   size_t image_size;		/* Size of the image buffer */
   unsigned char *image;		/* Grayscale value */
   unsigned char *aimage;	/* Alpha value */
@@ -86,46 +89,31 @@
   int spu_changed;
 } spudec_handle_t;
 
-/* Add packet to end of list */
-static void spudec_append_packet (spudec_handle_t *this, packet_t *packet)
+static void spudec_queue_packet(spudec_handle_t *this, packet_t *packet)
 {
-  packet->next = NULL;
-  if (this->last_packet == NULL)
-    this->packets = packet;
+  if (this->queue_head == NULL)
+    this->queue_head = packet;
   else
-    this->last_packet->next = packet;
-
-  this->last_packet = packet;
+    this->queue_tail->next = packet;
+  this->queue_tail = packet;
 }
 
-/* Add a new packet to end of the list */
-static void spudec_append_new_packet (spudec_handle_t *this)
+static packet_t *spudec_dequeue_packet(spudec_handle_t *this)
 {
-  packet_t *new_packet = calloc (1, sizeof (packet_t));
+  packet_t *retval = this->queue_head;
 
-  /* Do not process packet yet, so set process time way into the future */
-  new_packet->process_pts = -1L;
+  this->queue_head = retval->next;
+  if (this->queue_head == NULL)
+    this->queue_tail = NULL;
 
-  spudec_append_packet (this, new_packet);
+  return retval;
 }
 
-/* Remove top-most packet and free the memory it used */
-static void spudec_pop_packet (spudec_handle_t *this)
+static void spudec_free_packet(packet_t *packet)
 {
-  packet_t *temp;
-
-  if (this->packets != NULL)
-    {
-      temp = this->packets;
-      this->packets = temp->next;
-      if (temp->data != NULL)
-        free (temp->data);
-      free (temp);
-
-      /* Null last packet pointer if there are no packets in the queue */
-      if (this->packets == NULL)
-        this->last_packet = NULL;
-    }
+  if (packet->packet != NULL)
+    free(packet->packet);
+  free(packet);
 }
 
 static inline unsigned int get_be16(const unsigned char *p)
@@ -138,22 +126,22 @@
   return (get_be16(p) << 8) + p[2];
 }
 
-static void next_line(spudec_handle_t *this)
+static void next_line(packet_t *packet)
 {
-  if (this->current_nibble[this->deinterlace_oddness] % 2)
-    this->current_nibble[this->deinterlace_oddness]++;
-  this->deinterlace_oddness = (this->deinterlace_oddness + 1) % 2;
+  if (packet->current_nibble[packet->deinterlace_oddness] % 2)
+    packet->current_nibble[packet->deinterlace_oddness]++;
+  packet->deinterlace_oddness = (packet->deinterlace_oddness + 1) % 2;
 }
 
-static inline unsigned char get_nibble(spudec_handle_t *this, packet_t *packet)
+static inline unsigned char get_nibble(packet_t *packet)
 {
   unsigned char nib;
-  unsigned int *nibblep = this->current_nibble + this->deinterlace_oddness;
+  unsigned int *nibblep = packet->current_nibble + packet->deinterlace_oddness;
   if (*nibblep / 2 >= packet->control_start) {
     mp_msg(MSGT_SPUDEC,MSGL_WARN, "SPUdec: ERROR: get_nibble past end of packet\n");
     return 0;
   }
-  nib = packet->data[*nibblep / 2];
+  nib = packet->packet[*nibblep / 2];
   if (*nibblep % 2)
     nib &= 0xf;
   else
@@ -223,11 +211,12 @@
   unsigned int cmap[4], alpha[4];
   unsigned int i, x, y;
 
-  this->deinterlace_oddness = 0;
   this->scaled_frame_width = 0;
   this->scaled_frame_height = 0;
+  this->height = packet->height;
+  this->start_row = packet->start_row;
   for (i = 0; i < 4; ++i) {
-    alpha[i] = mkalpha(this->alpha[i]);
+    alpha[i] = mkalpha(packet->alpha[i]);
     if (alpha[i] == 0)
       cmap[i] = 0;
     else if (this->custom){
@@ -236,7 +225,7 @@
 	cmap[i] = 256 - alpha[i];
     }
     else {
-      cmap[i] = ((this->global_palette[this->palette[i]] >> 16) & 0xff);
+      cmap[i] = ((this->global_palette[packet->palette[i]] >> 16) & 0xff);
       if (cmap[i] + alpha[i] > 255)
 	cmap[i] = 256 - alpha[i];
     }
@@ -264,21 +253,21 @@
       memset(this->image + y * this->stride + this->width, 0, this->stride - this->width);
     }
 
-  i = this->current_nibble[1];
+  i = packet->current_nibble[1];
   x = 0;
   y = 0;
-  while (this->current_nibble[0] < i
-	 && this->current_nibble[1] / 2 < packet->control_start
+  while (packet->current_nibble[0] < i
+	 && packet->current_nibble[1] / 2 < packet->control_start
 	 && y < this->height) {
     unsigned int len, color;
     unsigned int rle = 0;
-    rle = get_nibble(this, packet);
+    rle = get_nibble(packet);
     if (rle < 0x04) {
-      rle = (rle << 4) | get_nibble(this, packet);
+      rle = (rle << 4) | get_nibble(packet);
       if (rle < 0x10) {
-	rle = (rle << 4) | get_nibble(this, packet);
+	rle = (rle << 4) | get_nibble(packet);
 	if (rle < 0x040) {
-	  rle = (rle << 4) | get_nibble(this, packet);
+	  rle = (rle << 4) | get_nibble(packet);
 	  if (rle < 0x0004)
 	    rle |= ((this->width - x) << 2);
 	}
@@ -293,7 +282,7 @@
     memset(this->aimage + y * this->stride + x, alpha[color], len);
     x += len;
     if (x >= this->width) {
-      next_line(this);
+      next_line(packet);
       x = 0;
       ++y;
     }
@@ -309,14 +298,14 @@
   I tested it with four streams and even got something readable. Half of the
 times I got black characters with white around and half the reverse.
 */
-static void compute_palette(spudec_handle_t *this)
+static void compute_palette(spudec_handle_t *this, packet_t *packet)
 {
   int used[16],i,cused,start,step,color;
 
   memset(used, 0, sizeof(used));
   for (i=0; i<4; i++)
-    if (this->alpha[i]) /* !Transparent? */
-       used[this->palette[i]] = 1;
+    if (packet->alpha[i]) /* !Transparent? */
+       used[packet->palette[i]] = 1;
   for (cused=0, i=0; i<16; i++)
     if (used[i]) cused++;
   if (!cused) return;
@@ -329,8 +318,8 @@
   }
   memset(used, 0, sizeof(used));
   for (i=0; i<4; i++) {
-    color = this->palette[i];
-    if (this->alpha[i] && !used[color]) { /* not assigned? */
+    color = packet->palette[i];
+    if (packet->alpha[i] && !used[color]) { /* not assigned? */
        used[color] = 1;
        this->global_palette[color] = start<<16;
        start += step;
@@ -338,24 +327,28 @@
   }
 }
 
-static void spudec_process_control(spudec_handle_t *this, packet_t *packet)
+static void spudec_process_control(spudec_handle_t *this, unsigned int pts100)
 {
   int a,b; /* Temporary vars */
   unsigned int date, type;
   unsigned int off;
   unsigned int start_off = 0;
   unsigned int next_off;
-  unsigned int pts100 = packet->process_pts;
+  unsigned int start_pts;
+  unsigned int end_pts;
+  unsigned int current_nibble[2];
+  unsigned int control_start;
+  unsigned int display = 0;
 
-  packet->control_start = get_be16(packet->data + 2);
-  next_off = packet->control_start;
+  control_start = get_be16(this->packet + 2);
+  next_off = control_start;
   while (start_off != next_off) {
     start_off = next_off;
-    date = get_be16(packet->data + start_off) * 1024;
-    next_off = get_be16(packet->data + start_off + 2);
+    date = get_be16(this->packet + start_off) * 1024;
+    next_off = get_be16(this->packet + start_off + 2);
     mp_msg(MSGT_SPUDEC,MSGL_DBG2, "date=%d\n", date);
     off = start_off + 4;
-    for (type = packet->data[off++]; type != 0xff; type = packet->data[off++]) {
+    for (type = this->packet[off++]; type != 0xff; type = this->packet[off++]) {
       mp_msg(MSGT_SPUDEC,MSGL_DBG2, "cmd=%d  ",type);
       switch(type) {
       case 0x00:
@@ -368,42 +361,39 @@
       case 0x01:
 	/* Start display */
 	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Start display!\n");
-	this->start_pts = pts100 + date;
-	this->end_pts = UINT_MAX;
+	start_pts = pts100 + date;
+	end_pts = UINT_MAX;
+	display = 1;
 	break;
       case 0x02:
 	/* Stop display */
 	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Stop display!\n");
-	this->end_pts = pts100 + date;
+	end_pts = pts100 + date;
 	break;
       case 0x03:
 	/* Palette */
-	this->palette[0] = packet->data[off] >> 4;
-	this->palette[1] = packet->data[off] & 0xf;
-	this->palette[2] = packet->data[off + 1] >> 4;
-	this->palette[3] = packet->data[off + 1] & 0xf;
+	this->palette[0] = this->packet[off] >> 4;
+	this->palette[1] = this->packet[off] & 0xf;
+	this->palette[2] = this->packet[off + 1] >> 4;
+	this->palette[3] = this->packet[off + 1] & 0xf;
 	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Palette %d, %d, %d, %d\n",
 	       this->palette[0], this->palette[1], this->palette[2], this->palette[3]);
 	off+=2;
 	break;
       case 0x04:
 	/* Alpha */
-	this->alpha[0] = packet->data[off] >> 4;
-	this->alpha[1] = packet->data[off] & 0xf;
-	this->alpha[2] = packet->data[off + 1] >> 4;
-	this->alpha[3] = packet->data[off + 1] & 0xf;
-	if (this->auto_palette) { 
-	  compute_palette(this);
-	  this->auto_palette = 0;
-	}
+	this->alpha[0] = this->packet[off] >> 4;
+	this->alpha[1] = this->packet[off] & 0xf;
+	this->alpha[2] = this->packet[off + 1] >> 4;
+	this->alpha[3] = this->packet[off + 1] & 0xf;
 	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Alpha %d, %d, %d, %d\n",
 	       this->alpha[0], this->alpha[1], this->alpha[2], this->alpha[3]);
 	off+=2;
 	break;
       case 0x05:
 	/* Co-ords */
-	a = get_be24(packet->data + off);
-	b = get_be24(packet->data + off + 3);
+	a = get_be24(this->packet + off);
+	b = get_be24(this->packet + off + 3);
 	this->start_col = a >> 12;
 	this->end_col = a & 0xfff;
 	this->width = (this->end_col < this->start_col) ? 0 : this->end_col - this->start_col + 1;
@@ -418,10 +408,10 @@
 	break;
       case 0x06:
 	/* Graphic lines */
-	this->current_nibble[0] = 2 * get_be16(packet->data + off);
-	this->current_nibble[1] = 2 * get_be16(packet->data + off + 2);
+	current_nibble[0] = 2 * get_be16(this->packet + off);
+	current_nibble[1] = 2 * get_be16(this->packet + off + 2);
 	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Graphic offset 1: %d  offset 2: %d\n",
-	       this->current_nibble[0] / 2, this->current_nibble[1] / 2);
+	       current_nibble[0] / 2, current_nibble[1] / 2);
 	off+=4;
 	break;
       case 0xff:
@@ -436,24 +426,41 @@
       }
     }
   next_control:
-    ;
+    if (display) {
+      packet_t *packet = calloc(1, sizeof(packet_t));
+      int i;
+      packet->start_pts = start_pts;
+      if (end_pts == UINT_MAX && start_off != next_off) {
+	start_pts = pts100 + get_be16(this->packet + next_off) * 1024;
+        packet->end_pts = start_pts - 1;
+      } else packet->end_pts = end_pts;
+      packet->current_nibble[0] = current_nibble[0];
+      packet->current_nibble[1] = current_nibble[1];
+      packet->height = this->height;
+      packet->start_row = this->start_row;
+      packet->control_start = control_start;
+      for (i=0; i<4; i++) {
+	packet->alpha[i] = this->alpha[i];
+	packet->palette[i] = this->palette[i];
+      }
+      packet->packet = malloc(this->packet_size);
+      memcpy(packet->packet, this->packet, this->packet_size);
+      spudec_queue_packet(this, packet);
+    }
   }
 }
 
-static void spudec_decode(spudec_handle_t *this, packet_t *queued_packet)
+static void spudec_decode(spudec_handle_t *this, unsigned int pts100)
 {
   if(this->hw_spu) {
     static vo_mpegpes_t packet = { NULL, 0, 0x20, 0 };
     static vo_mpegpes_t *pkg=&packet;
-    packet.data = queued_packet->data;
-    packet.size = queued_packet->size;
-    packet.timestamp = queued_packet->process_pts;
+    packet.data = this->packet;
+    packet.size = this->packet_size;
+    packet.timestamp = pts100;
     this->hw_spu->draw_frame((uint8_t**)&pkg);
-  } else {
-    spudec_process_control(this, queued_packet);
-    spudec_process_data(this, queued_packet);
-  }
-  this->spu_changed = 1;
+  } else
+    spudec_process_control(this, pts100);
 }
 
 int spudec_changed(void * this)
@@ -462,88 +469,80 @@
     return (spu->spu_changed || spu->now_pts > spu->end_pts);
 }
 
-void spudec_assemble(void *this, unsigned char *packet_bytes, unsigned int len, unsigned int pts100)
+void spudec_assemble(void *this, unsigned char *packet, unsigned int len, unsigned int pts100)
 {
   spudec_handle_t *spu = (spudec_handle_t*)this;
-  packet_t *last_packet;
-
-  /* Create a new packet if one doesn't exist in the queue */
-  if (spu->last_packet == NULL)
-    spudec_append_new_packet (spu);
-
-  last_packet = spu->last_packet;
-
 //  spudec_heartbeat(this, pts100);
   if (len < 2) {
       mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: packet too short\n");
       return;
   }
-  if ((last_packet->fragment_pts + 10000) < pts100) {
+  if ((spu->packet_pts + 10000) < pts100) {
     // [cb] too long since last fragment: force new packet
-    last_packet->offset = 0;
+    spu->packet_offset = 0;
   }
-  last_packet->fragment_pts = pts100;
-  if (last_packet->offset == 0) {
-    unsigned int len2 = get_be16(packet_bytes);
+  spu->packet_pts = pts100;
+  if (spu->packet_offset == 0) {
+    unsigned int len2 = get_be16(packet);
     // Start new fragment
-    if (last_packet->reserve < len2) {
-      if (last_packet->data != NULL)
-	free(last_packet->data);
-      last_packet->data = malloc(len2);
-      last_packet->reserve = last_packet->data != NULL ? len2 : 0;
+    if (spu->packet_reserve < len2) {
+      if (spu->packet != NULL)
+	free(spu->packet);
+      spu->packet = malloc(len2);
+      spu->packet_reserve = spu->packet != NULL ? len2 : 0;
     }
-    if (last_packet->data != NULL) {
-      last_packet->size = len2;
+    if (spu->packet != NULL) {
+      spu->packet_size = len2;
       if (len > len2) {
 	mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid frag len / len2: %d / %d \n", len, len2);
 	return;
       }
-      memcpy(last_packet->data, packet_bytes, len);
-      last_packet->offset = len;
+      memcpy(spu->packet, packet, len);
+      spu->packet_offset = len;
+      spu->packet_pts = pts100;
     }
   } else {
     // Continue current fragment
-    if (last_packet->size < last_packet->offset + len){
+    if (spu->packet_size < spu->packet_offset + len){
       mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid fragment\n");
-      last_packet->size = last_packet->offset = 0;
+      spu->packet_size = spu->packet_offset = 0;
       return;
     } else {
-      memcpy(last_packet->data + last_packet->offset, packet_bytes, len);
-      last_packet->offset += len;
+      memcpy(spu->packet + spu->packet_offset, packet, len);
+      spu->packet_offset += len;
     }
   }
 #if 1
   // check if we have a complete packet (unfortunatelly packet_size is bad
   // for some disks)
   // [cb] packet_size is padded to be even -> may be one byte too long
-  if ((last_packet->offset == last_packet->size) ||
-      ((last_packet->offset + 1) == last_packet->size)){
+  if ((spu->packet_offset == spu->packet_size) ||
+      ((spu->packet_offset + 1) == spu->packet_size)){
     unsigned int x=0,y;
-    while(x+4<=last_packet->offset) {
-      y=get_be16(last_packet->data+x+2); // next control pointer
-      mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUtest: x=%d y=%d off=%d size=%d\n",x,y,last_packet->offset,last_packet->size);
+    while(x+4<=spu->packet_offset){
+      y=get_be16(spu->packet+x+2); // next control pointer
+      mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUtest: x=%d y=%d off=%d size=%d\n",x,y,spu->packet_offset,spu->packet_size);
       if(x>=4 && x==y){		// if it points to self - we're done!
         // we got it!
-	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUgot: off=%d  size=%d \n",last_packet->offset,last_packet->size);
-        break;
+	mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUgot: off=%d  size=%d \n",spu->packet_offset,spu->packet_size);
+	spudec_decode(spu, pts100);
+	spu->packet_offset = 0;
+	break;
       }
-      if(y<=x || y>=last_packet->size){ // invalid?
+      if(y<=x || y>=spu->packet_size){ // invalid?
 	mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUtest: broken packet!!!!! y=%d < x=%d\n",y,x);
-        last_packet->size = last_packet->offset = 0;
-        return;
+        spu->packet_size = spu->packet_offset = 0;
+        break;
       }
       x=y;
     }
-
-    /* Packet is done.  Schedule time to process it and start a new one. */
-    last_packet->process_pts = last_packet->fragment_pts;
-    spudec_append_new_packet (spu);
+    // [cb] packet is done; start new packet
+    spu->packet_offset = 0;
   }
 #else
   if (spu->packet_offset == spu->packet_size) {
-    /* Packet is done.  Schedule time to process it and start a new one. */
-    last_packet->process_pts = last_packet->fragment_pts;
-    spudec_append_new_packet (spu);
+    spudec_decode(spu, pts100);
+    spu->packet_offset = 0;
   }
 #endif
 }
@@ -552,21 +551,24 @@
 {
   spudec_handle_t *spu = (spudec_handle_t*)this;
   spu->now_pts = 0;
-  while (spu->packets != NULL)
-    spudec_pop_packet (spu);
+  spu->packet_size = spu->packet_offset = 0;
 }
 
 void spudec_heartbeat(void *this, unsigned int pts100)
-{
+{ 
   spudec_handle_t *spu = (spudec_handle_t*) this;
   spu->now_pts = pts100;
 
-  /* Process queued instructions for the current beat */
-  while (spu->packets != NULL && pts100 >= spu->packets->process_pts)
-    {
-      spudec_decode (spu, spu->packets);
-      spudec_pop_packet (spu);
-    }
+  while (spu->queue_head != NULL && pts100 >= spu->queue_head->start_pts) {
+    packet_t *packet = spudec_dequeue_packet(spu);
+    spu->start_pts = packet->start_pts;
+    spu->end_pts = packet->end_pts;
+    if (spu->auto_palette)
+      compute_palette(spu, packet);
+    spudec_process_data(spu, packet);
+    spudec_free_packet(packet);
+    spu->spu_changed = 1;
+  }
 }
 
 int spudec_visible(void *this){
@@ -1078,7 +1080,7 @@
   spudec_handle_t *this = calloc(1, sizeof(spudec_handle_t));
   if (this){
     //(fprintf(stderr,"VobSub Custom Palette: %d,%d,%d,%d", this->cuspal[0], this->cuspal[1], this->cuspal[2],this->cuspal[3]);
-    this->packets = NULL;
+    this->packet = NULL;
     this->image = NULL;
     this->scaled_image = NULL;
     /* XXX Although the video frame is some size, the SPU frame is
@@ -1112,8 +1114,10 @@
 {
   spudec_handle_t *spu = (spudec_handle_t*)this;
   if (spu) {
-    while (spu->packets != NULL)
-      spudec_pop_packet (this);
+    while (spu->queue_head)
+      spudec_free_packet(spudec_dequeue_packet(spu));
+    if (spu->packet)
+      free(spu->packet);
     if (spu->scaled_image)
 	free(spu->scaled_image);
     if (spu->image)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
URL: <http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/attachments/20030216/d53693ca/attachment.pgp>


More information about the MPlayer-dev-eng mailing list