PageRenderTime 65ms CodeModel.GetById 13ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 1ms

/xbmc/cores/dvdplayer/DVDDemuxSPU.cpp

http://github.com/xbmc/xbmc
C++ | 650 lines | 451 code | 92 blank | 107 comment | 77 complexity | a449b019c127cbf382b77e8e4a3251a0 MD5 | raw file
  1/*
  2 *      Copyright (C) 2005-2013 Team XBMC
  3 *      http://xbmc.org
  4 *
  5 *  This Program is free software; you can redistribute it and/or modify
  6 *  it under the terms of the GNU General Public License as published by
  7 *  the Free Software Foundation; either version 2, or (at your option)
  8 *  any later version.
  9 *
 10 *  This Program is distributed in the hope that it will be useful,
 11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 13 *  GNU General Public License for more details.
 14 *
 15 *  You should have received a copy of the GNU General Public License
 16 *  along with XBMC; see the file COPYING.  If not, see
 17 *  <http://www.gnu.org/licenses/>.
 18 *
 19 */
 20
 21#include "DVDDemuxSPU.h"
 22#include "DVDClock.h"
 23#include "utils/log.h"
 24
 25#undef ALIGN
 26#define ALIGN(value, alignment) (((value)+((alignment)-1))&~((alignment)-1))
 27
 28// #define SPU_DEBUG
 29
 30void DebugLog(const char *format, ...)
 31{
 32#ifdef SPU_DEBUG
 33  static char temp_spubuffer[1024];
 34  va_list va;
 35
 36  va_start(va, format);
 37  _vsnprintf(temp_spubuffer, 1024, format, va);
 38  va_end(va);
 39
 40  CLog::Log(LOGDEBUG,temp_spubuffer);
 41#endif
 42}
 43
 44CDVDDemuxSPU::CDVDDemuxSPU()
 45{
 46  memset(&m_spuData, 0, sizeof(m_spuData));
 47  memset(m_clut, 0, sizeof(m_clut));
 48  m_bHasClut = false;
 49}
 50
 51CDVDDemuxSPU::~CDVDDemuxSPU()
 52{
 53  if (m_spuData.data) free(m_spuData.data);
 54}
 55
 56void CDVDDemuxSPU::Reset()
 57{
 58  FlushCurrentPacket();
 59
 60  // We can't reset this during playback, cause we don't always
 61  // get a new clut from libdvdnav leading to invalid colors
 62  // so let's just never reset it. It will only be reset
 63  // when dvdplayer is destructed and constructed
 64  // m_bHasClut = false;
 65  // memset(m_clut, 0, sizeof(m_clut));
 66}
 67
 68void CDVDDemuxSPU::FlushCurrentPacket()
 69{
 70  if (m_spuData.data) free(m_spuData.data);
 71  memset(&m_spuData, 0, sizeof(m_spuData));
 72}
 73
 74CDVDOverlaySpu* CDVDDemuxSPU::AddData(uint8_t* data, int iSize, double pts)
 75{
 76  SPUData* pSPUData = &m_spuData;
 77
 78  if (pSPUData->iNeededSize > 0 &&
 79      (pSPUData->iSize != pSPUData->iNeededSize) &&
 80      ((pSPUData->iSize + iSize) > pSPUData->iNeededSize))
 81  {
 82    DebugLog("corrupt spu data: packet does not fit");
 83    m_spuData.iNeededSize = 0;
 84    m_spuData.iSize = 0;
 85    return NULL;
 86  }
 87
 88  // check if we are about to start a new packet
 89  if (pSPUData->iSize == pSPUData->iNeededSize)
 90  {
 91    // for now we don't delete the memory assosiated with m_spuData.data
 92    pSPUData->iSize = 0;
 93
 94    // check spu data lenght, only needed / possible in the first spu pakcet
 95    unsigned __int16 length = data[0] << 8 | data[1];
 96    if (length == 0)
 97    {
 98      DebugLog("corrupt spu data: zero packet");
 99      m_spuData.iNeededSize = 0;
100      m_spuData.iSize = 0;
101      return NULL;
102    }
103    if (length > iSize) pSPUData->iNeededSize = length;
104    else pSPUData->iNeededSize = iSize;
105
106    // set presentation time stamp
107    if (pts > 0) pSPUData->pts = pts;
108  }
109
110  // allocate data if not already done ( done in blocks off 16384 bytes )
111  // or allocate some more if 16384 bytes is not enough
112  if((pSPUData->iSize + iSize) > pSPUData->iAllocatedSize)
113    pSPUData->data = (uint8_t*)realloc(pSPUData->data, ALIGN(pSPUData->iSize + iSize, 0x4000));
114
115  if(!pSPUData->data)
116    return NULL; // crap realloc failed, this will have leaked some memory due to odd realloc
117
118  // add new data
119  memcpy(pSPUData->data + pSPUData->iSize, data, iSize);
120  pSPUData->iSize += iSize;
121
122  if (pSPUData->iNeededSize - pSPUData->iSize == 1) // to make it even
123  {
124    DebugLog("missing 1 byte to complete packet, adding 0xff");
125
126    pSPUData->data[pSPUData->iSize] = 0xff;
127    pSPUData->iSize++;
128  }
129
130  if (pSPUData->iSize == pSPUData->iNeededSize)
131  {
132    DebugLog("got complete spu packet\n  length: %i bytes\n  stream: %i\n", pSPUData->iSize);
133
134    return ParsePacket(pSPUData);
135  }
136
137  return NULL;
138}
139
140#define CMD_END     0xFF
141#define FSTA_DSP    0x00
142#define STA_DSP     0x01
143#define STP_DSP     0x02
144#define SET_COLOR   0x03
145#define SET_CONTR   0x04
146#define SET_DAREA   0x05
147#define SET_DSPXA   0x06
148#define CHG_COLCON  0x07
149
150CDVDOverlaySpu* CDVDDemuxSPU::ParsePacket(SPUData* pSPUData)
151{
152  unsigned int alpha[4];
153  uint8_t* pUnparsedData = NULL;
154
155  if (pSPUData->iNeededSize != pSPUData->iSize)
156  {
157    DebugLog("GetPacket, packet is incomplete, missing: %i bytes", (pSPUData->iNeededSize - pSPUData->iSize));
158  }
159
160  if (pSPUData->data[pSPUData->iSize - 1] != 0xff)
161  {
162    DebugLog("GetPacket, missing end of data 0xff");
163  }
164
165  CDVDOverlaySpu* pSPUInfo = new CDVDOverlaySpu();
166  uint8_t* p = pSPUData->data; // pointer to walk through all data
167
168  // get data length
169  unsigned __int16 datalength = p[2] << 8 | p[3]; // datalength + 4 control bytes
170
171  pUnparsedData = pSPUData->data + 4;
172
173  // if it is set to 0 it means it's a menu overlay by defualt
174  // this is not what we want too, cause you get strange results on a parse error
175  pSPUInfo->iPTSStartTime = -1;
176
177  //skip data packet and goto control sequence
178  p += datalength;
179
180  bool bHasNewDCSQ = true;
181  while (bHasNewDCSQ)
182  {
183    DebugLog("  starting new SP_DCSQT");
184    // p is beginning of first SP_DCSQT now
185    unsigned __int16 delay = p[0] << 8 | p[1];
186    unsigned __int16 next_DCSQ = p[2] << 8 | p[3];
187
188    //offset within the Sub-Picture Unit to the next SP_DCSQ. If this is the last SP_DCSQ, it points to itself.
189    bHasNewDCSQ = ((pSPUData->data + next_DCSQ) != p);
190    // skip 4 bytes
191    p += 4;
192
193    while (*p != CMD_END && (unsigned int)(p - pSPUData->data) <= pSPUData->iSize)
194    {
195      switch (*p)
196      {
197      case FSTA_DSP:
198        p++;
199        DebugLog("    GetPacket, FSTA_DSP: Forced Start Display, no arguments");
200        pSPUInfo->iPTSStartTime = pSPUData->pts;
201        pSPUInfo->iPTSStopTime = 0x9000000000000LL;
202        pSPUInfo->bForced = true;
203        // delay is always 0, the dvdplayer should decide when to display the packet (menu highlight)
204        break;
205      case STA_DSP:
206        {
207          p++;
208          pSPUInfo->iPTSStartTime = pSPUData->pts;
209          pSPUInfo->iPTSStartTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
210          DebugLog("    GetPacket, STA_DSP: Start Display, delay: %i", ((delay * 1024) / 90000));
211        }
212        break;
213      case STP_DSP:
214        {
215          p++;
216          pSPUInfo->iPTSStopTime = pSPUData->pts;
217          pSPUInfo->iPTSStopTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
218          DebugLog("    GetPacket, STP_DSP: Stop Display, delay: %i", ((delay * 1024) / 90000));
219        }
220        break;
221      case SET_COLOR:
222        {
223          p++;
224
225          if (m_bHasClut)
226          {
227            pSPUInfo->bHasColor = true;
228
229            unsigned int idx[4];
230            // 0, 1, 2, 3
231            idx[0] = (p[0] >> 4) & 0x0f;
232            idx[1] = (p[0]) & 0x0f;
233            idx[2] = (p[1] >> 4) & 0x0f;
234            idx[3] = (p[1]) & 0x0f;
235
236            for (int i = 0; i < 4 ; i++) // emphasis 1, emphasis 2, pattern, back ground
237            {
238              uint8_t* iColor = m_clut[idx[i]];
239
240              pSPUInfo->color[3 - i][0] = iColor[0]; // Y
241              pSPUInfo->color[3 - i][1] = iColor[1]; // Cr
242              pSPUInfo->color[3 - i][2] = iColor[2]; // Cb
243            }
244          }
245
246          DebugLog("    GetPacket, SET_COLOR:");
247          p += 2;
248        }
249        break;
250      case SET_CONTR:  // alpha
251        {
252          p++;
253          // 3, 2, 1, 0
254          alpha[0] = (p[0] >> 4) & 0x0f;
255          alpha[1] = (p[0]) & 0x0f;
256          alpha[2] = (p[1] >> 4) & 0x0f;
257          alpha[3] = (p[1]) & 0x0f;
258
259          // Ignore blank alpha palette.
260          if (alpha[0] | alpha[1] | alpha[2] | alpha[3])
261          {
262            pSPUInfo->bHasAlpha = true;
263
264            // 0, 1, 2, 3
265            pSPUInfo->alpha[0] = alpha[3]; //0 // background, should be hidden
266            pSPUInfo->alpha[1] = alpha[2]; //1
267            pSPUInfo->alpha[2] = alpha[1]; //2 // wm button overlay
268            pSPUInfo->alpha[3] = alpha[0]; //3
269          }
270
271          DebugLog("    GetPacket, SET_CONTR:");
272          p += 2;
273        }
274        break;
275      case SET_DAREA:
276        {
277          p++;
278          pSPUInfo->x = (p[0] << 4) | (p[1] >> 4);
279          pSPUInfo->y = (p[3] << 4) | (p[4] >> 4);
280          pSPUInfo->width = (((p[1] & 0x0f) << 8) | p[2]) - pSPUInfo->x + 1;
281          pSPUInfo->height = (((p[4] & 0x0f) << 8) | p[5]) - pSPUInfo->y + 1;
282          DebugLog("    GetPacket, SET_DAREA: x,y:%i,%i width,height:%i,%i",
283                   pSPUInfo->x, pSPUInfo->y, pSPUInfo->width, pSPUInfo->height);
284          p += 6;
285        }
286        break;
287      case SET_DSPXA:
288        {
289          p++;
290          unsigned __int16 tfaddr = (p[0] << 8 | p[1]); // offset in packet
291          unsigned __int16 bfaddr = (p[2] << 8 | p[3]); // offset in packet
292          pSPUInfo->pTFData = (tfaddr - 4); //pSPUInfo->pData + (tfaddr - 4); // pSPUData->data = packet startaddr - 4
293          pSPUInfo->pBFData = (bfaddr - 4); //pSPUInfo->pData + (bfaddr - 4); // pSPUData->data = packet startaddr - 4
294          p += 4;
295          DebugLog("    GetPacket, SET_DSPXA: tf: %i bf: %i ", tfaddr, bfaddr);
296        }
297        break;
298      case CHG_COLCON:
299        {
300          p++;
301          unsigned __int16 paramlength = p[0] << 8 | p[1];
302          DebugLog("GetPacket, CHG_COLCON, skippin %i bytes", paramlength);
303          p += paramlength;
304        }
305        break;
306
307      default:
308        DebugLog("GetPacket, error parsing control sequence");
309        delete pSPUInfo;
310        return NULL;
311        break;
312      }
313    }
314    DebugLog("  end off SP_DCSQT");
315    if (*p == CMD_END) p++;
316    else
317    {
318      DebugLog("GetPacket, end off SP_DCSQT, but did not found 0xff (CMD_END)");
319    }
320  }
321
322  // parse the rle.
323  // this should be chnaged so it get's converted to a yuv overlay
324  return ParseRLE(pSPUInfo, pUnparsedData);
325}
326
327/*****************************************************************************
328 * AddNibble: read a nibble from a source packet and add it to our integer.
329 *****************************************************************************/
330inline unsigned int AddNibble( unsigned int i_code, uint8_t* p_src, unsigned int* pi_index )
331{
332  if ( *pi_index & 0x1 )
333  {
334    return ( i_code << 4 | ( p_src[(*pi_index)++ >> 1] & 0xf ) );
335  }
336  else
337  {
338    return ( i_code << 4 | p_src[(*pi_index)++ >> 1] >> 4 );
339  }
340}
341
342/*****************************************************************************
343 * ParseRLE: parse the RLE part of the subtitle
344 *****************************************************************************
345 * This part parses the subtitle graphical data and stores it in a more
346 * convenient structure for later decoding. For more information on the
347 * subtitles format, see http://sam.zoy.org/doc/dvd/subtitles/index.html
348 *****************************************************************************/
349CDVDOverlaySpu* CDVDDemuxSPU::ParseRLE(CDVDOverlaySpu* pSPU, uint8_t* pUnparsedData)
350{
351  uint8_t* p_src = pUnparsedData;
352
353  unsigned int i_code = 0;
354
355  unsigned int i_width = pSPU->width;
356  unsigned int i_height = pSPU->height;
357  unsigned int i_x, i_y;
358
359  // allocate a buffer for the result
360  unsigned __int16* p_dest = (unsigned __int16*)pSPU->result;
361
362  /* The subtitles are interlaced, we need two offsets */
363  unsigned int i_id = 0;                   /* Start on the even SPU layer */
364  unsigned int pi_table[2];
365  
366  /* Colormap statistics */
367  int i_border = -1;
368  int stats[4]; stats[0] = stats[1] = stats[2] = stats[3] = 0;
369
370  pi_table[ 0 ] = pSPU->pTFData << 1;
371  pi_table[ 1 ] = pSPU->pBFData << 1;
372
373  for ( i_y = 0 ; i_y < i_height ; i_y++ )
374  {
375    unsigned int *pi_offset = pi_table + i_id;
376
377    for ( i_x = 0 ; i_x < i_width ; i_x += i_code >> 2 )
378    {
379      i_code = AddNibble( 0, p_src, pi_offset );
380
381      if ( i_code < 0x04 )
382      {
383        i_code = AddNibble( i_code, p_src, pi_offset );
384
385        if ( i_code < 0x10 )
386        {
387          i_code = AddNibble( i_code, p_src, pi_offset );
388
389          if ( i_code < 0x040 )
390          {
391            i_code = AddNibble( i_code, p_src, pi_offset );
392
393            if ( i_code < 0x0100 )
394            {
395              /* If the 14 first bits are set to 0, then it's a
396               * new line. We emulate it. */
397              if ( i_code < 0x0004 )
398              {
399                i_code |= ( i_width - i_x ) << 2;
400              }
401              else
402              {
403                /* We have a boo boo ! */
404                CLog::Log(LOGERROR, "ParseRLE: unknown RLE code 0x%.4x", i_code);
405                return NULL;
406              }
407            }
408          }
409        }
410      }
411
412      if ( ( (i_code >> 2) + i_x + i_y * i_width ) > i_height * i_width )
413      {
414        CLog::Log(LOGERROR, "ParseRLE: out of bounds, %i at (%i,%i) is out of %ix%i",
415                 i_code >> 2, i_x, i_y, i_width, i_height );
416        return NULL;
417      }
418
419      // keep trace of all occouring pixels, even keeping the background in mind
420      stats[i_code & 0x3] += i_code >> 2;
421
422      // count the number of pixels for every occouring parts, without background
423      if (pSPU->alpha[i_code & 0x3] != 0x00)
424      {
425        // the last non background pixel is probably the border color
426        i_border = i_code & 0x3;
427        stats[i_border] += i_code >> 2;
428      }
429
430      /* Check we aren't overwriting our data range
431         This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
432         where we use around 96k rather than 64k + 20bytes */
433      if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
434      {
435        CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range.  Need %li bytes", (long)((uint8_t *)p_dest - pSPU->result));
436        return NULL;
437      }
438      *p_dest++ = i_code;
439    }
440
441    /* Check that we didn't go too far */
442    if ( i_x > i_width )
443    {
444      CLog::Log(LOGERROR, "ParseRLE: i_x overflowed, %i > %i", i_x, i_width );
445      return NULL;
446    }
447
448    /* Byte-align the stream */
449    if ( *pi_offset & 0x1 )
450    {
451      (*pi_offset)++;
452    }
453
454    /* Swap fields */
455    i_id = ~i_id & 0x1;
456  }
457
458  /* We shouldn't get any padding bytes */
459  if ( i_y < i_height )
460  {
461    DebugLog("ParseRLE: padding bytes found in RLE sequence" );
462    DebugLog("ParseRLE: send mail to <sam@zoy.org> if you want to help debugging this" );
463
464    /* Skip them just in case */
465    while ( i_y < i_height )
466    {
467      /* Check we aren't overwriting our data range
468         This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
469         where we use around 96k rather than 64k + 20bytes */
470      if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
471      {
472        CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range.  Need %li bytes", (long)((uint8_t *)p_dest - pSPU->result));
473        return NULL;
474      }
475      *p_dest++ = i_width << 2;
476      i_y++;
477    }
478
479    return NULL;
480  }
481
482  DebugLog("ParseRLE: valid subtitle, size: %ix%i, position: %i,%i",
483           pSPU->width, pSPU->height, pSPU->x, pSPU->y );
484
485  // forced spu's (menu overlays) retrieve their alpha/color information from InputStreamNavigator::GetCurrentButtonInfo
486  // also they may contain completely covering data wich is supposed to be hidden normally
487  // since whole spu is drawn, if this is done for forced, that may be displayed
488  // so we must trust what is given
489  if( !pSPU->bForced )
490  {
491    // Handle color if no palette was found.
492    // we only set it if there is a valid i_border color
493    if (!pSPU->bHasColor)
494    {
495      CLog::Log(LOGINFO, "%s - no color palette found, using default", __FUNCTION__);
496      FindSubtitleColor(i_border, stats, pSPU);
497    }
498
499    // check alpha values, for non forced spu's we use a default value
500    if (pSPU->bHasAlpha)
501    {
502      // check alpha values
503      // the array stats represents the nr of pixels for each color channel
504      // thus if there are no pixels to display, we assume the alphas are incorrect.
505      if (!CanDisplayWithAlphas(pSPU->alpha, stats))
506      {
507        CLog::Log(LOGINFO, "%s - no  matching color and alpha found, resetting alpha", __FUNCTION__);
508
509        pSPU->alpha[0] = 0x00; // back ground
510        pSPU->alpha[1] = 0x0f;
511        pSPU->alpha[2] = 0x0f;
512        pSPU->alpha[3] = 0x0f;
513      }
514    }
515    else
516    {
517      CLog::Log(LOGINFO, "%s - ignoring blank alpha palette, using default", __FUNCTION__);
518
519      pSPU->alpha[0] = 0x00; // back ground
520      pSPU->alpha[1] = 0x0f;
521      pSPU->alpha[2] = 0x0f;
522      pSPU->alpha[3] = 0x0f;
523    }
524
525  }
526
527  return pSPU;
528}
529
530void CDVDDemuxSPU::FindSubtitleColor(int last_color, int stats[4], CDVDOverlaySpu* pSPU)
531{
532  const int COLOR_INNER = 0;
533  const int COLOR_SHADE = 1;
534  const int COLOR_BORDER = 2;
535
536  //uint8_t custom_subtitle_color[4][3] = { // blue, yellow and something else (xine)
537  //  { 0x80, 0x90, 0x80 }, // inner color
538  //  { 0x00, 0x90, 0x00 }, // shade color
539  //  { 0x00, 0x90, 0xff }  // border color
540  //};
541
542  uint8_t custom_subtitle_color[4][3] = { // inner color white, gray shading and a black border
543    { 0xff, 0x80, 0x80 }, // inner color, white
544    { 0x80, 0x80, 0x80 }, // shade color, gray
545    { 0x00, 0x80, 0x80 }  // border color, black
546  };
547
548  //uint8_t custom_subtitle_color[4][3] = { // completely white and a black border
549  //  { 0xff, 0x80, 0x80 }, // inner color, white
550  //  { 0xff, 0x80, 0x80 }, // shade color, white
551  //  { 0x00, 0x80, 0x80 }  // border color, black
552  //};
553
554
555  int nrOfUsedColors = 0;
556  for (int i = 0; i < 4; i++)
557  {
558    if (pSPU->alpha[i] > 0) nrOfUsedColors++;
559  }
560
561  if (nrOfUsedColors == 0)
562  {
563    // nothing todo
564    DebugLog("FindSubtitleColor: all 4 alpha channels are 0, nothing todo");
565  }
566  else if (nrOfUsedColors == 1)
567  {
568    // only one color is used, probably the inner color
569    for (int i = 0; i < 4; i++) // find the position that is used
570    {
571      if (pSPU->alpha[i] > 0)
572      {
573        pSPU->color[i][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
574        pSPU->color[i][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
575        pSPU->color[i][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
576        return;
577      }
578    }
579
580  }
581  else
582  {
583    // old code
584
585    if (last_color >= 0 && last_color < 4)
586    {
587      int i, i_inner = -1, i_shade = -1;
588      // Set the border color, the last color is probably the border color
589      pSPU->color[last_color][0] = custom_subtitle_color[COLOR_BORDER][0];
590      pSPU->color[last_color][1] = custom_subtitle_color[COLOR_BORDER][1];
591      pSPU->color[last_color][2] = custom_subtitle_color[COLOR_BORDER][2];
592      stats[last_color] = 0;
593
594    // find the inner colors
595    for ( i = 0 ; i < 4 && i_inner == -1 ; i++ )
596    {
597      if ( stats[i] )
598      {
599        i_inner = i;
600      }
601    }
602
603    // try to find the shade color
604    for ( ; i < 4 && i_shade == -1 ; i++)
605    {
606      if ( stats[i] )
607      {
608        if ( stats[i] > stats[i_inner] )
609        {
610          i_shade = i_inner;
611          i_inner = i;
612        }
613        else
614        {
615          i_shade = i;
616        }
617      }
618    }
619
620    /* Set the inner color */
621    if ( i_inner != -1 )
622    {
623      // white color
624        pSPU->color[i_inner][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
625        pSPU->color[i_inner][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
626        pSPU->color[i_inner][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
627    }
628
629    /* Set the anti-aliasing color */
630    if ( i_shade != -1 )
631    {
632      // gray
633        pSPU->color[i_shade][0] = custom_subtitle_color[COLOR_SHADE][0];
634        pSPU->color[i_shade][1] = custom_subtitle_color[COLOR_SHADE][1];
635        pSPU->color[i_shade][2] = custom_subtitle_color[COLOR_SHADE][2];
636    }
637
638      DebugLog("ParseRLE: using custom palette (border %i, inner %i, shade %i)", last_color, i_inner, i_shade);
639    }
640  }
641}
642
643bool CDVDDemuxSPU::CanDisplayWithAlphas(int a[4], int stats[4])
644{
645  return(
646    a[0] * stats[0] > 0 ||
647    a[1] * stats[1] > 0 ||
648    a[2] * stats[2] > 0 ||
649    a[3] * stats[3] > 0);
650}