From d1e450f95a9b79b144a00dcf0f9356ff6cb9bfb2 Mon Sep 17 00:00:00 2001 From: Andreas Monzner Date: Sat, 24 Mar 2007 00:49:20 +0000 Subject: [PATCH] add a radiomode background picture (mvi file.. changable in /etc/enigma2/config ... config.misc.radiopic = /bla/blubber.mvi add support for radio text plus, add rass (radio screen show) support (yes used by SWR3, cont.ra and DASDING) thanks to seddi for some piece of code for better single iframe support its recommend to update in dreambox-dvb-modules.bb CVSDATE for dm7025 to 20070323 --- data/Makefile.am | 2 +- data/keymap.xml | 12 + data/radio.mvi | Bin 0 -> 74876 bytes data/rass_logo.png | 0 data/rass_page1.png | 0 data/rass_page2.png | 0 data/rass_page3.png | 0 data/rass_page4.png | 0 data/skin.xml | 77 ++-- data/skin_default.xml | 79 +++- lib/dvb/radiotext.cpp | 608 ++++++++++++++++++++++----- lib/dvb/radiotext.h | 31 +- lib/python/Components/Converter/Makefile.am | 2 +- lib/python/Components/Converter/RadioText.py | 0 lib/python/Components/Converter/RdsInfo.py | 53 +++ lib/python/Components/Sources/Makefile.am | 2 +- lib/python/Components/Sources/RadioText.py | 0 lib/python/Components/Sources/RdsDecoder.py | 29 ++ lib/python/Screens/ChannelSelection.py | 58 ++- lib/python/Screens/InfoBar.py | 12 +- lib/python/Screens/InfoBarGenerics.py | 51 ++- lib/python/Screens/Makefile.am | 2 +- lib/python/Screens/RdsDisplay.py | 272 ++++++++++++ lib/service/iservice.h | 34 +- lib/service/servicedvb.cpp | 93 +++- lib/service/servicedvb.h | 17 +- lib/service/servicemp3.h | 2 +- lib/service/servicexine.h | 2 +- 28 files changed, 1192 insertions(+), 246 deletions(-) create mode 100755 data/radio.mvi create mode 100644 data/rass_logo.png create mode 100644 data/rass_page1.png create mode 100644 data/rass_page2.png create mode 100644 data/rass_page3.png create mode 100644 data/rass_page4.png delete mode 100644 lib/python/Components/Converter/RadioText.py create mode 100644 lib/python/Components/Converter/RdsInfo.py delete mode 100644 lib/python/Components/Sources/RadioText.py create mode 100644 lib/python/Components/Sources/RdsDecoder.py create mode 100644 lib/python/Screens/RdsDisplay.py diff --git a/data/Makefile.am b/data/Makefile.am index c866eac..ed26ec5 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -5,4 +5,4 @@ SUBDIRS = countries fonts defaults extensions installdir = $(DATADIR)/enigma2 install_DATA = \ - *.xml *.png encoding.conf + *.xml *.png encoding.conf radio.mvi diff --git a/data/keymap.xml b/data/keymap.xml index 835c521..5c0cb54 100644 --- a/data/keymap.xml +++ b/data/keymap.xml @@ -300,6 +300,18 @@ + + + + + + + + + + + + diff --git a/data/radio.mvi b/data/radio.mvi new file mode 100755 index 0000000000000000000000000000000000000000..bbd8016eb1ee1ef7e84563cf5b89c148afa3421b GIT binary patch literal 74876 zcmXVX2{crV8~43)XEDq$*&1UTVT_QmWsO10SR3lKm$6e4vb0cl!XRaxG(?S}RV68; zm$9!&@+u)qM3zD&h5F|IecyA=oO|Y+Ip@yYd*^w6+XDdL6CDISN$vV}Het&aNl8g5 zVM+h5Wd3i-{=eG#|BFZ@%Kh&}5|*6&|4KnvN=iz~$|?W=RsyMPKs_iT;CNtAAld&E z`JfYX-=2s7GBqwJdIwp5hmpw+L&F_NncF&s4o+pqu>!r9C=5%-tmZ)&v}I|N0chi8Co! zQW|{F#zlSZ`r_L<^BSc0wMHkJYIeJe=D;xr-`NQqBx>Ndnz=z* zxs8ZoqT_T%FEdf$kE%^0_LU;q&lO3q7fHER-8+c$uK&Ec4FR5qiI^ow+w5bsFJf)> zB7?}Vs{pNa?-%Vb!AU-^{kC=?+#f9L)UME$+Kn#F&n`GiiDvHOJBVy+yB_A;9+1q!%@Wh@$!(wf72>~;`wjv z1kuf|Za2e>?4RW9bx)BDR>sTPTS~X&>s`w!slB2pYRKnW4INRto#aw-4eQo}N!`Kf z=iaM#y*0h_n11uN1#9EWbLlDLBR}~6;A+@5%9>_3()oTmy?NBEhZl^tbcQB`QxAF` z!KJA6)u~eTr8y}>M`EoMML6Z96D|{R0%P^BR+h=_@;~7)+bdyzbxl6MM~8cQj)Q)- zcN}fe+ob(3bA$6t<^#Ftt#>;gKDeSIzmFQ7#1E--F$guHzN7@GdX`fA`W_uqMuY`W zw3f!)=fjHqE6{39-4ssZu)^erd#wK2gqzP=Kj+!U7%QhO*I?X3TKaSUZ7}C;OUc7B zw6>l<8wXXwEj*ghZ=d7(OhO(_1=2MU{AHqGQ}Clw+gh}UTOHk*Xeg?&V*Sot@hDhn zZ)IM(Y_mZ20DhT2NOa>%acMWMocCR@t^x*l42FcrGnPbJ!fk3x>7^9!CE|v+;&RQ) zgMh|}XyV|l)5}F+A+ZO(CSkX?c}Y9jIk$Z+Pv=uqr0n+6x_eQ89wi1He%75}36Mck z19U#zS6s_^pCmRN&rNpnAHA#ww5njJn#l)kfynde;hQU|bwxk3Md4&LvgHz9>wDrV zeGa5!_;Q2`>grgm?=A-PG7L-;p?B$<_C9)bfqv&Tv~w#j2FAN4|6!jmCcc>yh{KiW z$x&9Ky-A}nw-ES_m<&Cxt)A{9uncT0fwzK8nuB)LA3Krb$r4pykDL?B;SJ-tR)L^RoRK{;@=pe-V@ z1zHM;9x~H2?|KxC!S|;2;xtPp&VDcDqTG;aU)ynS?%^@gzw^4wFMPk;jG*Ho2%^V0 z&LdhQQk~6NbxcA3-*=l#FdHQZEJr!4-SC&nGR}NR4mg1r7#O?3o_>b*FzQ#iZm#(J(9t;69uqr}-a$E1N~& zEZ-*?Enk0_OR_OP#}=DCQnT`8%Q3oBF}26l2RXv$n)Q*2JFaliWb#yL42K*RH0h=I zxP6WQJ2S=M4pDsqr~4%VFX?#_iPf&+uoH6I3g|e2ql@rF2hmewU3e@dA^PO_91g7Y zyy$CFEdIA*CGp|VnqT5jr}#66xF+l%EC=tTB28$bB^QPu2fHEoN)^0-B`3qc+tG*exMFF=u zv=<_jL+zWg?9vf#X;Xdr*%xDtDe!P|z(#906fMxCB^m%8*`iKUr&!;d`e@TENpR}@ zStk;4%U!smuP<3qwxyDVqTvKShA6V-Z6zfu@vGD(`V`w-Q$;q{Kc{%o7U-z#)+~7M z!bA6>o!FW{AYZxwkR?A^`WW-%i;}Pt!e-RUVEm~38kpcJlQm!?OG0ETGg+ddpxtu@ zy{EZa!7@1Yqse@7UsGpUak|XJ3sD%(A68lwHd}!v^dGE(``lQfo7#g`6R>MnP85^d zM$aS;jYX{jF$g7;T}t2-{ttZz6sxLW`JTiKGmsIy>b?)YHWbb7e3X{>tnIoB4YFkD zC>(j5(T@jrzB?kxW>eH1%)YUsU5>P`akAI^G*|il9d^_C-E(O&px*8g_r@w&!vOwj z`*Q5g*rJVIGXdBOS$w=hbW*=Ol(p?G2mTBu55TvWc$%M9t;98pLqWhorANy`*mEBq zJF6}i`1u^Dk^P7E`mUSVc7)(CZd8Th2_IwPYS!6mHyJUI|0npt3E_Q zZWrQ=LO8AD(;4QKxjqU$;KqS?{|`u=_BXeCMXQb&QETo#?Mw?JHU)*|*(KNLMipme z#$F6jzKh%P?83;jEhDzWOyXGau8rXFtZK+{#A4`r90odx~(?dSqaB!CuP@4tW?=<3Z;+la}q)m7}RI$~)0 zz6!CVPdx8s^}ffLog8y9u6S^u2m*-ki0Y4w`^K|4UGz;2@J{7x#NEaev6U+(sG>se zKffWLg@%u#J%{lpwll1G+&>55*<0qeGFOA|BtznI>*eCjTgvZ>Br$_XoQzAdNv_*^ z2SQ!9b?=pu^=%I+ICgV5yPL!VW{0jm|DG&pa+3;m+o -z8XgnJol-Bxz@Ktw8V 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 literal 0 HcmV?d00001 diff --git a/data/rass_logo.png b/data/rass_logo.png new file mode 100644 index 0000000..e69de29 diff --git a/data/rass_page1.png b/data/rass_page1.png new file mode 100644 index 0000000..e69de29 diff --git a/data/rass_page2.png b/data/rass_page2.png new file mode 100644 index 0000000..e69de29 diff --git a/data/rass_page3.png b/data/rass_page3.png new file mode 100644 index 0000000..e69de29 diff --git a/data/rass_page4.png b/data/rass_page4.png new file mode 100644 index 0000000..e69de29 diff --git a/data/skin.xml b/data/skin.xml index 36f7325..cf2d2c7 100644 --- a/data/skin.xml +++ b/data/skin.xml @@ -45,18 +45,13 @@ - - - - RadioText - - - + + - - - - + + + + - - - + + + - + SNR - + AGC - + BER - + SNR - + AGC - + BER - + - + IsCrypted - + IsMultichannel - + IsWidescreen - + Name - + WithSeconds - + StartTime Default - + StartTime Default - + Name - + Name - + Remaining InMinutes - + Duration InMinutes - + Progress - + - + - + SubservicesAvailable - + SubservicesAvailable - + - + - + - + diff --git a/data/skin_default.xml b/data/skin_default.xml index 4bdc5c1..65e810d 100644 --- a/data/skin_default.xml +++ b/data/skin_default.xml @@ -3,8 +3,7 @@ - - + @@ -18,8 +17,7 @@ - - + @@ -75,7 +73,7 @@ - + @@ -248,41 +246,43 @@ - - RadioText + + RadioText Name - WithSeconds Progress - - StartTime - Default + + RasInteractiveAvailable + - + StartTime Default - - Name - - + Name - - + Remaining InMinutes - + + Name + + + StartTime + Default + + Duration InMinutes @@ -331,11 +331,50 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/dvb/radiotext.cpp b/lib/dvb/radiotext.cpp index 87498da..eff40e9 100644 --- a/lib/dvb/radiotext.cpp +++ b/lib/dvb/radiotext.cpp @@ -3,19 +3,45 @@ #include #include -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)) @@ -65,14 +91,54 @@ 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_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) { @@ -130,137 +196,440 @@ void eDVBRadioTextParser::processPESPacket(__u8 *data, int 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))) @@ -268,9 +637,20 @@ int eDVBRadioTextParser::start(int 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; +} diff --git a/lib/dvb/radiotext.h b/lib/dvb/radiotext.h index b4ae0e2..8c354ff 100644 --- a/lib/dvb/radiotext.h +++ b/lib/dvb/radiotext.h @@ -6,24 +6,37 @@ #include #include -class eDVBRadioTextParser: public iObject, public ePESParser, public Object +class eDVBRdsDecoder: public iObject, public ePESParser, public Object { - DECLARE_REF(eDVBRadioTextParser); - int bytesread, ptr, p1, p2, msgPtr; - unsigned char buf[128], message[66], leninfo, todo, state; + DECLARE_REF(eDVBRdsDecoder); + int msgPtr, bsflag, qdar_pos, t_ptr, qdarmvi_show; + unsigned char message[66], lastmessage[66], datamessage[256], rtp_buf[5], leninfo, text_len, text_len2, state; + unsigned char rtp_item[64][64], rtplus_osd[64]; //rtp + unsigned char qdar[60*1024]; //60 kB for holding Rass qdar archive unsigned short crc16, crc; + long part, parts, partcnt; + enum { RadioTextChanged, RtpTextChanged, RassInteractivePicMaskChanged, RecvRassSlidePic }; + unsigned char rass_picture_mask[5]; // 40 bits... (10 * 4 pictures) + void addToPictureMask(int id); + void removeFromPictureMask(int id); public: - eDVBRadioTextParser(iDVBDemux *demux); + eDVBRdsDecoder(iDVBDemux *demux); + ~eDVBRdsDecoder(); int start(int pid); - void connectUpdatedRadiotext(const Slot0 &slot, ePtr &connection); - const char *getCurrentText() { return msgPtr ? (const char*)message : ""; } + void connectEvent(const Slot1 &slot, ePtr &connection); + const char *getRadioText() { return (const char*)message; } + const char *getRtpText() { return (const char*)rtplus_osd; } + ePyObject getRassPictureMask(); + std::string getRassPicture(int page, int subpage); + std::string getRassSlideshowPicture() { return "/tmp/RassLast.mvi"; } private: void abortNonAvail(); void processPESPacket(__u8 *pkt, int len); - inline void gotAncillaryByte(__u8 data); + inline void gotAncillaryData(__u8 *data, int len); + void process_qdar(unsigned char*); ePtr m_pes_reader; ePtr m_read_connection; - Signal0 m_updated_radiotext; + Signal1 m_event; eTimer m_abortTimer; }; diff --git a/lib/python/Components/Converter/Makefile.am b/lib/python/Components/Converter/Makefile.am index b25a32d..a5d95f5 100644 --- a/lib/python/Components/Converter/Makefile.am +++ b/lib/python/Components/Converter/Makefile.am @@ -3,5 +3,5 @@ installdir = $(LIBDIR)/enigma2/python/Components/Converter install_PYTHON = \ __init__.py ClockToText.py Converter.py EventName.py StaticText.py EventTime.py \ Poll.py RemainingToText.py StringList.py ServiceName.py FrontendInfo.py ServiceInfo.py \ - ConditionalShowHide.py ServicePosition.py ValueRange.py RadioText.py Streaming.py + ConditionalShowHide.py ServicePosition.py ValueRange.py RdsInfo.py Streaming.py diff --git a/lib/python/Components/Converter/RadioText.py b/lib/python/Components/Converter/RadioText.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/python/Components/Converter/RdsInfo.py b/lib/python/Components/Converter/RdsInfo.py new file mode 100644 index 0000000..3a7b2be --- /dev/null +++ b/lib/python/Components/Converter/RdsInfo.py @@ -0,0 +1,53 @@ +from enigma import iRdsDecoder, iPlayableService +from Components.Converter.Converter import Converter +from Components.Element import cached + +class RdsInfo(Converter, object): + RASS_INTERACTIVE_AVAILABLE = 0 + RTP_TEXT_CHANGED = 1 + RADIO_TEXT_CHANGED = 2 + + def __init__(self, type): + Converter.__init__(self, type) + self.type = { + "RadioText": self.RADIO_TEXT_CHANGED, + "RtpText": self.RTP_TEXT_CHANGED, + "RasInteractiveAvailable": self.RASS_INTERACTIVE_AVAILABLE + }[type] + + self.interesting_events = { + self.RADIO_TEXT_CHANGED: [iPlayableService.evUpdatedRadioText], + self.RTP_TEXT_CHANGED: [iPlayableService.evUpdatedRtpText], + self.RASS_INTERACTIVE_AVAILABLE: [iPlayableService.evUpdatedRassInteractivePicMask] + }[self.type] + + @cached + def getText(self): + decoder = self.source.decoder + text = "" + if decoder: + if self.type == self.RADIO_TEXT_CHANGED: + text = decoder.getText(iRdsDecoder.RadioText) + elif self.type == self.RTP_TEXT_CHANGED: + text = decoder.getText(iRdsDecoder.RtpText) + else: + print "unknown RdsInfo Converter type", self.type + return text + + text = property(getText) + + @cached + def getBoolean(self): + decoder = self.source.decoder + if self.type == self.RASS_INTERACTIVE_AVAILABLE: + mask = decoder and decoder.getRassInteractiveMask() + return (mask and mask[0] & 1 and True) or False + elif self.type == self.RADIO_TEXT_CHANGED: + return (len(decoder.getText(iRdsDecoder.RadioText)) and True) or False + elif self.type == self.RTP_TEXT_CHANGED: + return (len(decoder.getText(iRdsDecoder.RtpText)) and True) or False + boolean = property(getBoolean) + + def changed(self, what): + if what[0] != self.CHANGED_SPECIFIC or what[1] in self.interesting_events: + Converter.changed(self, what) diff --git a/lib/python/Components/Sources/Makefile.am b/lib/python/Components/Sources/Makefile.am index 5e6a30a..b5e16d98 100644 --- a/lib/python/Components/Sources/Makefile.am +++ b/lib/python/Components/Sources/Makefile.am @@ -2,5 +2,5 @@ installdir = $(LIBDIR)/enigma2/python/Components/Sources install_PYTHON = \ __init__.py Clock.py EventInfo.py Source.py MenuList.py CurrentService.py \ - FrontendStatus.py Boolean.py Config.py ServiceList.py RadioText.py StreamService.py \ + FrontendStatus.py Boolean.py Config.py ServiceList.py RdsDecoder.py StreamService.py \ StaticText.py diff --git a/lib/python/Components/Sources/RadioText.py b/lib/python/Components/Sources/RadioText.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/python/Components/Sources/RdsDecoder.py b/lib/python/Components/Sources/RdsDecoder.py new file mode 100644 index 0000000..886f81f --- /dev/null +++ b/lib/python/Components/Sources/RdsDecoder.py @@ -0,0 +1,29 @@ +from Components.PerServiceDisplay import PerServiceBase +from Components.Element import cached +from enigma import iPlayableService +from Source import Source + +class RdsDecoder(PerServiceBase, Source, object): + def __init__(self, navcore): + Source.__init__(self) + PerServiceBase.__init__(self, navcore, + { + iPlayableService.evStart: self.gotEvent, + iPlayableService.evUpdatedRadioText: self.gotEvent, + iPlayableService.evUpdatedRtpText: self.gotEvent, + iPlayableService.evUpdatedRassInteractivePicMask: self.gotEvent, + iPlayableService.evEnd: self.gotEvent + }, with_event=True) + + @cached + def getDecoder(self): + service = self.navcore.getCurrentService() + return service and service.rdsDecoder() + + decoder = property(getDecoder) + + def gotEvent(self, what): + if what in [iPlayableService.evStart, iPlayableService.evEnd]: + self.changed((self.CHANGED_CLEAR,)) + else: + self.changed((self.CHANGED_SPECIFIC, what)) diff --git a/lib/python/Screens/ChannelSelection.py b/lib/python/Screens/ChannelSelection.py index 5a0f117..7f9f797 100644 --- a/lib/python/Screens/ChannelSelection.py +++ b/lib/python/Screens/ChannelSelection.py @@ -1,7 +1,7 @@ from Screen import Screen from Components.Button import Button from Components.ServiceList import ServiceList -from Components.ActionMap import NumberActionMap, ActionMap +from Components.ActionMap import NumberActionMap, ActionMap, HelpableActionMap from Components.MenuList import MenuList from Components.ServiceEventTracker import ServiceEventTracker from EpgSelection import EPGSelection @@ -11,11 +11,13 @@ from Screens.FixedMenu import FixedMenu from Tools.NumericalTextInput import NumericalTextInput from Components.NimManager import nimmanager from Components.Sources.Clock import Clock +from Components.Sources.RdsDecoder import RdsDecoder from Components.Input import Input from Components.ParentalControl import parentalControl from Screens.InputBox import InputBox, PinInput from Screens.MessageBox import MessageBox from Screens.ServiceInfo import ServiceInfo +from Screens.RdsDisplay import RassInteractive from ServiceReference import ServiceReference from Tools.BoundFunction import boundFunction from re import * @@ -1256,32 +1258,30 @@ class ChannelSelection(ChannelSelectionBase, ChannelSelectionEdit, ChannelSelect self.revertMode = None self.close(None) -from Screens.InfoBarGenerics import InfoBarEvent, InfoBarServiceName, InfoBarInstantRecord, InfoBarRadioText +from Screens.InfoBarGenerics import InfoBarEvent, InfoBarServiceName -class RadioInfoBar(Screen, InfoBarEvent, InfoBarServiceName, InfoBarInstantRecord): +class RadioInfoBar(Screen, InfoBarEvent, InfoBarServiceName): def __init__(self, session): Screen.__init__(self, session) InfoBarEvent.__init__(self) InfoBarServiceName.__init__(self) - InfoBarInstantRecord.__init__(self) self["CurrentTime"] = Clock() + self["RdsDecoder"] = RdsDecoder(self.session.nav) -class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelSelectionEPG, InfoBarRadioText): - +class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelSelectionEPG): ALLOW_SUSPEND = True - def __init__(self, session): + def __init__(self, session, infobar): ChannelSelectionBase.__init__(self, session) ChannelSelectionEdit.__init__(self) ChannelSelectionEPG.__init__(self) - InfoBarRadioText.__init__(self) - + self.infobar = infobar config.radio = ConfigSubsection(); config.radio.lastservice = ConfigText() config.radio.lastroot = ConfigText() self.onLayoutFinish.append(self.onCreate) - self.info = session.instantiateDialog(RadioInfoBar) + self.info = session.instantiateDialog(RadioInfoBar) # our simple infobar self["actions"] = ActionMap(["OkCancelActions", "TvRadioActions"], { @@ -1297,6 +1297,37 @@ class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelS iPlayableService.evEnd: self.__evServiceEnd }) +########## RDS Radiotext / Rass Support BEGIN + self.infobar = infobar # reference to real infobar (the one and only) + self["RdsDecoder"] = self.info["RdsDecoder"] + self["RdsActions"] = HelpableActionMap(self, "InfobarRdsActions", + { + "startRassInteractive": (self.startRassInteractive, _("View Rass interactive...")) + },-1) + self["RdsActions"].setEnabled(False) + infobar.rds_display.onRassInteractivePossibilityChanged.append(self.RassInteractivePossibilityChanged) + + def startRassInteractive(self): + self.info.hide(); + self.infobar.rass_interactive = self.session.openWithCallback(self.RassInteractiveClosed, RassInteractive) + + def RassInteractiveClosed(self): + self.info.show() + self.infobar.rass_interactive = None + self.infobar.RassSlidePicChanged() + + def RassInteractivePossibilityChanged(self, state): + self["RdsActions"].setEnabled(state) +########## RDS Radiotext / Rass Support END + + def closeRadio(self): + self.infobar.rds_display.onRassInteractivePossibilityChanged.remove(self.RassInteractivePossibilityChanged) + self.info.hide() + #set previous tv service + lastservice=eServiceReference(config.tv.lastservice.value) + self.session.nav.playService(lastservice) + self.close(None) + def __evServiceStart(self): service = self.session.nav.getCurrentService() if service: @@ -1371,13 +1402,6 @@ class ChannelSelectionRadio(ChannelSelectionBase, ChannelSelectionEdit, ChannelS config.radio.lastservice.save() self.saveRoot() - def closeRadio(self): - self.info.hide() - #set previous tv service - lastservice=eServiceReference(config.tv.lastservice.value) - self.session.nav.playService(lastservice) - self.close(None) - class SimpleChannelSelection(ChannelSelectionBase): def __init__(self, session, title): ChannelSelectionBase.__init__(self, session) diff --git a/lib/python/Screens/InfoBar.py b/lib/python/Screens/InfoBar.py index 24c2b40..ab40838 100644 --- a/lib/python/Screens/InfoBar.py +++ b/lib/python/Screens/InfoBar.py @@ -12,7 +12,7 @@ from Components.config import config from Tools.Notifications import AddNotificationWithCallback from Screens.InfoBarGenerics import InfoBarShowHide, \ - InfoBarNumberZap, InfoBarChannelSelection, InfoBarMenu, InfoBarRadioText, \ + InfoBarNumberZap, InfoBarChannelSelection, InfoBarMenu, InfoBarRdsDecoder, \ InfoBarEPG, InfoBarEvent, InfoBarServiceName, InfoBarSeek, InfoBarInstantRecord, \ InfoBarAudioSelection, InfoBarAdditionalInfo, InfoBarNotifications, InfoBarDish, \ InfoBarSubserviceSelection, InfoBarTuner, InfoBarShowMovies, InfoBarTimeshift, \ @@ -23,7 +23,7 @@ from Screens.InfoBarGenerics import InfoBarShowHide, \ from Screens.HelpMenu import HelpableScreen, HelpMenu class InfoBar(InfoBarShowHide, - InfoBarNumberZap, InfoBarChannelSelection, InfoBarMenu, InfoBarEPG, InfoBarRadioText, + InfoBarNumberZap, InfoBarChannelSelection, InfoBarMenu, InfoBarEPG, InfoBarRdsDecoder, InfoBarEvent, InfoBarServiceName, InfoBarInstantRecord, InfoBarAudioSelection, HelpableScreen, InfoBarAdditionalInfo, InfoBarNotifications, InfoBarDish, InfoBarSubserviceSelection, InfoBarTuner, InfoBarTimeshift, InfoBarSeek, @@ -47,7 +47,7 @@ class InfoBar(InfoBarShowHide, for x in HelpableScreen, \ InfoBarShowHide, \ - InfoBarNumberZap, InfoBarChannelSelection, InfoBarMenu, InfoBarEPG, InfoBarRadioText, \ + InfoBarNumberZap, InfoBarChannelSelection, InfoBarMenu, InfoBarEPG, InfoBarRdsDecoder, \ InfoBarEvent, InfoBarServiceName, InfoBarInstantRecord, InfoBarAudioSelection, \ InfoBarAdditionalInfo, InfoBarNotifications, InfoBarDish, InfoBarSubserviceSelection, \ InfoBarTuner, InfoBarTimeshift, InfoBarSeek, InfoBarSummarySupport, InfoBarTimeshiftState, \ @@ -67,7 +67,11 @@ class InfoBar(InfoBarShowHide, if config.usage.e1like_radio_mode.value: self.showRadioChannelList(True) else: - self.session.open(ChannelSelectionRadio) + self.rds_display.hide() # in InfoBarRdsDecoder + self.session.openWithCallback(self.ChannelSelectionRadioClosed, ChannelSelectionRadio, self) + + def ChannelSelectionRadioClosed(self, *arg): + self.rds_display.show() # in InfoBarRdsDecoder def showMovies(self): self.session.openWithCallback(self.movieSelected, MovieSelection) diff --git a/lib/python/Screens/InfoBarGenerics.py b/lib/python/Screens/InfoBarGenerics.py index 2ccd52d..78a67b7 100644 --- a/lib/python/Screens/InfoBarGenerics.py +++ b/lib/python/Screens/InfoBarGenerics.py @@ -12,7 +12,6 @@ from Components.ProgressBar import * from Components.ServiceEventTracker import ServiceEventTracker from Components.Sources.CurrentService import CurrentService from Components.Sources.EventInfo import EventInfo -from Components.Sources.RadioText import RadioText from Components.Sources.FrontendStatus import FrontendStatus from Components.Sources.Boolean import Boolean from Components.Sources.Clock import Clock @@ -32,6 +31,7 @@ from Screens.MinuteInput import MinuteInput from Screens.TimerSelection import TimerSelection from Screens.PictureInPicture import PictureInPicture from Screens.SubtitleDisplay import SubtitleDisplay +from Screens.RdsDisplay import RdsInfoDisplay, RassInteractive from Screens.SleepTimerEdit import SleepTimerEdit from ServiceReference import ServiceReference @@ -538,10 +538,53 @@ class InfoBarEvent: self["Event_Now"] = EventInfo(self.session.nav, EventInfo.NOW) self["Event_Next"] = EventInfo(self.session.nav, EventInfo.NEXT) -class InfoBarRadioText: - """provides radio (RDS) text info display""" +class InfoBarRdsDecoder: + """provides RDS and Rass support/display""" def __init__(self): - self["RadioText"] = RadioText(self.session.nav) + self.rds_display = self.session.instantiateDialog(RdsInfoDisplay) + self.rass_interactive = None + + self.__event_tracker = ServiceEventTracker(screen=self, eventmap= + { + iPlayableService.evEnd: self.__serviceStopped, + iPlayableService.evUpdatedRassSlidePic: self.RassSlidePicChanged + }) + + self["RdsActions"] = HelpableActionMap(self, "InfobarRdsActions", + { + "startRassInteractive": (self.startRassInteractive, _("View Rass interactive...")) + },-1) + + self["RdsActions"].setEnabled(False) + + self.onLayoutFinish.append(self.rds_display.show) + self.rds_display.onRassInteractivePossibilityChanged.append(self.RassInteractivePossibilityChanged) + + def RassInteractivePossibilityChanged(self, state): + self["RdsActions"].setEnabled(state) + + def RassSlidePicChanged(self): + if not self.rass_interactive: + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + if decoder: + decoder.showRassSlidePicture() + + def __serviceStopped(self): + if self.rass_interactive is not None: + rass_interactive = self.rass_interactive + self.rass_interactive = None + rass_interactive.close() + + def startRassInteractive(self): + self.rds_display.hide() + self.rass_interactive = self.session.openWithCallback(self.RassInteractiveClosed, RassInteractive) + + def RassInteractiveClosed(self, *val): + if self.rass_interactive is not None: + self.rass_interactive = None + self.RassSlidePicChanged() + self.rds_display.show() class InfoBarServiceName: def __init__(self): diff --git a/lib/python/Screens/Makefile.am b/lib/python/Screens/Makefile.am index d5512e7..920a97f 100644 --- a/lib/python/Screens/Makefile.am +++ b/lib/python/Screens/Makefile.am @@ -12,4 +12,4 @@ install_PYTHON = \ Console.py InputBox.py ChoiceBox.py SimpleSummary.py ImageWizard.py \ MediaPlayer.py TimerSelection.py PictureInPicture.py TimeDateInput.py \ SubtitleDisplay.py SubservicesQuickzap.py ParentalControlSetup.py NumericalTextInputHelpDialog.py \ - SleepTimerEdit.py Ipkg.py + SleepTimerEdit.py Ipkg.py RdsDisplay.py diff --git a/lib/python/Screens/RdsDisplay.py b/lib/python/Screens/RdsDisplay.py new file mode 100644 index 0000000..4190b94 --- /dev/null +++ b/lib/python/Screens/RdsDisplay.py @@ -0,0 +1,272 @@ +from enigma import iPlayableService, loadPNG, iRdsDecoder, ePoint, gRGB +from Screens.Screen import Screen +from Components.Sources.RdsDecoder import RdsDecoder +from Components.ActionMap import NumberActionMap +from Components.ServiceEventTracker import ServiceEventTracker +from Components.Pixmap import Pixmap +from Components.Label import Label +from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE + +class RdsInfoDisplay(Screen): + ALLOW_SUSPEND = True + + def __init__(self, session): + Screen.__init__(self, session) + + self.__event_tracker = ServiceEventTracker(screen=self, eventmap= + { + iPlayableService.evEnd: self.__serviceStopped, + iPlayableService.evUpdatedRadioText: self.RadioTextChanged, + iPlayableService.evUpdatedRtpText: self.RtpTextChanged, + iPlayableService.evUpdatedRassInteractivePicMask: self.RassInteractivePicMaskChanged, + }) + + self["RadioText"] = Label() + self["RtpText"] = Label() + self["RassLogo"] = Pixmap() + + self.onLayoutFinish.append(self.hideWidgets) + self.rassInteractivePossible=False + self.onRassInteractivePossibilityChanged = [ ] + + def hideWidgets(self): + for x in (self["RadioText"],self["RtpText"],self["RassLogo"]): + x.hide() + + def RadioTextChanged(self): + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + rdsText = decoder and decoder.getText(iRdsDecoder.RadioText) + if rdsText and len(rdsText): + self["RadioText"].setText(rdsText) + self["RadioText"].show() + else: + self["RadioText"].hide() + + def RtpTextChanged(self): + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + rtpText = decoder and decoder.getText(iRdsDecoder.RtpText) + if rtpText and len(rtpText): + self["RtpText"].setText(rtpText) + self["RtpText"].show() + else: + self["RtpText"].hide() + + def RassInteractivePicMaskChanged(self): + if not self.rassInteractivePossible: + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + mask = decoder and decoder.getRassInteractiveMask() + if mask[0] & 1: #rass interactive index page available + self["RassLogo"].show() + self.rassInteractivePossible = True + for x in self.onRassInteractivePossibilityChanged: + x(True) + + def __serviceStopped(self): + self.hideWidgets() + if self.rassInteractivePossible: + self.rassInteractivePossible = False + for x in self.onRassInteractivePossibilityChanged: + x(False) + +class RassInteractive(Screen): + def __init__(self, session): + Screen.__init__(self, session) + + self["actions"] = NumberActionMap( [ "NumberActions", "RassInteractiveActions" ], + { + "exit": self.close, + "0": lambda x : self.numPressed(0), + "1": lambda x : self.numPressed(1), + "2": lambda x : self.numPressed(2), + "3": lambda x : self.numPressed(3), + "4": lambda x : self.numPressed(4), + "5": lambda x : self.numPressed(5), + "6": lambda x : self.numPressed(6), + "7": lambda x : self.numPressed(7), + "8": lambda x : self.numPressed(8), + "9": lambda x : self.numPressed(9), + "nextPage": self.nextPage, + "prevPage": self.prevPage, + "nextSubPage": self.nextSubPage, + "prevSubPage": self.prevSubPage + }) + + self.__event_tracker = ServiceEventTracker(screen=self, eventmap= + { + iPlayableService.evUpdatedRassInteractivePicMask: self.recvRassInteractivePicMaskChanged + }) + + self["subpages_1"] = Pixmap() + self["subpages_2"] = Pixmap() + self["subpages_3"] = Pixmap() + self["subpages_4"] = Pixmap() + self["subpages_5"] = Pixmap() + self["subpages_6"] = Pixmap() + self["subpages_7"] = Pixmap() + self["subpages_8"] = Pixmap() + self["subpages_9"] = Pixmap() + self["Marker"] = Label(">") + + self.subpage = { + 1 : self["subpages_1"], + 2 : self["subpages_2"], + 3 : self["subpages_3"], + 4 : self["subpages_4"], + 5 : self["subpages_5"], + 6 : self["subpages_6"], + 7 : self["subpages_7"], + 8 : self["subpages_8"], + 9 : self["subpages_9"] } + + self.subpage_png = { + 1 : loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, "rass_page1.png")), + 2 : loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, "rass_page2.png")), + 3 : loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, "rass_page3.png")), + 4 : loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, "rass_page4.png")) } + + self.current_page=0; + self.current_subpage=0; + self.showRassPage(0,0) + self.onLayoutFinish.append(self.updateSubPagePixmaps) + + def updateSubPagePixmaps(self): + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + if not decoder: # this should never happen + print "NO RDS DECODER in showRassPage" + else: + mask = decoder.getRassInteractiveMask() + page = 1 + while page < 10: + subpage_cnt = self.countAvailSubpages(page, mask) + subpage = self.subpage[page] + if subpage_cnt > 0: + if subpage.instance: + png = self.subpage_png[subpage_cnt] + if png: + subpage.instance.setPixmap(png) + subpage.show() + else: + print "rass png missing" + else: + subpage.hide() + page += 1 + + def recvRassInteractivePicMaskChanged(self): + self.updateSubPagePixmaps() + + def showRassPage(self, page, subpage): + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + if not decoder: # this should never happen + print "NO RDS DECODER in showRassPage" + else: + decoder.showRassInteractivePic(page, subpage) + page_diff = page - self.current_page + self.current_page = page + if page_diff: + current_pos = self["Marker"].getPosition() + y = current_pos[1] + y += page_diff * 25 + self["Marker"].setPosition(current_pos[0],y) + + def getMaskForPage(self, page, masks=None): + if not masks: + service = self.session.nav.getCurrentService() + decoder = service and service.rdsDecoder() + if not decoder: # this should never happen + print "NO RDS DECODER in getMaskForPage" + masks = decoder.getRassInteractiveMask() + if masks: + mask = masks[(page*4)/8] + if page % 2: + mask >>= 4 + else: + mask &= 0xF + return mask + + def countAvailSubpages(self, page, masks): + mask = self.getMaskForPage(page, masks) + cnt = 0 + while mask: + if mask & 1: + cnt += 1 + mask >>= 1 + return cnt + + def nextPage(self): + mask = 0 + page = self.current_page + while mask == 0: + page += 1 + if page > 9: + page = 0 + mask = self.getMaskForPage(page) + self.numPressed(page) + + def prevPage(self): + mask = 0 + page = self.current_page + while mask == 0: + if page > 0: + page -= 1 + else: + page = 9 + mask = self.getMaskForPage(page) + self.numPressed(page) + + def nextSubPage(self): + self.numPressed(self.current_page) + + def prevSubPage(self): + num = self.current_page + mask = self.getMaskForPage(num) + cur_bit = 1 << self.current_subpage + tmp = cur_bit + while True: + if tmp == 1: + tmp = 8 + else: + tmp >>= 1 + if tmp == cur_bit: # no other subpage avail + return + if mask & tmp: # next subpage found + subpage = 0 + while tmp > 1: # convert bit to subpage + subpage += 1 + tmp >>= 1 + self.current_subpage = subpage + self.showRassPage(num, subpage) + return + + def numPressed(self, num): + mask = self.getMaskForPage(num) + if self.current_page == num: + self.skip = 0 + cur_bit = 1 << self.current_subpage + tmp = cur_bit + else: + self.skip = 1 + cur_bit = 16 + tmp = 1 + while True: + if not self.skip: + if tmp == 8 and cur_bit < 16: + tmp = 1 + else: + tmp <<= 1 + else: + self.skip = 0 + if tmp == cur_bit: # no other subpage avail + return + if mask & tmp: # next subpage found + subpage = 0 + while tmp > 1: # convert bit to subpage + subpage += 1 + tmp >>= 1 + self.current_subpage = subpage + self.showRassPage(num, subpage) + return diff --git a/lib/service/iservice.h b/lib/service/iservice.h index feaa771..df4e302 100644 --- a/lib/service/iservice.h +++ b/lib/service/iservice.h @@ -483,17 +483,30 @@ public: }; SWIG_TEMPLATE_TYPEDEF(ePtr, iAudioDelayPtr); -SWIG_IGNORE(iRadioText); -class iRadioText: public iObject +class iRdsDecoder_ENUMS { #ifdef SWIG - iRadioText(); - ~iRadioText(); + iRdsDecoder_ENUMS(); + ~iRdsDecoder_ENUMS(); #endif public: - virtual std::string getRadioText(int x=0)=0; + enum { RadioText, RtpText }; }; -SWIG_TEMPLATE_TYPEDEF(ePtr, iRadioTextPtr); + +SWIG_IGNORE(iRdsDecoder); +class iRdsDecoder: public iObject, public iRdsDecoder_ENUMS +{ +#ifdef SWIG + iRdsDecoder(); + ~iRdsDecoder(); +#endif +public: + virtual std::string getText(int x=RadioText)=0; + virtual void showRassSlidePicture()=0; + virtual void showRassInteractivePic(int page, int subpage)=0; + virtual SWIG_PYOBJECT(ePyObject) getRassInteractiveMask()=0; +}; +SWIG_TEMPLATE_TYPEDEF(ePtr, iRdsDecoderPtr); SWIG_IGNORE(iSubserviceList); class iSubserviceList: public iObject @@ -676,8 +689,13 @@ public: /* when cueSheet is implemented */ evCuesheetChanged, - /* when radioText is implemented */ + /* when rdsDecoder is implemented */ evUpdatedRadioText, + evUpdatedRtpText, + + /* Radio Screenshow Support */ + evUpdatedRassSlidePic, + evUpdatedRassInteractivePicMask, evVideoSizeChanged, @@ -712,7 +730,7 @@ public: virtual SWIG_VOID(RESULT) cueSheet(ePtr &SWIG_OUTPUT)=0; virtual SWIG_VOID(RESULT) subtitle(ePtr &SWIG_OUTPUT)=0; virtual SWIG_VOID(RESULT) audioDelay(ePtr &SWIG_OUTPUT)=0; - virtual SWIG_VOID(RESULT) radioText(ePtr &SWIG_OUTPUT)=0; + virtual SWIG_VOID(RESULT) rdsDecoder(ePtr &SWIG_OUTPUT)=0; }; SWIG_TEMPLATE_TYPEDEF(ePtr, iPlayableServicePtr); diff --git a/lib/service/servicedvb.cpp b/lib/service/servicedvb.cpp index 9d9f2d1..e47ae52 100644 --- a/lib/service/servicedvb.cpp +++ b/lib/service/servicedvb.cpp @@ -1475,7 +1475,7 @@ RESULT eDVBServicePlay::audioDelay(ePtr &ptr) return 0; } -RESULT eDVBServicePlay::radioText(ePtr &ptr) +RESULT eDVBServicePlay::rdsDecoder(ePtr &ptr) { ptr = this; return 0; @@ -1705,8 +1705,8 @@ int eDVBServicePlay::selectAudioStream(int i) if (m_decoder->setAudioPID(program.audioStreams[i].pid, program.audioStreams[i].type)) return -4; - if (m_radiotext_parser) - m_radiotext_parser->start(program.audioStreams[i].pid); + if (m_rds_decoder) + m_rds_decoder->start(program.audioStreams[i].pid); if (m_dvb_service && !m_is_pvr) { @@ -1743,20 +1743,81 @@ RESULT eDVBServicePlay::selectChannel(int i) return 0; } -std::string eDVBServicePlay::getRadioText(int x) +std::string eDVBServicePlay::getText(int x) { - if (m_radiotext_parser) + if (m_rds_decoder) switch(x) { - case 0: - return convertLatin1UTF8(m_radiotext_parser->getCurrentText()); + case RadioText: + return convertLatin1UTF8(m_rds_decoder->getRadioText()); + case RtpText: + return convertLatin1UTF8(m_rds_decoder->getRtpText()); } return ""; } -void eDVBServicePlay::radioTextUpdated() +void eDVBServicePlay::rdsDecoderEvent(int what) { - m_event((iPlayableService*)this, evUpdatedRadioText); + switch(what) + { + case eDVBRdsDecoder::RadioTextChanged: + m_event((iPlayableService*)this, evUpdatedRadioText); + break; + case eDVBRdsDecoder::RtpTextChanged: + m_event((iPlayableService*)this, evUpdatedRtpText); + break; + case eDVBRdsDecoder::RassInteractivePicMaskChanged: + m_event((iPlayableService*)this, evUpdatedRassInteractivePicMask); + break; + case eDVBRdsDecoder::RecvRassSlidePic: + m_event((iPlayableService*)this, evUpdatedRassSlidePic); + break; + } +} + +void eDVBServicePlay::showRassSlidePicture() +{ + if (m_rds_decoder) + { + if (m_decoder) + { + std::string rass_slide_pic = m_rds_decoder->getRassSlideshowPicture(); + if (rass_slide_pic.length()) + m_decoder->showSinglePic(rass_slide_pic.c_str()); + else + eDebug("empty filename for rass slide picture received!!"); + } + else + eDebug("no MPEG Decoder to show iframes avail"); + } + else + eDebug("showRassSlidePicture called.. but not decoder"); +} + +void eDVBServicePlay::showRassInteractivePic(int page, int subpage) +{ + if (m_rds_decoder) + { + if (m_decoder) + { + std::string rass_interactive_pic = m_rds_decoder->getRassPicture(page, subpage); + if (rass_interactive_pic.length()) + m_decoder->showSinglePic(rass_interactive_pic.c_str()); + else + eDebug("empty filename for rass interactive picture %d/%d received!!", page, subpage); + } + else + eDebug("no MPEG Decoder to show iframes avail"); + } + else + eDebug("showRassInteractivePic called.. but not decoder"); +} + +ePyObject eDVBServicePlay::getRassInteractiveMask() +{ + if (m_rds_decoder) + return m_rds_decoder->getRassPictureMask(); + Py_RETURN_NONE; } int eDVBServiceBase::getFrontendInfo(int w) @@ -2089,11 +2150,11 @@ void eDVBServicePlay::switchToLive() m_decoder = 0; m_decode_demux = 0; m_teletext_parser = 0; - m_radiotext_parser = 0; + m_rds_decoder = 0; m_subtitle_parser = 0; m_new_dvb_subtitle_page_connection = 0; m_new_subtitle_page_connection = 0; - m_radiotext_updated_connection = 0; + m_rds_decoder_event_connection = 0; m_video_event_connection = 0; /* free the timeshift service handler, we need the resources */ @@ -2113,11 +2174,11 @@ void eDVBServicePlay::switchToTimeshift() m_decode_demux = 0; m_decoder = 0; m_teletext_parser = 0; - m_radiotext_parser = 0; + m_rds_decoder = 0; m_subtitle_parser = 0; m_new_subtitle_page_connection = 0; m_new_dvb_subtitle_page_connection = 0; - m_radiotext_updated_connection = 0; + m_rds_decoder_event_connection = 0; m_video_event_connection = 0; m_timeshift_active = 1; @@ -2259,9 +2320,9 @@ void eDVBServicePlay::updateDecoder() ePtr data_demux; if (!h.getDataDemux(data_demux)) { - m_radiotext_parser = new eDVBRadioTextParser(data_demux); - m_radiotext_parser->connectUpdatedRadiotext(slot(*this, &eDVBServicePlay::radioTextUpdated), m_radiotext_updated_connection); - m_radiotext_parser->start(apid); + m_rds_decoder = new eDVBRdsDecoder(data_demux); + m_rds_decoder->connectEvent(slot(*this, &eDVBServicePlay::rdsDecoderEvent), m_rds_decoder_event_connection); + m_rds_decoder->start(apid); } } } diff --git a/lib/service/servicedvb.h b/lib/service/servicedvb.h index 34ed972..7c785c4 100644 --- a/lib/service/servicedvb.h +++ b/lib/service/servicedvb.h @@ -89,7 +89,7 @@ class eDVBServicePlay: public eDVBServiceBase, public iAudioTrackSelection, public iAudioChannelSelection, public iSubserviceList, public iTimeshiftService, public iCueSheet, public iSubtitleOutput, public iAudioDelay, - public iRadioText + public iRdsDecoder { DECLARE_REF(eDVBServicePlay); public: @@ -112,7 +112,7 @@ public: RESULT cueSheet(ePtr &ptr); RESULT subtitle(ePtr &ptr); RESULT audioDelay(ePtr &ptr); - RESULT radioText(ePtr &ptr); + RESULT rdsDecoder(ePtr &ptr); // iPauseableService RESULT pause(); @@ -144,8 +144,11 @@ public: int getCurrentChannel(); RESULT selectChannel(int i); - // iRadioText - std::string getRadioText(int i=0); + // iRdsDecoder + std::string getText(int i=0); + void showRassSlidePicture(); + void showRassInteractivePic(int page, int subpage); + ePyObject getRassInteractiveMask(); // iSubserviceList int getNumberOfSubservices(); @@ -267,9 +270,9 @@ private: void checkSubtitleTiming(); /* radiotext */ - ePtr m_radiotext_parser; - ePtr m_radiotext_updated_connection; - void radioTextUpdated(); + ePtr m_rds_decoder; + ePtr m_rds_decoder_event_connection; + void rdsDecoderEvent(int); ePtr m_video_event_connection; void video_event(struct iTSMPEGDecoder::videoEvent); diff --git a/lib/service/servicemp3.h b/lib/service/servicemp3.h index 4245c81..4996488 100644 --- a/lib/service/servicemp3.h +++ b/lib/service/servicemp3.h @@ -66,7 +66,7 @@ public: RESULT cueSheet(ePtr &ptr) { ptr = 0; return -1; } RESULT subtitle(ePtr &ptr) { ptr = 0; return -1; } RESULT audioDelay(ePtr &ptr) { ptr = 0; return -1; } - RESULT radioText(ePtr &ptr) { ptr = 0; return -1; } + RESULT rdsDecoder(ePtr &ptr) { ptr = 0; return -1; } // iPausableService RESULT pause(); diff --git a/lib/service/servicexine.h b/lib/service/servicexine.h index 4a50b11..b56a485 100644 --- a/lib/service/servicexine.h +++ b/lib/service/servicexine.h @@ -69,7 +69,7 @@ public: RESULT cueSheet(ePtr &ptr) { ptr = 0; return -1; } RESULT subtitle(ePtr &ptr) { ptr = 0; return -1; } RESULT audioDelay(ePtr &ptr) { ptr = 0; return -1; } - RESULT radioText(ePtr &ptr) { ptr = 0; return -1; } + RESULT rdsDecoder(ePtr &ptr) { ptr = 0; return -1; } // iPausableService RESULT pause(); -- 2.7.4