#include <lib/dvb/idemux.h>
#include <lib/gdi/gpixmap.h>
-DEFINE_REF(eDVBRadioTextParser);
+DEFINE_REF(eDVBRdsDecoder);
-eDVBRadioTextParser::eDVBRadioTextParser(iDVBDemux *demux)
- :bytesread(0), ptr(0), p1(-1), p2(-1), msgPtr(0), state(0)
+eDVBRdsDecoder::eDVBRdsDecoder(iDVBDemux *demux)
+ :msgPtr(0), bsflag(0), qdar_pos(0), t_ptr(0), qdarmvi_show(0), state(0)
,m_abortTimer(eApp)
{
setStreamID(0xC0, 0xC0);
+ memset(rass_picture_mask, 0, sizeof(rass_picture_mask));
+
if (demux->createPESReader(eApp, m_pes_reader))
eDebug("failed to create PES reader!");
else
- m_pes_reader->connectRead(slot(*this, &eDVBRadioTextParser::processData), m_read_connection);
- CONNECT(m_abortTimer.timeout, eDVBRadioTextParser::abortNonAvail);
+ m_pes_reader->connectRead(slot(*this, &eDVBRdsDecoder::processData), m_read_connection);
+ CONNECT(m_abortTimer.timeout, eDVBRdsDecoder::abortNonAvail);
+}
+
+eDVBRdsDecoder::~eDVBRdsDecoder()
+{
+ // delete cached rass slides
+ for (int page=0; page < 10; ++page)
+ {
+ unsigned char mask = rass_picture_mask[(page*4)/8];
+ if (page % 2)
+ mask >>= 4;
+ int subpage=0;
+ while(mask)
+ {
+ if (mask & 1)
+ {
+ std::string filename = getRassPicture(page, subpage);
+ if (filename.length())
+ remove(filename.c_str());
+ }
+ mask >>= 1;
+ ++subpage;
+ }
+ }
+ remove("/tmp/RassLast.mvi");
}
#define SWAP(x) ((x<<8)|(x>>8))
{ 11025,12000,8000,0 }
};
-void eDVBRadioTextParser::connectUpdatedRadiotext(const Slot0<void> &slot, ePtr<eConnection> &connection)
+void eDVBRdsDecoder::connectEvent(const Slot1<void, int> &slot, ePtr<eConnection> &connection)
{
- connection = new eConnection(this, m_updated_radiotext.connect(slot));
+ connection = new eConnection(this, m_event.connect(slot));
}
-void eDVBRadioTextParser::processPESPacket(__u8 *data, int len)
+void eDVBRdsDecoder::addToPictureMask(int id)
+{
+ int page = id / 1000;
+ int tmp = page > 0 ? id / page : id;
+ int subpage = 0;
+ while(tmp > 1000)
+ {
+ ++subpage;
+ tmp -= 1000;
+ tmp *= 10;
+ }
+ int index = (page*4+subpage)/8;
+ int val = (page%2) ? 16 * (1 << subpage) : (1 << subpage);
+ if (rass_picture_mask[index] & val) // already have this picture
+ return;
+ rass_picture_mask[index] |= val;
+ /* emit */ m_event(RassInteractivePicMaskChanged);
+}
+
+void eDVBRdsDecoder::removeFromPictureMask(int id)
+{
+ int page = id / 1000;
+ int tmp = page > 0 ? id / page : id;
+ int subpage = 0;
+ while(tmp > 1000)
+ {
+ ++subpage;
+ tmp -= 1000;
+ tmp *= 10;
+ }
+ int index = (page*4)/8;
+ int val = (page%2) ? 16 * (1 << subpage) : (1 << subpage);
+ if (rass_picture_mask[index] & val) // have this picture
+ {
+ rass_picture_mask[index] &= ~val;
+ /* emit */ m_event(RassInteractivePicMaskChanged);
+ }
+}
+
+void eDVBRdsDecoder::processPESPacket(__u8 *data, int len)
{
int pos=9+data[8];// skip pes header
+ int cnt=0;
while (pos < len)
{
m_abortTimer.stop();
int ancillary_len = 1 + data[offs - 1];
offs -= ancillary_len;
- while(offs < pos)
- gotAncillaryByte(data[offs++]);
+ gotAncillaryData(data+offs, ancillary_len);
}
}
}
-inline void eDVBRadioTextParser::gotAncillaryByte(__u8 data)
+void eDVBRdsDecoder::process_qdar(unsigned char *buf)
{
- buf[bytesread]=data;
- bytesread+=1;
- if ( bytesread == 128 )
+ if (buf[0] == 0x40 && buf[1] == 0xDA)
{
- while(ptr<128)
+ unsigned int item,cnt,ctrl,item_type;
+ unsigned long item_length,id,item_no,ptr,tmp;
+ unsigned short crc_qdar,crc_read;
+ char fname[50];
+ ptr=4;cnt=0;
+ item=buf[2]<<8; // Number of Items
+ item|=buf[3];
+
+ while ( cnt++ < item ) //read in items
{
- if ( buf[ptr] == 0xFD )
- {
- if (p1 == -1)
- p1 = ptr;
- else
- p2 = ptr;
- }
- if ( p1 != -1 && p2 != -1 )
+ id=buf[ptr++]<<8; //QDarID
+ id|=buf[ptr++];
+
+ item_no=buf[ptr++]<<8; // Item Number
+ item_no|=buf[ptr++];
+
+ ctrl=buf[ptr++]; //controlbyte
+ item_type=buf[ptr++]; //item type
+
+ item_length=buf[ptr++]<<24; // Item length
+ item_length|=buf[ptr++]<<16;
+ item_length|=buf[ptr++]<<8;
+ item_length|=buf[ptr++];
+
+ ptr=ptr+4; // rfu Bytes ... not used
+ tmp=ptr; // calc crc
+ crc_qdar=0xFFFF;
+ while (tmp < ptr+item_length)
+ crc_qdar = crc_ccitt_byte(crc_qdar, buf[tmp++]);
+
+ crc_read=buf[ptr+item_length]<<8;
+ crc_read|=buf[ptr+item_length+1];
+ //eDebug("[RDS/Rass] CRC read: %04X calculated: %04X",crc_read,crc_qdar^0xFFFF);
+
+ if (crc_read == (crc_qdar^0xFFFF)) // process item
{
- int cnt=buf[--p2];
- while ( cnt-- > 0 )
+ switch(item_type)
{
- unsigned char c = buf[--p2];
- if ( state == 1 )
- crc=0xFFFF;
- if ( state >= 1 && state < 11 )
- crc = crc_ccitt_byte(crc, c);
-
- switch (state)
- {
- case 0:
- if ( c==0xFE ) // Startkennung
- state=1;
- break;
- case 1: // 10bit Site Address + 6bit Encoder Address
- case 2:
- case 3: // Sequence Counter
- ++state;
- break;
- case 4:
- leninfo=c;
- ++state;
- break;
- case 5:
- if ( c==0x0A ) // message element code 0x0A Radio Text
- ++state;
- else
- state=0;
- break;
- case 6: // Data Set Number ... ignore
- case 7: // Program Service Number ... ignore
- ++state;
- break;
- case 8: // Message Element Length
- todo=c;
- if ( !todo || todo > 65 || todo > leninfo-4)
- state=0;
+ case 0x01: //Stillframe
+ if (ctrl&0x01) // display slide
+ {
+ sprintf(fname,"/tmp/RassLast.mvi");
+ FILE *fh=fopen(fname,"wb");
+ fwrite(buf+ptr,1,item_length-2,fh);
+ fclose(fh);
+ /*emit*/ m_event(RecvRassSlidePic);
+ qdarmvi_show=1;
+ }
+ if (ctrl&0x02) // save slide for interactive mode
+ {
+ if (id == 0 || id >= 1000)
+ {
+ sprintf(fname,"/tmp/Rass%04d.mvi",(int)id);
+ FILE *fh=fopen(fname,"wb");
+ fwrite(buf+ptr,1,item_length-2,fh);
+ fclose(fh);
+ addToPictureMask(id);
+ }
else
+ eDebug("ignore recv interactive picture id %lu", id);
+ }
+ if (ctrl&0x04) // display slide if nothing had been displayed yet
+ {
+ if (qdarmvi_show != 1)
{
- ++state;
- todo-=2;
- msgPtr=0;
+ sprintf(fname,"/tmp/RassLast.mvi");
+ FILE *fh=fopen(fname,"wb");
+ fwrite(buf+ptr,1,item_length-2,fh);
+ fclose(fh);
+ /*emit*/ m_event(RecvRassSlidePic);
+ qdarmvi_show=1;
}
- break;
- case 9: // Radio Text Status bit:
- // 0 = AB-flagcontrol
- // 1-4 = Transmission-Number
- // 5-6 = Buffer-Config
- ++state; // ignore ...
- break;
- case 10:
- // TODO build a complete radiotext charcode to UTF8 conversion table for all character > 0x80
- switch (c)
+ }
+ if (ctrl&0x08) // delete slide
+ {
+ eDebug("delete slide id %lu, item_no %lu", id, item_no);
+ if (id == 0 || id >= 1000)
{
- case 0 ... 0x7f: break;
- case 0x8d: c='ß'; break;
- case 0x91: c='ä'; break;
- case 0xd1: c='Ä'; break;
- case 0x97: c='ö'; break;
- case 0xd7: c='Ö'; break;
- case 0x99: c='ü'; break;
- case 0xd9: c='Ü'; break;
- default: c=' '; break; // convert all unknown to space
+ eDebug("delete %lu", id);
+ removeFromPictureMask(id);
+ sprintf(fname,"/tmp/Rass%04d.mvi",(int)id); // was item_no ? ! ?
+ remove(fname);
}
- message[msgPtr++]=c;
- if(todo)
- --todo;
else
- ++state;
- break;
- case 11:
- crc16=c<<8;
+ eDebug("ignore del interactive picture id %lu", id);
+ }
+ break;
+ default: //nothing more yet defined
+ break;
+ }
+ }
+ else
+ {
+ eDebug("[RDS/Rass] CRC error, skip Rass-Qdar-Item");
+ }
+
+ ptr=+item_length;
+ }
+ }
+ else
+ {
+ eDebug("[RDS/Rass] No Rass-QDAR archive (%02X %02X) so skipping !\n",buf[0],buf[1]);
+ }
+}
+
+inline void eDVBRdsDecoder::gotAncillaryData(__u8 *buf, int len)
+{
+ int cnt=buf[--len];
+ while ( cnt-- > 0 )
+ {
+ unsigned char c = buf[--len];
+
+ if (bsflag == 1) // byte stuffing
+ {
+ bsflag=2;
+ switch (c)
+ {
+ case 0x00: c=0xFD; break;
+ case 0x01: c=0xFE; break;
+ case 0x02: c=0xFF; break;
+ }
+ }
+
+ if (c == 0xFD && bsflag ==0)
+ bsflag=1;
+ else
+ bsflag=0;
+
+ if (bsflag == 0)
+ {
+ if ( state == 1 )
+ crc=0xFFFF;
+ if (( state >= 1 && state < 11 ) || ( state >=26 && state < 36 ))
+ crc = crc_ccitt_byte(crc, c);
+
+ switch (state)
+ {
+ case 0:
+ if ( c==0xFE ) // Startkennung
+ state=1;
+ break;
+ case 1: // 10bit Site Address + 6bit Encoder Address
+ case 2:
+ case 3: // Sequence Counter
+ ++state;
+ break;
+ case 4:
+ leninfo=c;
+ ++state;
+ break;
+ case 5:
+ switch (c)
+ {
+ case 0x0A: // Radiotext
++state;
break;
- case 12:
- crc16|=c;
- message[msgPtr--]=0;
- while(message[msgPtr] == ' ' && msgPtr > 0)
- message[msgPtr--] = 0;
- if ( crc16 == (crc^0xFFFF) )
+ case 0x46: // Radiotext Plus tags
+ state=38;
+ break;
+ case 0xDA: // Rass
+ state=26;
+ break;
+ default: // reset to state 0
+ state=0;
+ }
+ break;
+
+ // process Radiotext
+ case 6: // Data Set Number ... ignore
+ case 7: // Program Service Number ... ignore
+ ++state;
+ break;
+ case 8: // Message Element Length
+ text_len=c;
+ if ( !text_len || text_len > 65 || text_len > leninfo-4)
+ state=0;
+ else
+ {
+ ++state;
+ text_len-=2;
+ msgPtr=0;
+ }
+ break;
+ case 9: // Radio Text Status bit:
+ // 0 = AB-flagcontrol
+ // 1-4 = Transmission-Number
+ // 5-6 = Buffer-Config
+ ++state; // ignore ...
+ break;
+ case 10:
+ // TODO build a complete radiotext charcode to UTF8 conversion table for all character > 0x80
+ switch (c)
+ {
+ case 0 ... 0x7f: break;
+ case 0x8d: c='ß'; break;
+ case 0x91: c='ä'; break;
+ case 0xd1: c='Ä'; break;
+ case 0x97: c='ö'; break;
+ case 0xd7: c='Ö'; break;
+ case 0x99: c='ü'; break;
+ case 0xd9: c='Ü'; break;
+ default: c=' '; break; // convert all unknown to space
+ }
+ message[msgPtr++]=c;
+ if(text_len)
+ --text_len;
+ else
+ ++state;
+ break;
+ case 11:
+ crc16=c<<8;
+ ++state;
+ break;
+ case 12:
+ crc16|=c;
+ message[msgPtr--]=0;
+ while(message[msgPtr] == ' ' && msgPtr > 0)
+ message[msgPtr--] = 0;
+ if ( crc16 == (crc^0xFFFF) )
+ {
+ eDebug("radiotext: (%s)", message);
+ /*emit*/ m_event(RadioTextChanged);
+ memcpy(lastmessage,message,66);
+ }
+ else
+ eDebug("invalid radiotext crc (%s)", message);
+ state=0;
+ break;
+
+ // process Rass
+ case 26: //MEL
+ text_len = c;
+ text_len2 = c;
+ ++state;
+ text_len-=9;
+ text_len2-=9;
+ t_ptr=0;
+ break;
+ case 27: // SID not used atm
+ ++state;
+ break;
+ case 28: // SID not used atm
+ ++state;
+ break;
+ case 29: // PNR packet number
+ part=c<<16;
+ ++state;
+ break;
+ case 30: // PNR packet number
+ part|=c<<8;
+ ++state;
+ break;
+ case 31: // PNR packet number
+ part|=c;
+ ++state;
+ break;
+ case 32: // NOP number of packets
+ parts=c<<16;
+ ++state;
+ break;
+ case 33: // NOP number of packets
+ parts|=c<<8;
+ ++state;
+ break;
+ case 34: // NOP number of packets
+ parts|=c;
+ ++state;
+ break;
+ case 35:
+ datamessage[t_ptr++]=c;
+ if(text_len)
+ --text_len;
+ else
+ ++state;
+ break;
+ case 36:
+ crc16=c<<8;
+ ++state;
+ break;
+ case 37:
+ crc16|=c;
+ //eDebug("[RDS/Rass] CRC read: %04X CRC calculated: %04X",crc16,crc^0xFFFF);
+ state=0;
+ if ( crc16 == (crc^0xFFFF) )
+ {
+ if (partcnt == -1)
+ partcnt=1;
+ if (partcnt == part)
+ {
+ memcpy(qdar+qdar_pos,datamessage,text_len2+1);
+ qdar_pos=qdar_pos+text_len2+1;
+ if (partcnt == parts)
{
- eDebug("radiotext: (%s)", message);
- /*emit*/ m_updated_radiotext();
+ process_qdar(qdar); // decode qdar archive
+ qdar_pos=0;
+ partcnt=-1;
}
else
- eDebug("invalid radiotext crc (%s)", message);
- state=0;
- break;
+ ++partcnt;
+ }
+ else
+ {
+ qdar_pos=0;
+ partcnt=-1;
+ }
}
- }
- p1=ptr;
- p2=-1;
+ else
+ {
+ eDebug("[RDS/Rass] CRC error, skip Rass-Qdar-Packet");
+ eDebug("[RDS/Rass] CRC read: %04X CRC calculated: %04X",crc16,crc^0xFFFF);
+ partcnt=-1;
+ }
+ state=0;
+ break;
+
+ // process RT plus tags ...
+ case 38: // Message Element Length
+ text_len=c;
+ ++state;
+ break;
+ case 39: // Application ID
+ case 40: // always 0x4BD7 so we ignore it ;)
+ case 41: // Applicationgroup Typecode/PTY ... ignore
+ ++state;
+ break;
+ case 42:
+ rtp_buf[0]=c;
+ ++state;
+ break;
+ case 43:
+ rtp_buf[1]=c;
+ ++state;
+ break;
+ case 44:
+ rtp_buf[2]=c;
+ ++state;
+ break;
+ case 45:
+ rtp_buf[3]=c;
+ ++state;
+ break;
+ case 46: // bit 10#4 = Item Togglebit
+ // bit 10#3 = Item Runningbit
+ // Tag1: bit 10#2..11#5 = Contenttype, 11#4..12#7 = Startmarker, 12#6..12#1 = Length
+ rtp_buf[4]=c;
+ if (lastmessage[0] == 0) // no rds message till now ? quit ...
+ break;
+ int rtp_typ[2],rtp_start[2],rtp_len[2];
+ rtp_typ[0] = (0x38 & rtp_buf[0]<<3) | rtp_buf[1]>>5;
+ rtp_start[0] = (0x3e & rtp_buf[1]<<1) | rtp_buf[2]>>7;
+ rtp_len[0] = 0x3f & rtp_buf[2]>>1;
+ // Tag2: bit 12#0..13#3 = Contenttype, 13#2..14#5 = Startmarker, 14#4..14#0 = Length(5bit)
+ rtp_typ[1] = (0x20 & rtp_buf[2]<<5) | rtp_buf[3]>>3;
+ rtp_start[1] = (0x38 & rtp_buf[3]<<3) | rtp_buf[4]>>5;
+ rtp_len[1] = 0x1f & rtp_buf[4];
+
+ unsigned char rtplus_osd_tmp[64];
+
+ memcpy(rtp_item[rtp_typ[0]],lastmessage+rtp_start[0],rtp_len[0]+1);
+ rtp_item[rtp_typ[0]][rtp_len[0]+1]=0;
+
+ if (rtp_typ[0] != rtp_typ[1])
+ {
+ memcpy(rtp_item[rtp_typ[1]],lastmessage+rtp_start[1],rtp_len[1]+1);
+ rtp_item[rtp_typ[1]][rtp_len[1]+1]=0;
+ }
+
+ // main RTPlus item_types used by the radio stations:
+ // 1 title
+ // 4 artist
+ // 24 info.date_time
+ // 31 stationname
+ // 32 program.now
+ // 39 homepage
+ // 41 phone.hotline
+ // 46 email.hotline
+ // todo: make a window to display all saved items ...
+
+ //create RTPlus OSD for title/artist
+ rtplus_osd[0]=0;
+
+ if ( rtp_item[4][0] != 0 )//artist
+ sprintf((char*)rtplus_osd_tmp," (%s)",rtp_item[4]);
+
+ if ( rtp_item[1][0] != 0 )//title
+ sprintf((char*)rtplus_osd,"%s%s",rtp_item[1],rtplus_osd_tmp);
+
+ if ( rtplus_osd[0] != 0 )
+ {
+ /*emit*/ m_event(RtpTextChanged);
+ eDebug("RTPlus: %s",rtplus_osd);
+ }
+
+ state=0;
+ break;
}
- ++ptr;
}
- if (p1 != -1 && (128-p1) != 128)
- {
- bytesread=ptr=128-p1;
- memcpy(buf, buf+p1, ptr);
- p1=0;
- }
- else
- bytesread=ptr=0;
}
}
-int eDVBRadioTextParser::start(int pid)
+std::string eDVBRdsDecoder::getRassPicture(int page, int subpage)
+{
+ int val=0;
+
+ switch(subpage)
+ {
+ case 0:
+ val=page*1000;
+ break;
+ case 1:
+ val=page*1100;
+ break;
+ case 2:
+ val=page*1110;
+ break;
+ case 3:
+ val=page*1111;
+ break;
+ }
+ char fname[50];
+ sprintf(fname,"/tmp/Rass%04d.mvi",val);
+ return fname;
+}
+
+int eDVBRdsDecoder::start(int pid)
{
int ret = -1;
if (m_pes_reader && !(ret = m_pes_reader->start(pid)))
return ret;
}
-void eDVBRadioTextParser::abortNonAvail()
+void eDVBRdsDecoder::abortNonAvail()
{
eDebug("no ancillary data in audio stream... abort radiotext pes parser");
if (m_pes_reader)
m_pes_reader->stop();
}
+
+ePyObject eDVBRdsDecoder::getRassPictureMask()
+{
+ ePyObject ret = PyTuple_New(5);
+ PyTuple_SET_ITEM(ret, 0, PyInt_FromLong(rass_picture_mask[0]));
+ PyTuple_SET_ITEM(ret, 1, PyInt_FromLong(rass_picture_mask[1]));
+ PyTuple_SET_ITEM(ret, 2, PyInt_FromLong(rass_picture_mask[2]));
+ PyTuple_SET_ITEM(ret, 3, PyInt_FromLong(rass_picture_mask[3]));
+ PyTuple_SET_ITEM(ret, 4, PyInt_FromLong(rass_picture_mask[4]));
+ return ret;
+}