X-Git-Url: https://git.cweiske.de/enigma2.git/blobdiff_plain/d4a3610bf6cc725b724d6ca2677ae086387f0c76..0c59a4279f93f08fe95fca5f2e55f3e025f0cceb:/lib/dvb/radiotext.cpp diff --git a/lib/dvb/radiotext.cpp b/lib/dvb/radiotext.cpp index 34a5acd6..9f8cf5f0 100644 --- a/lib/dvb/radiotext.cpp +++ b/lib/dvb/radiotext.cpp @@ -3,19 +3,46 @@ #include #include -DEFINE_REF(eDVBRadioTextParser); +DEFINE_REF(eDVBRdsDecoder); -eDVBRadioTextParser::eDVBRadioTextParser(iDVBDemux *demux) - :bytesread(0), ptr(0), p1(-1), p2(-1), msgPtr(0), state(0) - ,m_abortTimer(eApp) +eDVBRdsDecoder::eDVBRdsDecoder(iDVBDemux *demux) + :msgPtr(0), bsflag(0), qdar_pos(0), t_ptr(0), qdarmvi_show(0), state(0) + ,m_abortTimer(eTimer::create(eApp)) { setStreamID(0xC0, 0xC0); + memset(rass_picture_mask, 0, sizeof(rass_picture_mask)); + memset(rtp_item, 0, sizeof(rtp_item)); + 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)) @@ -65,12 +92,51 @@ static int frequency[3][4] = { { 11025,12000,8000,0 } }; -void eDVBRadioTextParser::connectUpdatedRadiotext(const Slot0 &slot, ePtr &connection) +void eDVBRdsDecoder::connectEvent(const Slot1 &slot, ePtr &connection) +{ + connection = new eConnection(this, m_event.connect(slot)); +} + +void eDVBRdsDecoder::addToPictureMask(int id) { - connection = new eConnection(this, m_updated_radiotext.connect(slot)); + 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 eDVBRadioTextParser::processPESPacket(__u8 *data, int len) +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 @@ -84,7 +150,7 @@ void eDVBRadioTextParser::processPESPacket(__u8 *data, int len) int channel = mode == 3 ? 1 : 2; int id = (data[pos + 1] >> 3) & 1; int emphasis_bit = data[pos + 3] & 3; - int protection_bit = data[pos + 1] & 1; + //int protection_bit = data[pos + 1] & 1; int rate = -1; int sample_freq = -1; int layer = -1; @@ -127,150 +193,470 @@ void eDVBRadioTextParser::processPESPacket(__u8 *data, int len) if (data[offs] == 0xFD) { - m_abortTimer.stop(); + m_abortTimer->stop(); int ancillary_len = 1 + data[offs - 1]; offs -= ancillary_len; - while(offs < pos) - gotAncillaryByte(data[offs++]); + gotAncillaryData(data+offs, ancillary_len); } } } -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]; + + if (rtp_start[0] < 66 && (rtp_len[0]+rtp_start[0]) < 66) + { + 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]) + { + if (rtp_start[1] < 66 && (rtp_len[1]+rtp_start[1]) < 66) + { + 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))) - m_abortTimer.startLongTimer(20); + m_abortTimer->startLongTimer(20); 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; +}