vdr/dvbspu.c
Klaus Schmidinger c80a53ff6e Version 1.3.7
- Fixed a memory leak in thread handling when using NPTL (thanks to Jon Burgess).
- Fixed handling Setup.RecordDolbyDigital, which was broken since version 1.1.6.
- Fixed handling text lengths for itemized EPG texts (thanks to Marcel Wiesweg).
- Fixed the help for LSTE and LSTR (was broken in 1.3.6).
- Improved iso8859-7 fonts (thanks to Dimitrios Dimitrakos).
- Added some 3-letter language codes (thanks to Marcus Mönnig).
- Added language code handling to the subtitling descriptor in 'libsi' (thanks to
  Pekka Virtanen).
- Moved several menu item classes from menu.c to menuitems.[hc] to make them
  available for plugins.
- The epg2html.pl script now handles '|' in description texts.
- The new setup option "OSD/Use small font" can be used to control the use of
  the small font (see MANUAL for details).
- Swapped osd.[hc] and osdbase.[hc] to have the virtual OSD base class named cOsd.
  Plugins may need to adjust their #include statements.
- Colors are now given as AARRGGBB instead of AABBGGRR. The values are mapped to
  the driver's (wrong) sequence in dvbosd.c (this should really be fixed in the
  driver, together with the endian problem).
- The new OSD setup parameters "Left" and "Top" can be used to define the top left
  corner of the OSD.
- The OSD size parameters are now in pixel (as opposed to formerly characters).
  When reading a 'setup.conf' file from an older version of VDR, the OSDwidth
  and OSDheight values will be converted to pixel automatically.
- The OSD is now fully device independent. See the comments in VDR/osd.h and the
  description in PLUGINS.html for information on how a plugin can implement an OSD
  display on arbitrary hardware.
- The OSD (actually its cBitmap class) can now handle XPM files. There are several
  XPM files in the VDR/symbols directory which can be used by skins (some of these
  have been taken from the "elchi" patch). See VDR/skinsttng.c for examples on how
  to use these.
- Due to the changes in the OSD handling the DEBUG_OSD option for a textual OSD
  has been dropped. There will be a plugin that implements a skin with this
  functionality later.
- The entire OSD display can now be implemented via "skins". See VDR/skins.[hc],
  VDR/skinclassic.[hc], VDR/skinsttng.[hc] and PLUGINS.html for information on how
  a plugin can implement its own skin. By default VDR comes with a "Classic" skin
  that implements the OSD display known from previous versions, and the new skin
  named "ST:TNG Panels", which is also the default skin now. The actual skin can
  be selected through "Setup/OSD/Skin".
- The colors used in a skin can now be configured using "themes". See PLUGINS.html
  for information on how a skin can make use of themes, and man vdr(5) for the
  structure of a theme file. The actual theme to use can be selected through
  "Setup/OSD/Theme".
- Added Croatian language texts (thanks to Drazen Dupor).
  NOTE: there is apparently a problem with the newly introduced iso8859-2 font,
  because as soon as Setup/OSD/Language is set to Croatian (currently the last one
  in the list) everything freezes and the vdr processes have to be killed with -9
  and the driver needs to be reloaded. Maybe somebody else can find out what's
  going wrong here...
- Added missing NULL checks when accessing sectionHandler in device.c (thanks to
  Pekka Virtanen).
- Fixed setting the time from the DVB data stream (thanks to Helmut Auer for
  pointing out a frequency/transponder handling mixup). This now also takes the
  actual source (sat, cable etc.) into account. Please go into "Setup/EPG" and
  set the "Set system time" and "Use time from transponder" parameters accordingly
  (this is necessary even if you have already set them before!).
2004-05-16 18:00:00 +02:00

513 lines
13 KiB
C

/*
* SPU decoder for DVB devices
*
* Copyright (C) 2001.2002 Andreas Schultz <aschultz@warp10.net>
*
* This code is distributed under the terms and conditions of the
* GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
*
* parts of this file are derived from the OMS program.
*
* $Id: dvbspu.c 1.6 2004/04/30 13:45:02 kls Exp $
*/
#include <assert.h>
#include <string.h>
#include <inttypes.h>
#include <math.h>
#include "device.h"
#include "dvbspu.h"
/*
* cDvbSpubitmap:
*
* this is a bitmap of the full screen and two palettes
* the normal palette for the background and the highlight palette
*
* Inputs:
* - a SPU rle encoded image on creation, which will be decoded into
* the full screen indexed bitmap
*
* Output:
* - a minimal sized cDvbSpuBitmap a given palette, the indexed bitmap
* will be scanned to get the smallest possible resulting bitmap considering
* transparencies
*/
// #define SPUDEBUG
#ifdef SPUDEBUG
#define DEBUG(format, args...) printf (format, ## args)
#else
#define DEBUG(format, args...)
#endif
// --- cDvbSpuPalette----------------------------------
void cDvbSpuPalette::setPalette(const uint32_t * pal)
{
for (int i = 0; i < 16; i++)
palette[i] = yuv2rgb(pal[i]);
}
// --- cDvbSpuBitmap --------------------------------------------
#define setMin(a, b) if (a > b) a = b
#define setMax(a, b) if (a < b) a = b
#define spuXres 720
#define spuYres 576
#define revRect(r1, r2) { r1.x1 = r2.x2; r1.y1 = r2.y2; r1.x2 = r2.x1; r1.y2 = r2.y1; }
cDvbSpuBitmap::cDvbSpuBitmap(sDvbSpuRect size,
uint8_t * fodd, uint8_t * eodd,
uint8_t * feven, uint8_t * eeven)
{
if (size.x1 < 0 || size.y1 < 0 || size.x2 >= spuXres
|| size.y2 >= spuYres)
throw;
bmpsize = size;
revRect(minsize[0], size);
revRect(minsize[1], size);
revRect(minsize[2], size);
revRect(minsize[3], size);
if (!(bmp = new uint8_t[spuXres * spuYres * sizeof(uint8_t)]))
throw;
memset(bmp, 0, spuXres * spuYres * sizeof(uint8_t));
putFieldData(0, fodd, eodd);
putFieldData(1, feven, eeven);
}
cDvbSpuBitmap::~cDvbSpuBitmap()
{
delete[]bmp;
}
cBitmap *cDvbSpuBitmap::getBitmap(const aDvbSpuPalDescr paldescr,
const cDvbSpuPalette & pal,
sDvbSpuRect & size) const
{
int h = size.height();
int w = size.width();
if (size.y1 + h >= spuYres)
h = spuYres - size.y1 - 1;
if (size.x1 + w >= spuXres)
w = spuXres - size.x1 - 1;
if (w & 0x03)
w += 4 - (w & 0x03);
cBitmap *ret = new cBitmap(w, h, 2);
// set the palette
for (int i = 0; i < 4; i++) {
uint32_t color =
pal.getColor(paldescr[i].index, paldescr[i].trans);
ret->SetColor(i, (tColor) color);
}
// set the content
for (int yp = 0; yp < h; yp++) {
for (int xp = 0; xp < w; xp++) {
uint8_t idx = bmp[(size.y1 + yp) * spuXres + size.x1 + xp];
ret->SetIndex(xp, yp, idx);
}
}
return ret;
}
// find the minimum non-transparent area
bool cDvbSpuBitmap::getMinSize(const aDvbSpuPalDescr paldescr,
sDvbSpuRect & size) const
{
bool ret = false;
for (int i = 0; i < 4; i++) {
if (paldescr[i].trans != 0) {
if (!ret)
size = minsize[i];
else {
setMin(size.x1, minsize[i].x1);
setMin(size.y1, minsize[i].y1);
setMax(size.x2, minsize[i].x2);
setMax(size.y2, minsize[i].y2);
}
ret = true;
}
}
if (ret)
DEBUG("MinSize: (%d, %d) x (%d, %d)\n",
size.x1, size.y1, size.x2, size.y2);
return ret;
}
void cDvbSpuBitmap::putPixel(int xp, int yp, int len, uint8_t colorid)
{
memset(bmp + spuXres * yp + xp, colorid, len);
setMin(minsize[colorid].x1, xp);
setMin(minsize[colorid].y1, yp);
setMax(minsize[colorid].x2, xp + len - 1);
setMax(minsize[colorid].y2, yp + len - 1);
}
static uint8_t getBits(uint8_t * &data, uint8_t & bitf)
{
uint8_t ret = *data;
if (bitf)
ret >>= 4;
else
data++;
bitf ^= 1;
return (ret & 0xf);
}
void cDvbSpuBitmap::putFieldData(int field, uint8_t * data, uint8_t * endp)
{
int xp = bmpsize.x1;
int yp = bmpsize.y1 + field;
uint8_t bitf = 1;
while (data < endp) {
uint16_t vlc = getBits(data, bitf);
if (vlc < 0x0004) {
vlc = (vlc << 4) | getBits(data, bitf);
if (vlc < 0x0010) {
vlc = (vlc << 4) | getBits(data, bitf);
if (vlc < 0x0040) {
vlc = (vlc << 4) | getBits(data, bitf);
}
}
}
uint8_t color = vlc & 0x03;
int len = vlc >> 2;
// if len == 0 -> end sequence - fill to end of line
len = len ? len : bmpsize.x2 - xp + 1;
putPixel(xp, yp, len, color);
xp += len;
if (xp > bmpsize.x2) {
// nextLine
if (!bitf)
data++;
bitf = 1;
xp = bmpsize.x1;
yp += 2;
if (yp > bmpsize.y2)
return;
}
}
}
// --- cDvbSpuDecoder-----------------------------
#define CMD_SPU_MENU 0x00
#define CMD_SPU_SHOW 0x01
#define CMD_SPU_HIDE 0x02
#define CMD_SPU_SET_PALETTE 0x03
#define CMD_SPU_SET_ALPHA 0x04
#define CMD_SPU_SET_SIZE 0x05
#define CMD_SPU_SET_PXD_OFFSET 0x06
#define CMD_SPU_EOF 0xff
#define spuU32(i) ((spu[i] << 8) + spu[i+1])
cDvbSpuDecoder::cDvbSpuDecoder()
{
clean = true;
scaleMode = eSpuNormal;
spu = NULL;
osd = NULL;
spubmp = NULL;
}
cDvbSpuDecoder::~cDvbSpuDecoder()
{
delete spubmp;
delete spu;
delete osd;
}
void cDvbSpuDecoder::processSPU(uint32_t pts, uint8_t * buf)
{
setTime(pts);
DEBUG("SPU pushData: pts: %d\n", pts);
delete spubmp;
spubmp = NULL;
delete[]spu;
spu = buf;
spupts = pts;
DCSQ_offset = cmdOffs();
prev_DCSQ_offset = 0;
clean = true;
}
void cDvbSpuDecoder::setScaleMode(cSpuDecoder::eScaleMode ScaleMode)
{
scaleMode = ScaleMode;
}
void cDvbSpuDecoder::setPalette(uint32_t * pal)
{
palette.setPalette(pal);
}
void cDvbSpuDecoder::setHighlight(uint16_t sx, uint16_t sy,
uint16_t ex, uint16_t ey,
uint32_t palette)
{
aDvbSpuPalDescr pld;
for (int i = 0; i < 4; i++) {
pld[i].index = 0xf & (palette >> (16 + 4 * i));
pld[i].trans = 0xf & (palette >> (4 * i));
}
bool ne = hlpsize.x1 != sx || hlpsize.y1 != sy ||
hlpsize.x2 != ex || hlpsize.y2 != ey ||
pld[0] != hlpDescr[0] || pld[1] != hlpDescr[1] ||
pld[2] != hlpDescr[2] || pld[3] != hlpDescr[3];
if (ne) {
DEBUG("setHighlight: %d,%d x %d,%d\n", sx, sy, ex, ey);
hlpsize.x1 = sx;
hlpsize.y1 = sy;
hlpsize.x2 = ex;
hlpsize.y2 = ey;
memcpy(hlpDescr, pld, sizeof(aDvbSpuPalDescr));
highlight = true;
clean = false;
}
}
void cDvbSpuDecoder::clearHighlight(void)
{
clean &= !highlight;
highlight = false;
hlpsize.x1 = -1;
hlpsize.y1 = -1;
hlpsize.x2 = -1;
hlpsize.y2 = -1;
}
int cDvbSpuDecoder::ScaleYcoord(int value)
{
if (scaleMode == eSpuLetterBox) {
int offset = cDevice::PrimaryDevice()->GetVideoSystem() == vsPAL ? 72 : 60;
return lround((value * 3.0) / 4.0) + offset;
}
else
return value;
}
int cDvbSpuDecoder::ScaleYres(int value)
{
if (scaleMode == eSpuLetterBox)
return lround((value * 3.0) / 4.0);
else
return value;
}
void cDvbSpuDecoder::DrawBmp(sDvbSpuRect & size, cBitmap * bmp)
{
tArea Area = { size.x1, size.y1, size.x2, size.y2, 2 };
osd->SetAreas(&Area, 1);
osd->DrawBitmap(size.x1, size.y1, *bmp);
delete bmp;
}
void cDvbSpuDecoder::Draw(void)
{
Hide();
if (!spubmp)
return;
cBitmap *fg = NULL;
cBitmap *bg = NULL;
sDvbSpuRect bgsize;
sDvbSpuRect hlsize;
hlsize.x1 = hlpsize.x1;
hlsize.y1 = ScaleYcoord(hlpsize.y1);
hlsize.x2 = hlpsize.x2;
hlsize.y2 = ScaleYcoord(hlpsize.y2);
if (highlight)
fg = spubmp->getBitmap(hlpDescr, palette, hlsize);
if (spubmp->getMinSize(palDescr, bgsize)) {
bg = spubmp->getBitmap(palDescr, palette, bgsize);
if (scaleMode == eSpuLetterBox) {
// the coordinates have to be modified for letterbox
int y1 = ScaleYres(bgsize.y1) + bgsize.height();
bgsize.y2 = y1 + bgsize.height();
bgsize.y1 = y1;
}
}
if (bg || fg) {
if (osd == NULL)
if ((osd = cOsdProvider::NewOsd(0, 0)) == NULL) {
dsyslog("NewOsd failed\n");
return;
}
if (fg)
DrawBmp(hlsize, fg);
if (bg)
DrawBmp(bgsize, bg);
osd->Flush();
}
clean = true;
}
void cDvbSpuDecoder::Hide(void)
{
delete osd;
osd = NULL;
}
void cDvbSpuDecoder::Empty(void)
{
Hide();
delete spubmp;
spubmp = NULL;
delete[]spu;
spu = NULL;
clearHighlight();
clean = true;
}
int cDvbSpuDecoder::setTime(uint32_t pts)
{
if (!spu)
return 0;
if (spu && !clean)
Draw();
while (DCSQ_offset != prev_DCSQ_offset) { /* Display Control Sequences */
int i = DCSQ_offset;
state = spNONE;
uint32_t exec_time = spupts + spuU32(i) * 1024;
if ((pts != 0) && (exec_time > pts))
return 0;
DEBUG("offs = %d, rel = %d, time = %d, pts = %d, diff = %d\n",
i, spuU32(i) * 1024, exec_time, pts, exec_time - pts);
if (pts != 0) {
uint16_t feven = 0;
uint16_t fodd = 0;
i += 2;
prev_DCSQ_offset = DCSQ_offset;
DCSQ_offset = spuU32(i);
DEBUG("offs = %d, DCSQ = %d, prev_DCSQ = %d\n",
i, DCSQ_offset, prev_DCSQ_offset);
i += 2;
while (spu[i] != CMD_SPU_EOF) { // Command Sequence
switch (spu[i]) {
case CMD_SPU_SHOW: // show subpicture
DEBUG("\tshow subpicture\n");
state = spSHOW;
i++;
break;
case CMD_SPU_HIDE: // hide subpicture
DEBUG("\thide subpicture\n");
state = spHIDE;
i++;
break;
case CMD_SPU_SET_PALETTE: // CLUT
palDescr[0].index = spu[i + 2] & 0xf;
palDescr[1].index = spu[i + 2] >> 4;
palDescr[2].index = spu[i + 1] & 0xf;
palDescr[3].index = spu[i + 1] >> 4;
i += 3;
break;
case CMD_SPU_SET_ALPHA: // transparency palette
palDescr[0].trans = spu[i + 2] & 0xf;
palDescr[1].trans = spu[i + 2] >> 4;
palDescr[2].trans = spu[i + 1] & 0xf;
palDescr[3].trans = spu[i + 1] >> 4;
i += 3;
break;
case CMD_SPU_SET_SIZE: // image coordinates
size.x1 = (spu[i + 1] << 4) | (spu[i + 2] >> 4);
size.x2 = ((spu[i + 2] & 0x0f) << 8) | spu[i + 3];
size.y1 = (spu[i + 4] << 4) | (spu[i + 5] >> 4);
size.y2 = ((spu[i + 5] & 0x0f) << 8) | spu[i + 6];
DEBUG("\t(%d, %d) x (%d, %d)\n",
size.x1, size.y1, size.x2, size.y2);
i += 7;
break;
case CMD_SPU_SET_PXD_OFFSET: // image 1 / image 2 offsets
fodd = spuU32(i + 1);
feven = spuU32(i + 3);
DEBUG("\todd = %d even = %d\n", fodd, feven);
i += 5;
break;
case CMD_SPU_MENU:
DEBUG("\tspu menu\n");
state = spMENU;
i++;
break;
default:
esyslog("invalid sequence in control header (%.2x)\n",
spu[i]);
assert(0);
i++;
break;
}
}
if (fodd != 0 && feven != 0) {
delete spubmp;
spubmp = new cDvbSpuBitmap(size, spu + fodd, spu + feven,
spu + feven, spu + cmdOffs());
}
} else if (!clean)
state = spSHOW;
if (state == spSHOW || state == spMENU)
Draw();
if (state == spHIDE)
Hide();
if (pts == 0)
return 0;
}
return 1;
}