diff --git a/CONTRIBUTORS b/CONTRIBUTORS index bddbeed1..4cd65b55 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1024,6 +1024,8 @@ Rolf Ahrenberg for improving cControl::Launch() to keep 'control' from pointing to uninitialized memory for adding internationalization to the "skincurses" plugin + for helping with adding compatibility mode for playback of recordings made with + the subtitles plugin Ralf Klueber for reporting a bug in cutting a recording if there is only a single editing mark @@ -1293,6 +1295,8 @@ Marcus M Pekka Virtanen for adding language code handling to the subtitling descriptor in 'libsi' for adding missing NULL checks when accessing sectionHandler in device.c + for writing the subtitle plugin, which helped in implementing subtitle handling + in VDR John Kennedy for publishing "A Fast Bresenham Algorithm For Drawing Ellipses" (found at @@ -1419,6 +1423,8 @@ Marco Schl for fixing a problem with characters >0x7F in the modified version of skipspace() for reporting a faulty comment in Make.config.template for fixing checking for ttDolbyLast in cDevice::SetCurrentAudioTrack() + for fixing selecting the audio track when pressing Ok in the Audio menu + for implementing handling DVB subtitles Jürgen Schmitz for reporting a bug in displaying the current channel when switching via the SVDRP diff --git a/HISTORY b/HISTORY index c89b2183..f178c3f4 100644 --- a/HISTORY +++ b/HISTORY @@ -5416,3 +5416,32 @@ Video Disk Recorder Revision History function to hand through the Level. - Fixed checking for ttDolbyLast in cDevice::SetCurrentAudioTrack() (thanks to Marco Schlüßler). + +2007-10-12: Version 1.5.10 + +- Implemented handling DVB subtitles (thanks to Marco Schlüßler, and also to + Pekka Virtanen for writing the subtitle plugin, which helped in implementing + subtitle handling in VDR). +- The new remote control key "Subtitles" can be used to bring up the list + of available subtitles. +- The new setup option "DVB/Subtitle languages" can be used to define the + preferred languages for subtitles. +- Fixed selecting the audio track when pressing Ok in the Audio menu (thanks + to Marco Schlüßler). +- Implemented display of DVB subtitles in live viewing mode. +- Implemented subtitle track selection. +- Implemented bitmap color reduction and shrinking to display subtitles even + on devices that can't display the necessary number of colors. +- Added compatibility mode for playback of recordings made with the subtitles + plugin (with some help from Rolf Ahrenberg). +- The new setup option "DVB/Subtitle offset" can be used to shift the location + of the subtitles in the vertical direction. +- The new setup options "DVB/Subtitle foreground/background transparency" + define an additional level of transparency for the foreground and background + color of subtitles. +- Existing recordings made with the subtitle plugin can be given an 'X' record + in their info.vdr file, so that subtitles can be automatically selected upon + replay, according to the preferred language setup, as in + X 3 03 ger deutsch + (see vdr.5). Note that these entries need to be added in the proper sequence, + so that they correspond with the actual track languages in the recording. diff --git a/MANUAL b/MANUAL index 60c6a71f..106533c1 100644 --- a/MANUAL +++ b/MANUAL @@ -59,6 +59,7 @@ Version 1.4 Mute mute Audio select audio track + Subtitles select subtitles Schedule \ Channels | @@ -675,6 +676,31 @@ Version 1.4 many "Audio language" options which allow you to select the individual preferred languages. + Display subtitles = no If set to 'yes', the first available subtitles in the list + of preferred subtitle languages will be turned on when + switching to a channel that provides subtitles. + + Subtitle languages = 0 Some tv stations broadcast various subtitle tracks in different + languages. This option allows you to define which language(s) + you prefer in such cases. By default, or if none of the + preferred languages is broadcast, no subtitles will + be selected when switching to such a channel. If this option + is set to a non-zero value, the menu page will contain that + many "Subtitle language" options which allow you to select the + individual preferred languages. + + Subtitle offset = 0 Allows you to shift the location of the subtitles in the + vertical direction. The valid range is -50...50. This option + is only avialable if "Display subtitles" is set to 'yes'. + + Subtitle foreground transparency = 0 + Subtitle background transparency = 0 + These define an additional level of transparency for the + foreground and background color of subtitles. Valid ranges + are 0...9 for foreground transparency, and 0...10 for + background transparency. By default the values as broadcast + are used. + LNB: SLOF = 11700 The switching frequency (in MHz) between low and diff --git a/Makefile b/Makefile index 294ca788..949ef1ee 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.106 2007/08/25 08:52:17 kls Exp $ +# $Id: Makefile 1.107 2007/09/01 12:19:53 kls Exp $ .DELETE_ON_ERROR: @@ -37,7 +37,7 @@ DOXYFILE = Doxyfile SILIB = $(LSIDIR)/libsi.a OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o dvbosd.o\ - dvbplayer.o dvbspu.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\ + dvbplayer.o dvbspu.o dvbsubtitle.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\ lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o rcu.o\ receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o shutdown.o\ skinclassic.o skins.o skinsttng.o sources.o spu.o status.o svdrp.o themes.o thread.o\ diff --git a/channels.c b/channels.c index d725ac76..b01bb931 100644 --- a/channels.c +++ b/channels.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.c 1.54 2007/07/21 14:55:01 kls Exp $ + * $Id: channels.c 1.55 2007/10/12 14:40:53 kls Exp $ */ #include "channels.h" @@ -440,12 +440,12 @@ static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[] return q - s; } -void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int Tpid) +void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid) { int mod = CHANNELMOD_NONE; if (vpid != Vpid || ppid != Ppid || tpid != Tpid) mod |= CHANNELMOD_PIDS; - int m = IntArraysDiffer(apids, Apids, alangs, ALangs) | IntArraysDiffer(dpids, Dpids, dlangs, DLangs); + int m = IntArraysDiffer(apids, Apids, alangs, ALangs) | IntArraysDiffer(dpids, Dpids, dlangs, DLangs) | IntArraysDiffer(spids, Spids, slangs, SLangs); if (m & STRDIFF) mod |= CHANNELMOD_LANGS; if (m & VALDIFF) @@ -468,7 +468,16 @@ void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE q += IntArrayToString(q, Dpids, 10, DLangs); } *q = 0; - dsyslog("changing pids of channel %d from %d+%d:%s:%d to %d+%d:%s:%d", Number(), vpid, ppid, OldApidsBuf, tpid, Vpid, Ppid, NewApidsBuf, Tpid); + const int SBufferSize = MAXSPIDS * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod', +10: paranoia + char OldSpidsBuf[SBufferSize]; + char NewSpidsBuf[SBufferSize]; + q = OldSpidsBuf; + q += IntArrayToString(q, spids, 10, slangs); + *q = 0; + q = NewSpidsBuf; + q += IntArrayToString(q, Spids, 10, SLangs); + *q = 0; + dsyslog("changing pids of channel %d from %d+%d:%s:%s:%d to %d+%d:%s:%s:%d", Number(), vpid, ppid, OldApidsBuf, OldSpidsBuf, tpid, Vpid, Ppid, NewApidsBuf, NewSpidsBuf, Tpid); vpid = Vpid; ppid = Ppid; for (int i = 0; i < MAXAPIDS; i++) { @@ -481,6 +490,11 @@ void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE strn0cpy(dlangs[i], DLangs[i], MAXLANGCODE2); } dpids[MAXDPIDS] = 0; + for (int i = 0; i < MAXSPIDS; i++) { + spids[i] = Spids[i]; + strn0cpy(slangs[i], SLangs[i], MAXLANGCODE2); + } + spids[MAXSPIDS] = 0; tpid = Tpid; modification |= mod; Channels.SetModified(); diff --git a/channels.h b/channels.h index 6381a201..6eaa66f9 100644 --- a/channels.h +++ b/channels.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.h 1.44 2007/07/21 14:58:36 kls Exp $ + * $Id: channels.h 1.45 2007/09/02 10:23:11 kls Exp $ */ #ifndef __CHANNELS_H @@ -33,7 +33,7 @@ #define MAXAPIDS 32 // audio #define MAXDPIDS 16 // dolby (AC3 + DTS) -#define MAXSPIDS 8 // subtitles +#define MAXSPIDS 32 // subtitles #define MAXCAIDS 8 // conditional access #define MAXLANGCODE1 4 // a 3 letter language code, zero terminated @@ -212,7 +212,7 @@ public: void SetId(int Nid, int Tid, int Sid, int Rid = 0); void SetName(const char *Name, const char *ShortName, const char *Provider); void SetPortalName(const char *PortalName); - void SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int Tpid); + void SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid); void SetCaIds(const int *CaIds); // list must be zero-terminated void SetCaDescriptors(int Level); void SetLinkChannels(cLinkChannels *LinkChannels); diff --git a/config.c b/config.c index aed0936a..1305ffbb 100644 --- a/config.c +++ b/config.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.156 2007/08/12 12:09:37 kls Exp $ + * $Id: config.c 1.157 2007/10/06 14:28:58 kls Exp $ */ #include "config.h" @@ -236,6 +236,11 @@ cSetup::cSetup(void) MarginStart = 2; MarginStop = 10; AudioLanguages[0] = -1; + DisplaySubtitles = 0; + SubtitleLanguages[0] = -1; + SubtitleOffset = 0; + SubtitleFgTransparency = 0; + SubtitleBgTransparency = 0; EPGLanguages[0] = -1; EPGScanTimeout = 5; EPGBugfixLevel = 3; @@ -406,6 +411,11 @@ bool cSetup::Parse(const char *Name, const char *Value) else if (!strcasecmp(Name, "MarginStart")) MarginStart = atoi(Value); else if (!strcasecmp(Name, "MarginStop")) MarginStop = atoi(Value); else if (!strcasecmp(Name, "AudioLanguages")) return ParseLanguages(Value, AudioLanguages); + else if (!strcasecmp(Name, "DisplaySubtitles")) DisplaySubtitles = atoi(Value); + else if (!strcasecmp(Name, "SubtitleLanguages")) return ParseLanguages(Value, SubtitleLanguages); + else if (!strcasecmp(Name, "SubtitleOffset")) SubtitleOffset = atoi(Value); + else if (!strcasecmp(Name, "SubtitleFgTransparency")) SubtitleFgTransparency = atoi(Value); + else if (!strcasecmp(Name, "SubtitleBgTransparency")) SubtitleBgTransparency = atoi(Value); else if (!strcasecmp(Name, "EPGLanguages")) return ParseLanguages(Value, EPGLanguages); else if (!strcasecmp(Name, "EPGScanTimeout")) EPGScanTimeout = atoi(Value); else if (!strcasecmp(Name, "EPGBugfixLevel")) EPGBugfixLevel = atoi(Value); @@ -483,6 +493,11 @@ bool cSetup::Save(void) Store("MarginStart", MarginStart); Store("MarginStop", MarginStop); StoreLanguages("AudioLanguages", AudioLanguages); + Store("DisplaySubtitles", DisplaySubtitles); + StoreLanguages("SubtitleLanguages", SubtitleLanguages); + Store("SubtitleOffset", SubtitleOffset); + Store("SubtitleFgTransparency", SubtitleFgTransparency); + Store("SubtitleBgTransparency", SubtitleBgTransparency); StoreLanguages("EPGLanguages", EPGLanguages); Store("EPGScanTimeout", EPGScanTimeout); Store("EPGBugfixLevel", EPGBugfixLevel); diff --git a/config.h b/config.h index a5efb9b7..e51637f6 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.298 2007/08/19 16:02:50 kls Exp $ + * $Id: config.h 1.299 2007/10/06 14:27:18 kls Exp $ */ #ifndef __CONFIG_H @@ -22,13 +22,13 @@ // VDR's own version number: -#define VDRVERSION "1.5.9" -#define VDRVERSNUM 10509 // Version * 10000 + Major * 100 + Minor +#define VDRVERSION "1.5.10" +#define VDRVERSNUM 10510 // Version * 10000 + Major * 100 + Minor // The plugin API's version number: -#define APIVERSION "1.5.9" -#define APIVERSNUM 10509 // Version * 10000 + Major * 100 + Minor +#define APIVERSION "1.5.10" +#define APIVERSNUM 10510 // Version * 10000 + Major * 100 + Minor // When loading plugins, VDR searches them by their APIVERSION, which // may be smaller than VDRVERSION in case there have been no changes to @@ -220,6 +220,10 @@ public: int TimeTransponder; int MarginStart, MarginStop; int AudioLanguages[I18N_MAX_LANGUAGES + 1]; + int DisplaySubtitles; + int SubtitleLanguages[I18N_MAX_LANGUAGES + 1]; + int SubtitleOffset; + int SubtitleFgTransparency, SubtitleBgTransparency; int EPGLanguages[I18N_MAX_LANGUAGES + 1]; int EPGScanTimeout; int EPGBugfixLevel; diff --git a/device.c b/device.c index e23b74b8..89f7f2fd 100644 --- a/device.c +++ b/device.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.c 1.142 2007/08/26 11:11:42 kls Exp $ + * $Id: device.c 1.143 2007/10/12 14:27:30 kls Exp $ */ #include "device.h" @@ -19,6 +19,75 @@ #include "status.h" #include "transfer.h" +// --- cLiveSubtitle --------------------------------------------------------- + +#define LIVESUBTITLEBUFSIZE KILOBYTE(100) + +class cLiveSubtitle : public cReceiver, public cThread { +private: + cRingBufferLinear *ringBuffer; + cRemux *remux; +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + virtual void Action(void); +public: + cLiveSubtitle(int SPid); + virtual ~cLiveSubtitle(); + }; + +cLiveSubtitle::cLiveSubtitle(int SPid) +:cReceiver(tChannelID(), -1, SPid) +,cThread("live subtitle") +{ + ringBuffer = new cRingBufferLinear(LIVESUBTITLEBUFSIZE, TS_SIZE * 2, true, "Live Subtitle"); + int NoPids = 0; + int SPids[] = { SPid, 0 }; + remux = new cRemux(0, &NoPids, &NoPids, SPids); +} + +cLiveSubtitle::~cLiveSubtitle() +{ + cReceiver::Detach(); + delete remux; + delete ringBuffer; +} + +void cLiveSubtitle::Activate(bool On) +{ + if (On) + Start(); + else + Cancel(3); +} + +void cLiveSubtitle::Receive(uchar *Data, int Length) +{ + if (Running()) { + int p = ringBuffer->Put(Data, Length); + if (p != Length && Running()) + ringBuffer->ReportOverflow(Length - p); + } +} + +void cLiveSubtitle::Action(void) +{ + while (Running()) { + int Count; + uchar *b = ringBuffer->Get(Count); + if (b) { + Count = remux->Put(b, Count); + if (Count) + ringBuffer->Del(Count); + } + b = remux->Get(Count); + if (b) { + Count = cDevice::PrimaryDevice()->PlaySubtitle(b, Count); + remux->Del(Count); + } + } +} + // --- cPesAssembler --------------------------------------------------------- class cPesAssembler { @@ -172,6 +241,10 @@ cDevice::cDevice(void) ClrAvailableTracks(); currentAudioTrack = ttNone; currentAudioTrackMissingCount = 0; + currentSubtitleTrack = ttNone; + liveSubtitle = NULL; + dvbSubtitleConverter = NULL; + autoSelectPreferredSubtitleLanguage = true; for (int i = 0; i < MAXRECEIVERS; i++) receiver[i] = NULL; @@ -186,6 +259,8 @@ cDevice::~cDevice() { Detach(player); DetachAllReceivers(); + delete liveSubtitle; + delete dvbSubtitleConverter; delete nitFilter; delete sdtFilter; delete patFilter; @@ -674,8 +749,11 @@ bool cDevice::SwitchChannel(int Direction) eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) { - if (LiveView) + if (LiveView) { StopReplay(); + DELETENULL(liveSubtitle); + DELETENULL(dvbSubtitleConverter); + } cDevice *Device = (LiveView && IsPrimaryDevice()) ? GetDevice(Channel, 0, LiveView) : this; @@ -735,8 +813,11 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) for (int i = 0; i < MAXDPIDS; i++) SetAvailableTrack(ttDolby, i, Channel->Dpid(i), Channel->Dlang(i)); } + for (int i = 0; i < MAXSPIDS; i++) + SetAvailableTrack(ttSubtitle, i, Channel->Spid(i), Channel->Slang(i)); if (!NeedsTransferMode) EnsureAudioTrack(true); + EnsureSubtitleTrack(); } cStatus::MsgChannelSwitch(this, Channel->Number()); // only report status if channel switch successfull } @@ -848,6 +929,7 @@ void cDevice::ClrAvailableTracks(bool DescriptionsOnly, bool IdsOnly) SetAudioChannel(0); // fall back to stereo currentAudioTrackMissingCount = 0; currentAudioTrack = ttNone; + currentSubtitleTrack = ttNone; } } @@ -855,18 +937,23 @@ bool cDevice::SetAvailableTrack(eTrackType Type, int Index, uint16_t Id, const c { eTrackType t = eTrackType(Type + Index); if (Type == ttAudio && IS_AUDIO_TRACK(t) || - Type == ttDolby && IS_DOLBY_TRACK(t)) { + Type == ttDolby && IS_DOLBY_TRACK(t) || + Type == ttSubtitle && IS_SUBTITLE_TRACK(t)) { if (Language) strn0cpy(availableTracks[t].language, Language, sizeof(availableTracks[t].language)); if (Description) Utf8Strn0Cpy(availableTracks[t].description, Description, sizeof(availableTracks[t].description)); if (Id) { availableTracks[t].id = Id; // setting 'id' last to avoid the need for extensive locking - int numAudioTracks = NumAudioTracks(); - if (!availableTracks[currentAudioTrack].id && numAudioTracks && currentAudioTrackMissingCount++ > numAudioTracks * 10) - EnsureAudioTrack(); - else if (t == currentAudioTrack) - currentAudioTrackMissingCount = 0; + if (Type == ttAudio || Type == ttDolby) { + int numAudioTracks = NumAudioTracks(); + if (!availableTracks[currentAudioTrack].id && numAudioTracks && currentAudioTrackMissingCount++ > numAudioTracks * 10) + EnsureAudioTrack(); + else if (t == currentAudioTrack) + currentAudioTrackMissingCount = 0; + } + else if (Type == ttSubtitle && autoSelectPreferredSubtitleLanguage) + EnsureSubtitleTrack(); } return true; } @@ -880,16 +967,26 @@ const tTrackId *cDevice::GetTrack(eTrackType Type) return (ttNone < Type && Type < ttMaxTrackTypes) ? &availableTracks[Type] : NULL; } -int cDevice::NumAudioTracks(void) const +int cDevice::NumTracks(eTrackType FirstTrack, eTrackType LastTrack) const { int n = 0; - for (int i = ttAudioFirst; i <= ttDolbyLast; i++) { + for (int i = FirstTrack; i <= LastTrack; i++) { if (availableTracks[i].id) n++; } return n; } +int cDevice::NumAudioTracks(void) const +{ + return NumTracks(ttAudioFirst, ttDolbyLast); +} + +int cDevice::NumSubtitleTracks(void) const +{ + return NumTracks(ttSubtitleFirst, ttSubtitleLast); +} + bool cDevice::SetCurrentAudioTrack(eTrackType Type) { if (ttNone < Type && Type <= ttDolbyLast) { @@ -908,6 +1005,30 @@ bool cDevice::SetCurrentAudioTrack(eTrackType Type) return false; } +bool cDevice::SetCurrentSubtitleTrack(eTrackType Type, bool Manual) +{ + if (Type == ttNone || IS_SUBTITLE_TRACK(Type)) { + currentSubtitleTrack = Type; + autoSelectPreferredSubtitleLanguage = !Manual; + if (dvbSubtitleConverter) + dvbSubtitleConverter->Reset(); + if (Type == ttNone && dvbSubtitleConverter) { + cMutexLock MutexLock(&mutexCurrentSubtitleTrack); + DELETENULL(dvbSubtitleConverter); + } + DELETENULL(liveSubtitle); + if (currentSubtitleTrack != ttNone && !Replaying() && !Transferring()) { + const tTrackId *TrackId = GetTrack(currentSubtitleTrack); + if (TrackId && TrackId->id) { + liveSubtitle = new cLiveSubtitle(TrackId->id); + AttachReceiver(liveSubtitle); + } + } + return true; + } + return false; +} + void cDevice::EnsureAudioTrack(bool Force) { if (Force || !availableTracks[currentAudioTrack].id) { @@ -939,6 +1060,25 @@ void cDevice::EnsureAudioTrack(bool Force) } } +void cDevice::EnsureSubtitleTrack(void) +{ + if (Setup.DisplaySubtitles) { + eTrackType PreferredTrack = ttSubtitleFirst; + int LanguagePreference = -1; + for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) { + const tTrackId *TrackId = GetTrack(eTrackType(i)); + if (TrackId && TrackId->id && I18nIsPreferredLanguage(Setup.SubtitleLanguages, TrackId->language, LanguagePreference)) + PreferredTrack = eTrackType(i); + } + // Make sure we're set to an available subtitle track: + const tTrackId *Track = GetTrack(GetCurrentSubtitleTrack()); + if (!Track || !Track->id || PreferredTrack != GetCurrentSubtitleTrack()) + SetCurrentSubtitleTrack(PreferredTrack); + } + else + SetCurrentSubtitleTrack(ttNone); +} + bool cDevice::CanReplay(void) const { return HasDecoder(); @@ -961,6 +1101,8 @@ void cDevice::TrickSpeed(int Speed) void cDevice::Clear(void) { Audios.ClearAudio(); + if (dvbSubtitleConverter) + dvbSubtitleConverter->Reset(); } void cDevice::Play(void) @@ -1016,6 +1158,9 @@ void cDevice::Detach(cPlayer *Player) player = NULL; // avoids recursive calls to Detach() p->Activate(false); p->device = NULL; + cMutexLock MutexLock(&mutexCurrentSubtitleTrack); + delete dvbSubtitleConverter; + dvbSubtitleConverter = NULL; SetPlayMode(pmNone); SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat)); Audios.ClearAudio(); @@ -1051,6 +1196,13 @@ int cDevice::PlayAudio(const uchar *Data, int Length, uchar Id) return -1; } +int cDevice::PlaySubtitle(const uchar *Data, int Length) +{ + if (!dvbSubtitleConverter) + dvbSubtitleConverter = new cDvbSubtitleConverter; + return dvbSubtitleConverter->Convert(Data, Length); +} + int cDevice::PlayPesPacket(const uchar *Data, int Length, bool VideoOnly) { cMutexLock MutexLock(&mutexCurrentAudioTrack); @@ -1076,6 +1228,11 @@ int cDevice::PlayPesPacket(const uchar *Data, int Length, bool VideoOnly) break; case 0xBD: { // private stream 1 int PayloadOffset = Data[8] + 9; + + // Compatibility mode for old subtitles plugin: + if ((Data[PayloadOffset - 3] & 0x81) == 1 && Data[PayloadOffset - 2] == 0x81) + PayloadOffset--; + uchar SubStreamId = Data[PayloadOffset]; uchar SubStreamType = SubStreamId & 0xF0; uchar SubStreamIndex = SubStreamId & 0x1F; @@ -1090,6 +1247,9 @@ pre_1_3_19_PrivateStreamDeteced: switch (SubStreamType) { case 0x20: // SPU case 0x30: // SPU + SetAvailableTrack(ttSubtitle, SubStreamIndex, SubStreamId); + if (!VideoOnly && currentSubtitleTrack != ttNone && SubStreamId == availableTracks[currentSubtitleTrack].id) + w = PlaySubtitle(Start, d); break; case 0x80: // AC3 & DTS if (Setup.UseDolbyDigital) { @@ -1139,6 +1299,8 @@ int cDevice::PlayPes(const uchar *Data, int Length, bool VideoOnly) { if (!Data) { pesAssembler->Reset(); + if (dvbSubtitleConverter) + dvbSubtitleConverter->Reset(); return 0; } int Result = 0; diff --git a/device.h b/device.h index e74c8841..ceccaddd 100644 --- a/device.h +++ b/device.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.h 1.82 2007/07/22 11:20:13 kls Exp $ + * $Id: device.h 1.83 2007/10/12 13:53:50 kls Exp $ */ #ifndef __DEVICE_H @@ -12,6 +12,7 @@ #include "channels.h" #include "ci.h" +#include "dvbsubtitle.h" #include "eit.h" #include "filter.h" #include "nit.h" @@ -70,16 +71,15 @@ enum eTrackType { ttNone, ttDolby, ttDolbyFirst = ttDolby, ttDolbyLast = ttDolbyFirst + 15, // MAXDPIDS - 1 - /* future... ttSubtitle, ttSubtitleFirst = ttSubtitle, - ttSubtitleLast = ttSubtitleFirst + 7, // MAXSPIDS - 1 - */ + ttSubtitleLast = ttSubtitleFirst + 31, // MAXSPIDS - 1 ttMaxTrackTypes }; #define IS_AUDIO_TRACK(t) (ttAudioFirst <= (t) && (t) <= ttAudioLast) #define IS_DOLBY_TRACK(t) (ttDolbyFirst <= (t) && (t) <= ttDolbyLast) +#define IS_SUBTITLE_TRACK(t) (ttSubtitleFirst <= (t) && (t) <= ttSubtitleLast) struct tTrackId { uint16_t id; // The PES packet id or the PID. @@ -90,10 +90,12 @@ struct tTrackId { class cPlayer; class cReceiver; class cPesAssembler; +class cLiveSubtitle; /// The cDevice class is the base from which actual devices can be derived. class cDevice : public cThread { + friend class cLiveSubtitle; private: static int numDevices; static int useDevice; @@ -185,6 +187,9 @@ public: // SPU facilities +private: + cLiveSubtitle *liveSubtitle; + cDvbSubtitleConverter *dvbSubtitleConverter; public: virtual cSpuDecoder *GetSpuDecoder(void); ///< Returns a pointer to the device's SPU decoder (or NULL, if this @@ -362,8 +367,11 @@ public: private: tTrackId availableTracks[ttMaxTrackTypes]; eTrackType currentAudioTrack; + eTrackType currentSubtitleTrack; cMutex mutexCurrentAudioTrack; + cMutex mutexCurrentSubtitleTrack; int currentAudioTrackMissingCount; + bool autoSelectPreferredSubtitleLanguage; bool pre_1_3_19_PrivateStream; protected: virtual void SetAudioTrackDevice(eTrackType Type); @@ -384,18 +392,33 @@ public: const tTrackId *GetTrack(eTrackType Type); ///< Returns a pointer to the given track id, or NULL if Type is not ///< less than ttMaxTrackTypes. + int NumTracks(eTrackType FirstTrack, eTrackType LastTrack) const; + ///< Returns the number of tracks in the given range that are currently + ///< available. int NumAudioTracks(void) const; ///< Returns the number of audio tracks that are currently available. ///< This is just for information, to quickly find out whether there ///< is more than one audio track. + int NumSubtitleTracks(void) const; + ///< Returns the number of subtitle tracks that are currently available. eTrackType GetCurrentAudioTrack(void) { return currentAudioTrack; } bool SetCurrentAudioTrack(eTrackType Type); ///< Sets the current audio track to the given Type. ///< \return Returns true if Type is a valid audio track, false otherwise. + eTrackType GetCurrentSubtitleTrack(void) { return currentSubtitleTrack; } + bool SetCurrentSubtitleTrack(eTrackType Type, bool Manual = false); + ///< Sets the current subtitle track to the given Type. + ///< IF Manual is true, no automatic preferred subtitle language selection + ///< will be done for the rest of the current replay session, or until + ///< the channel is changed. + ///< \return Returns true if Type is a valid subtitle track, false otherwise. void EnsureAudioTrack(bool Force = false); ///< Makes sure an audio track is selected that is actually available. ///< If Force is true, the language and Dolby Digital settings will ///< be verified even if the current audio track is available. + void EnsureSubtitleTrack(void); + ///< Makes sure one of the preferred language subtitle tracks is selected. + ///< Only has an effect if Setup.DisplaySubtitles is on. // Audio facilities @@ -454,6 +477,13 @@ protected: ///< Length) or not at all (returning 0 or -1 and setting 'errno' to EAGAIN). ///< \return Returns the number of bytes actually taken from Data, or -1 ///< in case of an error. + virtual int PlaySubtitle(const uchar *Data, int Length); + ///< Plays the given data block as a subtitle. + ///< Data points to exactly one complete PES packet of the given Length. + ///< PlaySubtitle() shall process the packet either as a whole (returning + ///< Length) or not at all (returning 0 or -1 and setting 'errno' to EAGAIN). + ///< \return Returns the number of bytes actually taken from Data, or -1 + ///< in case of an error. virtual int PlayPesPacket(const uchar *Data, int Length, bool VideoOnly = false); ///< Plays the single PES packet in Data with the given Length. ///< If VideoOnly is true, only the video will be displayed, diff --git a/dvbosd.c b/dvbosd.c index 87c8dec2..e076a3c9 100644 --- a/dvbosd.c +++ b/dvbosd.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbosd.c 1.31 2007/08/26 09:39:20 kls Exp $ + * $Id: dvbosd.c 1.32 2007/09/16 08:55:54 kls Exp $ */ #include "dvbosd.h" @@ -32,6 +32,7 @@ public: cDvbOsd(int Left, int Top, int OsdDev, uint Level); virtual ~cDvbOsd(); virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas); + virtual eOsdError SetAreas(const tArea *Areas, int NumAreas); virtual void Flush(void); }; @@ -106,6 +107,19 @@ eOsdError cDvbOsd::CanHandleAreas(const tArea *Areas, int NumAreas) return Result; } +eOsdError cDvbOsd::SetAreas(const tArea *Areas, int NumAreas) +{ + if (shown) { + cBitmap *Bitmap; + for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) { + Cmd(OSD_SetWindow, 0, i + 1); + Cmd(OSD_Close); + } + shown = false; + } + return cOsd::SetAreas(Areas, NumAreas); +} + void cDvbOsd::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, const void *data) { if (osdDev >= 0) { diff --git a/dvbsubtitle.c b/dvbsubtitle.c new file mode 100644 index 00000000..905d761c --- /dev/null +++ b/dvbsubtitle.c @@ -0,0 +1,1029 @@ +/* + * dvbsubtitle.c: DVB subtitles + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * Original author: Marco Schlüßler + * With some input from the "subtitle plugin" by Pekka Virtanen + * + * $Id: dvbsubtitle.c 1.1 2007/10/12 14:27:30 kls Exp $ + */ + +#include "dvbsubtitle.h" +#include "device.h" + +#define PAGE_COMPOSITION_SEGMENT 0x10 +#define REGION_COMPOSITION_SEGMENT 0x11 +#define CLUT_DEFINITION_SEGMENT 0x12 +#define OBJECT_DATA_SEGMENT 0x13 +#define END_OF_DISPLAY_SET_SEGMENT 0x80 + +// Set these to 'true' for debug output: +static bool DebugConverter = false; +static bool DebugSegments = false; +static bool DebugPages = false; +static bool DebugRegions = false; +static bool DebugObjects = false; +static bool DebugCluts = false; + +#define dbgconverter(a...) if (DebugConverter) fprintf(stderr, a) +#define dbgsegments(a...) if (DebugSegments) fprintf(stderr, a) +#define dbgpages(a...) if (DebugPages) fprintf(stderr, a) +#define dbgregions(a...) if (DebugRegions) fprintf(stderr, a) +#define dbgobjects(a...) if (DebugObjects) fprintf(stderr, a) +#define dbgcluts(a...) if (DebugCluts) fprintf(stderr, a) + +// --- cSubtitleClut --------------------------------------------------------- + +class cSubtitleClut : public cListObject { +private: + int clutId; + int version; + cPalette palette2; + cPalette palette4; + cPalette palette8; +public: + cSubtitleClut(int ClutId); + int ClutId(void) { return clutId; } + int Version(void) { return version; } + void SetVersion(int Version) { version = Version; } + void SetColor(int Bpp, int Index, tColor Color); + const cPalette *GetPalette(int Bpp); + }; + +cSubtitleClut::cSubtitleClut(int ClutId) +:palette2(2) +,palette4(4) +,palette8(8) +{ + clutId = ClutId; + version = -1; +} + +void cSubtitleClut::SetColor(int Bpp, int Index, tColor Color) +{ + switch (Bpp) { + case 2: palette2.SetColor(Index, Color); break; + case 4: palette4.SetColor(Index, Color); break; + case 8: palette8.SetColor(Index, Color); break; + default: esyslog("ERROR: wrong Bpp in cSubtitleClut::SetColor(%d, %d, %08X)", Bpp, Index, Color); + } +} + +const cPalette *cSubtitleClut::GetPalette(int Bpp) +{ + switch (Bpp) { + case 2: return &palette2; + case 4: return &palette4; + case 8: return &palette8; + default: esyslog("ERROR: wrong Bpp in cSubtitleClut::GetPalette(%d)", Bpp); + } + return &palette8; +} + +// --- cSubtitleObject ------------------------------------------------------- + +class cSubtitleObject : public cListObject { +private: + int objectId; + int version; + int codingMethod; + bool nonModifyingColorFlag; + int nibblePos; + uchar backgroundColor; + uchar foregroundColor; + int providerFlag; + int px; + int py; + cBitmap *bitmap; + void DrawLine(int x, int y, tIndex Index, int Length); + uchar Get2Bits(const uchar *Data, int &Index); + uchar Get4Bits(const uchar *Data, int &Index); + bool Decode2BppCodeString(const uchar *Data, int &Index, int&x, int y); + bool Decode4BppCodeString(const uchar *Data, int &Index, int&x, int y); + bool Decode8BppCodeString(const uchar *Data, int &Index, int&y, int y); +public: + cSubtitleObject(int ObjectId, cBitmap *Bitmap); + int ObjectId(void) { return objectId; } + int Version(void) { return version; } + int CodingMethod(void) { return codingMethod; } + bool NonModifyingColorFlag(void) { return nonModifyingColorFlag; } + void DecodeSubBlock(const uchar *Data, int Length, bool Even); + void SetVersion(int Version) { version = Version; } + void SetBackgroundColor(uchar BackgroundColor) { backgroundColor = BackgroundColor; } + void SetForegroundColor(uchar ForegroundColor) { foregroundColor = ForegroundColor; } + void SetNonModifyingColorFlag(bool NonModifyingColorFlag) { nonModifyingColorFlag = NonModifyingColorFlag; } + void SetCodingMethod(int CodingMethod) { codingMethod = CodingMethod; } + void SetPosition(int x, int y) { px = x; py = y; } + void SetProviderFlag(int ProviderFlag) { providerFlag = ProviderFlag; } + }; + +cSubtitleObject::cSubtitleObject(int ObjectId, cBitmap *Bitmap) +{ + objectId = ObjectId; + version = -1; + codingMethod = -1; + nonModifyingColorFlag = false; + nibblePos = 0; + backgroundColor = 0; + foregroundColor = 0; + providerFlag = -1; + px = py = 0; + bitmap = Bitmap; +} + +void cSubtitleObject::DecodeSubBlock(const uchar *Data, int Length, bool Even) +{ + int x = 0; + int y = Even ? 0 : 1; + for (int index = 0; index < Length; ) { + switch (Data[index++]) { + case 0x10: { + nibblePos = 8; + while (Decode2BppCodeString(Data, index, x, y) && index < Length) + ; + if (!nibblePos) + index++; + break; + } + case 0x11: { + nibblePos = 4; + while (Decode4BppCodeString(Data, index, x, y) && index < Length) + ; + if (!nibblePos) + index++; + break; + } + case 0x12: + while (Decode8BppCodeString(Data, index, x, y) && index < Length) + ; + break; + case 0x20: //TODO + dbgobjects("sub block 2 to 4 map"); + index += 4; + break; + case 0x21: //TODO + dbgobjects("sub block 2 to 8 map"); + index += 4; + break; + case 0x22: //TODO + dbgobjects("sub block 4 to 8 map"); + index += 16; + break; + case 0xF0: + x = 0; + y += 2; + break; + } + } +} + +void cSubtitleObject::DrawLine(int x, int y, tIndex Index, int Length) +{ + if (nonModifyingColorFlag && Index == 1) + return; + x += px; + y += py; + for (int pos = x; pos < x + Length; pos++) + bitmap->SetIndex(pos, y, Index); +} + +uchar cSubtitleObject::Get2Bits(const uchar *Data, int &Index) +{ + uchar result = Data[Index]; + if (!nibblePos) { + Index++; + nibblePos = 8; + } + nibblePos -= 2; + return (result >> nibblePos) & 0x03; +} + +uchar cSubtitleObject::Get4Bits(const uchar *Data, int &Index) +{ + uchar result = Data[Index]; + if (!nibblePos) { + Index++; + nibblePos = 4; + } + else { + result >>= 4; + nibblePos -= 4; + } + return result & 0x0F; +} + +bool cSubtitleObject::Decode2BppCodeString(const uchar *Data, int &Index, int &x, int y) +{ + int rl = 0; + int color = 0; + uchar code = Get2Bits(Data, Index); + if (code) { + color = code; + rl = 1; + } + else { + code = Get2Bits(Data, Index); + if (code & 2) { // switch_1 + rl = ((code & 1) << 2) + Get2Bits(Data, Index) + 3; + color = Get2Bits(Data, Index); + } + else if (code & 1) + rl = 1; //color 0 + else { + code = Get2Bits(Data, Index); + switch (code & 0x3) { //switch_3 + case 0: + return false; + case 1: + rl = 2; //color 0 + break; + case 2: + rl = (Get2Bits(Data, Index) << 2) + Get2Bits(Data, Index) + 12; + color = Get2Bits(Data, Index); + break; + case 3: + rl = (Get2Bits(Data, Index) << 6) + (Get2Bits(Data, Index) << 4) + (Get2Bits(Data, Index) << 2) + Get2Bits(Data, Index) + 29; + color = Get2Bits(Data, Index); + break; + } + } + } + DrawLine(x, y, color, rl); + x += rl; + return true; +} + +bool cSubtitleObject::Decode4BppCodeString(const uchar *Data, int &Index, int &x, int y) +{ + int rl = 0; + int color = 0; + uchar code = Get4Bits(Data, Index); + if (code) { + color = code; + rl = 1; + } + else { + code = Get4Bits(Data, Index); + if (code & 8) { // switch_1 + if (code & 4) { //switch_2 + switch (code & 3) { //switch_3 + case 0: // color 0 + rl = 1; + break; + case 1: // color 0 + rl = 2; + break; + case 2: + rl = Get4Bits(Data, Index) + 9; + color = Get4Bits(Data, Index); + break; + case 3: + rl = (Get4Bits(Data, Index) << 4) + Get4Bits(Data, Index) + 25; + color = Get4Bits(Data, Index); + break; + } + } + else { + rl = (code & 3) + 4; + color = Get4Bits(Data, Index); + } + } + else { // color 0 + if (!code) + return false; + rl = code + 2; + } + } + DrawLine(x, y, color, rl); + x += rl; + return true; +} + +bool cSubtitleObject::Decode8BppCodeString(const uchar *Data, int &Index, int &x, int y) +{ + int rl = 0; + int color = 0; + uchar code = Data[Index++]; + if (code) { + color = code; + rl = 1; + } + else { + code = Data[Index++]; + rl = code & 0x63; + if (code & 0x80) + color = Data[Index++]; + else if (!rl) + return false; //else color 0 + } + DrawLine(x, y, color, rl); + x += rl; + return true; +} + +// --- cSubtitleRegion ------------------------------------------------------- + +class cSubtitleRegion : public cListObject, public cBitmap { +private: + int regionId; + int version; + int clutId; + int horizontalAddress; + int verticalAddress; + int level; + cList objects; +public: + cSubtitleRegion(int RegionId); + int RegionId(void) { return regionId; } + int Version(void) { return version; } + int ClutId(void) { return clutId; } + int Level(void) { return level; } + int Depth(void) { return Bpp(); } + void FillRegion(tIndex Index); + cSubtitleObject *GetObjectById(int ObjectId, bool New = false); + int HorizontalAddress(void) { return horizontalAddress; } + int VerticalAddress(void) { return verticalAddress; } + void SetVersion(int Version) { version = Version; } + void SetClutId(int ClutId) { clutId = ClutId; } + void SetLevel(int Level); + void SetDepth(int Depth); + void SetHorizontalAddress(int HorizontalAddress) { horizontalAddress = HorizontalAddress; } + void SetVerticalAddress(int VerticalAddress) { verticalAddress = VerticalAddress; } + }; + +cSubtitleRegion::cSubtitleRegion(int RegionId) +:cBitmap(1, 1, 4) +{ + regionId = RegionId; + version = -1; + clutId = -1; + horizontalAddress = 0; + verticalAddress = 0; + level = 0; +} + +void cSubtitleRegion::FillRegion(tIndex Index) +{ + dbgregions("FillRegion %d\n", Index); + for (int y = 0; y < Height(); y++) { + for (int x = 0; x < Width(); x++) + SetIndex(x, y, Index); + } +} + +cSubtitleObject *cSubtitleRegion::GetObjectById(int ObjectId, bool New) +{ + cSubtitleObject *result = NULL; + for (cSubtitleObject *so = objects.First(); so; so = objects.Next(so)) { + if (so->ObjectId() == ObjectId) + result = so; + } + if (!result && New) { + result = new cSubtitleObject(ObjectId, this); + objects.Add(result); + } + return result; +} + +void cSubtitleRegion::SetLevel(int Level) +{ + if (Level > 0 && Level < 4) + level = 1 << Level; +} + +void cSubtitleRegion::SetDepth(int Depth) +{ + if (Depth > 0 && Depth < 4) + SetBpp(1 << Depth); +} + +// --- cDvbSubtitlePage ------------------------------------------------------ + +class cDvbSubtitlePage : public cListObject { +private: + int pageId; + int version; + int state; + int64_t pts; + int timeout; + cList cluts; +public: + cList regions; + cDvbSubtitlePage(int PageId); + virtual ~cDvbSubtitlePage(); + int PageId(void) { return pageId; } + int Version(void) { return version; } + int State(void) { return state; } + tArea *GetAreas(void); + cSubtitleClut *GetClutById(int ClutId, bool New = false); + cSubtitleObject *GetObjectById(int ObjectId); + cSubtitleRegion *GetRegionById(int RegionId, bool New = false); + int64_t Pts(void) const { return pts; } + int Timeout(void) { return timeout; } + void SetVersion(int Version) { version = Version; } + void SetPts(int64_t Pts) { pts = Pts; } + void SetState(int State); + void SetTimeout(int Timeout) { timeout = Timeout; } + void UpdateRegionPalette(cSubtitleClut *Clut); + }; + +cDvbSubtitlePage::cDvbSubtitlePage(int PageId) +{ + pageId = PageId; + version = -1; + state = -1; + pts = 0; + timeout = 0; +} + +cDvbSubtitlePage::~cDvbSubtitlePage() +{ +} + +tArea *cDvbSubtitlePage::GetAreas(void) +{ + if (regions.Count() > 0) { + tArea *Areas = new tArea[regions.Count()]; + tArea *a = Areas; + for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) { + a->x1 = sr->HorizontalAddress(); + a->y1 = sr->VerticalAddress(); + a->x2 = sr->HorizontalAddress() + sr->Width() - 1; + a->y2 = sr->VerticalAddress() + sr->Height() - 1; + a->bpp = sr->Bpp(); + while ((a->Width() & 3) != 0) + a->x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work + a++; + } + return Areas; + } + return NULL; +} + +cSubtitleClut *cDvbSubtitlePage::GetClutById(int ClutId, bool New) +{ + cSubtitleClut *result = NULL; + for (cSubtitleClut *sc = cluts.First(); sc; sc = cluts.Next(sc)) { + if (sc->ClutId() == ClutId) + result = sc; + } + if (!result && New) { + result = new cSubtitleClut(ClutId); + cluts.Add(result); + } + return result; +} + +cSubtitleRegion *cDvbSubtitlePage::GetRegionById(int RegionId, bool New) +{ + cSubtitleRegion *result = NULL; + for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) { + if (sr->RegionId() == RegionId) + result = sr; + } + if (!result && New) { + result = new cSubtitleRegion(RegionId); + regions.Add(result); + } + return result; +} + +cSubtitleObject *cDvbSubtitlePage::GetObjectById(int ObjectId) +{ + cSubtitleObject *result = NULL; + for (cSubtitleRegion *sr = regions.First(); sr && !result; sr = regions.Next(sr)) + result = sr->GetObjectById(ObjectId); + return result; +} + +void cDvbSubtitlePage::SetState(int State) +{ + state = State; + switch (state) { + case 0: // normal case - page update + dbgpages("page update\n"); + break; + case 1: // aquisition point - page refresh + dbgpages("page refresh\n"); + regions.Clear(); + break; + case 2: // mode change - new page + dbgpages("new Page\n"); + regions.Clear(); + cluts.Clear(); + break; + case 3: // reserved + break; + } +} + +void cDvbSubtitlePage::UpdateRegionPalette(cSubtitleClut *Clut) +{ + for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) { + if (sr->ClutId() == Clut->ClutId()) + sr->Replace(*Clut->GetPalette(sr->Bpp())); + } +} + +// --- cDvbSubtitleAssembler ------------------------------------------------- + +class cDvbSubtitleAssembler { +private: + uchar *data; + int length; + int pos; + int size; + bool Realloc(int Size); +public: + cDvbSubtitleAssembler(void); + virtual ~cDvbSubtitleAssembler(); + void Reset(void); + unsigned char *Get(int &Length); + void Put(const uchar *Data, int Length); + }; + +cDvbSubtitleAssembler::cDvbSubtitleAssembler(void) +{ + data = NULL; + size = 0; + Reset(); +} + +cDvbSubtitleAssembler::~cDvbSubtitleAssembler() +{ + free(data); +} + +void cDvbSubtitleAssembler::Reset(void) +{ + length = 0; + pos = 0; +} + +bool cDvbSubtitleAssembler::Realloc(int Size) +{ + if (Size > size) { + size = max(Size, 2048); + data = (uchar *)realloc(data, size); + if (!data) { + esyslog("ERROR: can't allocate memory for subtitle assembler"); + length = 0; + size = 0; + return false; + } + } + return true; +} + +unsigned char *cDvbSubtitleAssembler::Get(int &Length) +{ + if (length > pos + 5) { + Length = (data[pos + 4] << 8) + data[pos + 5] + 6; + if (length >= pos + Length) { + unsigned char *result = data + pos; + pos += Length; + return result; + } + } + return NULL; +} + +void cDvbSubtitleAssembler::Put(const uchar *Data, int Length) +{ + if (Length && Realloc(length + Length)) { + memcpy(data + length, Data, Length); + length += Length; + } +} + +// --- cDvbSubtitleBitmaps --------------------------------------------------- + +class cDvbSubtitleBitmaps : public cListObject { +private: + int64_t pts; + int timeout; + tArea *areas; + int numAreas; + cVector bitmaps; +public: + cDvbSubtitleBitmaps(int64_t Pts, int Timeout, tArea *Areas, int NumAreas); + ~cDvbSubtitleBitmaps(); + int64_t Pts(void) { return pts; } + int Timeout(void) { return timeout; } + void AddBitmap(cBitmap *Bitmap); + void Draw(cOsd *Osd); + }; + +cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int64_t Pts, int Timeout, tArea *Areas, int NumAreas) +{ + pts = Pts; + timeout = Timeout; + areas = Areas; + numAreas = NumAreas; +} + +cDvbSubtitleBitmaps::~cDvbSubtitleBitmaps() +{ + delete[] areas; + for (int i = 0; i < bitmaps.Size(); i++) + delete bitmaps[i]; +} + +void cDvbSubtitleBitmaps::AddBitmap(cBitmap *Bitmap) +{ + bitmaps.Append(Bitmap); +} + +void cDvbSubtitleBitmaps::Draw(cOsd *Osd) +{ + if (Osd->SetAreas(areas, numAreas) == oeOk) { + for (int i = 0; i < bitmaps.Size(); i++) + Osd->DrawBitmap(bitmaps[i]->X0(), bitmaps[i]->Y0(), *bitmaps[i]); + Osd->Flush(); + } +} + +// --- cDvbSubtitleConverter ------------------------------------------------- + +int cDvbSubtitleConverter::setupLevel = 0; + +cDvbSubtitleConverter::cDvbSubtitleConverter(void) +:cThread("subtitleConverter") +{ + dvbSubtitleAssembler = new cDvbSubtitleAssembler; + osd = NULL; + pages = new cList; + bitmaps = new cList; + Start(); +} + +cDvbSubtitleConverter::~cDvbSubtitleConverter() +{ + Cancel(3); + delete dvbSubtitleAssembler; + delete osd; + delete bitmaps; + delete pages; +} + +void cDvbSubtitleConverter::SetupChanged(void) +{ + setupLevel++; +} + +void cDvbSubtitleConverter::Reset(void) +{ + dbgconverter("Converter reset -----------------------\n"); + dvbSubtitleAssembler->Reset(); + Lock(); + pages->Clear(); + bitmaps->Clear(); + DELETENULL(osd); + Unlock(); +} + +int cDvbSubtitleConverter::Convert(const uchar *Data, int Length) +{ + if (Data && Length > 8) { + int PayloadOffset = Data[8] + 9; + int SubstreamHeaderLength = 4; + bool ResetSubtitleAssembler = Data[PayloadOffset + 3] == 0x00; + + // Compatibility mode for old subtitles plugin: + if ((Data[PayloadOffset - 3] & 0x81) == 1 && Data[PayloadOffset - 2] == 0x81) { + PayloadOffset--; + SubstreamHeaderLength = 1; + ResetSubtitleAssembler = Data[8] >= 5; + } + + if (Length > PayloadOffset + SubstreamHeaderLength) { + int64_t pts = 0; + if ((Data[7] & 0x80) && Data[8] >= 5) { + pts = (((int64_t)Data[ 9]) & 0x0E) << 29; + pts |= ( (int64_t)Data[10]) << 22; + pts |= (((int64_t)Data[11]) & 0xFE) << 14; + pts |= ( (int64_t)Data[12]) << 7; + pts |= (((int64_t)Data[13]) & 0xFE) >> 1; + dbgconverter("Converter PTS: %lld\n", pts); + } + const uchar *data = Data + PayloadOffset + SubstreamHeaderLength; // skip substream header + int length = Length - PayloadOffset - SubstreamHeaderLength; // skip substream header + if (ResetSubtitleAssembler) + dvbSubtitleAssembler->Reset(); + + if (length > 3) { + if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) + dvbSubtitleAssembler->Put(data + 2, length - 2); + else + dvbSubtitleAssembler->Put(data, length); + + int Count; + while (true) { + unsigned char *b = dvbSubtitleAssembler->Get(Count); + if (b && b[0] == 0x0F) { + if (ExtractSegment(b, Count, pts) == -1) + break; + } + else + break; + } + } + return Length; + } + } + return 0; +} + +#define LimitTo32Bit(n) (n & 0x00000000FFFFFFFFL) +#define MAXDELTA 40000 // max. reasonable PTS/STC delta in ms + +void cDvbSubtitleConverter::Action(void) +{ + int LastSetupLevel = setupLevel; + cTimeMs Timeout; + while (Running()) { + if (osd) { + int NewSetupLevel = setupLevel; + if (Timeout.TimedOut() || LastSetupLevel != NewSetupLevel) { + DELETENULL(osd); + } + LastSetupLevel = NewSetupLevel; + } + int WaitMs = 100; + Lock(); + if (cDvbSubtitleBitmaps *sb = bitmaps->First()) { + int64_t STC = cDevice::PrimaryDevice()->GetSTC(); + int64_t Delta = 0; + if (STC >= 0) { + Delta = LimitTo32Bit(sb->Pts()) - LimitTo32Bit(STC); // some devices only deliver 32 bits + if (Delta > (int64_t(1) << 31)) + Delta -= (int64_t(1) << 32); + else if (Delta < -((int64_t(1) << 31) - 1)) + Delta += (int64_t(1) << 32); + } + else { + //TODO sync on PTS? are there actually devices that don't deliver an STC? + } + Delta /= 90; // STC and PTS are in 1/90000s + if (abs(Delta) <= MAXDELTA) { + if (Delta <= 0) { + dbgconverter("Got %d bitmaps, showing #%d\n", bitmaps->Count(), sb->Index() + 1); + if (AssertOsd()) { + sb->Draw(osd); + Timeout.Set(sb->Timeout() * 1000); + dbgconverter("PTS: %lld STC: %lld (%lld) timeout: %d\n", sb->Pts(), cDevice::PrimaryDevice()->GetSTC(), Delta, sb->Timeout()); + } + bitmaps->Del(sb); + } + else + WaitMs = min(max(Delta, int64_t(0)), int64_t(1000)); + } + else + bitmaps->Del(sb); + } + Unlock(); + cCondWait::SleepMs(WaitMs); + } +} + +tColor cDvbSubtitleConverter::yuv2rgb(int Y, int Cb, int Cr) +{ + int Ey, Epb, Epr; + int Eg, Eb, Er; + + Ey = (Y - 16); + Epb = (Cb - 128); + Epr = (Cr - 128); + /* ITU-R 709 */ + Er = max(min(((298 * Ey + 460 * Epr) / 256), 255), 0); + Eg = max(min(((298 * Ey - 55 * Epb - 137 * Epr) / 256), 255), 0); + Eb = max(min(((298 * Ey + 543 * Epb ) / 256), 255), 0); + + return (Er << 16) | (Eg << 8) | Eb; +} + +bool cDvbSubtitleConverter::AssertOsd(void) +{ + return osd || (osd = cOsdProvider::NewOsd(0, Setup.SubtitleOffset, OSD_LEVEL_SUBTITLES)); +} + +int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts) +{ + if (Length > 5 && Data[0] == 0x0F) { + int segmentLength = (Data[4] << 8) + Data[5] + 6; + if (segmentLength > Length) + return -1; + int segmentType = Data[1]; + int pageId = (Data[2] << 8) + Data[3]; + cDvbSubtitlePage *page = NULL; + LOCK_THREAD; + for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) { + if (sp->PageId() == pageId) { + page = sp; + break; + } + } + if (!page) { + page = new cDvbSubtitlePage(pageId); + pages->Add(page); + dbgpages("Create SubtitlePage %d (total pages = %d)\n", pageId, pages->Count()); + } + switch (segmentType) { + case PAGE_COMPOSITION_SEGMENT: { + dbgsegments("PAGE_COMPOSITION_SEGMENT\n"); + int pageVersion = (Data[6 + 1] & 0xF0) >> 4; + if (pageVersion == page->Version()) + break; // no update + page->SetVersion(pageVersion); + if (Pts) + page->SetPts(Pts); + page->SetTimeout(Data[6]); + page->SetState((Data[6 + 1] & 0x0C) >> 2); + page->regions.Clear(); + dbgpages("Update page id %d version %d pts %lld timeout %d state %d\n", pageId, page->Version(), page->Pts(), page->Timeout(), page->State()); + for (int i = 6 + 2; i < segmentLength; i += 6) { + cSubtitleRegion *region = page->GetRegionById(Data[i], true); + region->SetHorizontalAddress((Data[i + 2] << 8) + Data[i + 3]); + region->SetVerticalAddress((Data[i + 4] << 8) + Data[i + 5]); + } + break; + } + case REGION_COMPOSITION_SEGMENT: { + dbgsegments("REGION_COMPOSITION_SEGMENT\n"); + cSubtitleRegion *region = page->GetRegionById(Data[6]); + if (!region) + break; + int regionVersion = (Data[6 + 1] & 0xF0) >> 4; + if (regionVersion == region->Version()) + break; // no update + region->SetVersion(regionVersion); + bool regionFillFlag = (Data[6 + 1] & 0x08) >> 3; + int regionWidth = (Data[6 + 2] << 8) | Data[6 + 3]; + int regionHeight = (Data[6 + 4] << 8) | Data[6 + 5]; + region->SetSize(regionWidth, regionHeight); + region->SetLevel((Data[6 + 6] & 0xE0) >> 5); + region->SetDepth((Data[6 + 6] & 0x1C) >> 2); + region->SetClutId(Data[6 + 7]); + dbgregions("Region pageId %d id %d version %d fill %d width %d height %d level %d depth %d clutId %d\n", pageId, region->RegionId(), region->Version(), regionFillFlag, regionWidth, regionHeight, region->Level(), region->Depth(), region->ClutId()); + if (regionFillFlag) { + switch (region->Bpp()) { + case 2: region->FillRegion((Data[6 + 9] & 0x0C) >> 2); break; + case 4: region->FillRegion((Data[6 + 9] & 0xF0) >> 4); break; + case 8: region->FillRegion(Data[6 + 8]); break; + } + } + for (int i = 6 + 10; i < segmentLength; i += 6) { + cSubtitleObject *object = region->GetObjectById((Data[i] << 8) | Data[i + 1], true); + int objectType = (Data[i + 2] & 0xC0) >> 6; + object->SetCodingMethod(objectType); + object->SetProviderFlag((Data[i + 2] & 0x30) >> 4); + int objectHorizontalPosition = ((Data[i + 2] & 0x0F) << 8) | Data[i + 3]; + int objectVerticalPosition = ((Data[i + 4] & 0x0F) << 8) | Data[i + 5]; + object->SetPosition(objectHorizontalPosition, objectVerticalPosition); + if (objectType == 0x01 || objectType == 0x02) { + object->SetForegroundColor(Data[i + 6]); + object->SetBackgroundColor(Data[i + 7]); + i += 2; + } + } + break; + } + case CLUT_DEFINITION_SEGMENT: { + dbgsegments("CLUT_DEFINITION_SEGMENT\n"); + cSubtitleClut *clut = page->GetClutById(Data[6], true); + int clutVersion = (Data[6 + 1] & 0xF0) >> 4; + if (clutVersion == clut->Version()) + break; // no update + clut->SetVersion(clutVersion); + dbgcluts("Clut pageId %d id %d version %d\n", pageId, clut->ClutId(), clut->Version()); + for (int i = 6 + 2; i < segmentLength; i += 2) { + uchar clutEntryId = Data[i]; + bool fullRangeFlag = Data[i + 1] & 1; + uchar yval; + uchar crval; + uchar cbval; + uchar tval; + if (fullRangeFlag) { + yval = Data[i + 2]; + crval = Data[i + 3]; + cbval = Data[i + 4]; + tval = Data[i + 5]; + } + else { + yval = Data[i + 2] & 0xFC; + crval = (Data[i + 2] & 0x03) << 6; + crval |= (Data[i + 3] & 0xC0) >> 2; + cbval = (Data[i + 3] & 0x3C) << 2; + tval = (Data[i + 3] & 0x03) << 6; + } + tColor value = 0; + if (yval) { + value = yuv2rgb(yval, cbval, crval); + value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24; + } + int EntryFlags = Data[i + 1]; + dbgcluts("%2d %d %d %d %08X\n", clutEntryId, (EntryFlags & 0x80) ? 2 : 0, (EntryFlags & 0x40) ? 4 : 0, (EntryFlags & 0x20) ? 8 : 0, value); + if ((EntryFlags & 0x80) != 0) + clut->SetColor(2, clutEntryId, value); + if ((EntryFlags & 0x40) != 0) + clut->SetColor(4, clutEntryId, value); + if ((EntryFlags & 0x20) != 0) + clut->SetColor(8, clutEntryId, value); + i += fullRangeFlag ? 4 : 2; + } + dbgcluts("\n"); + page->UpdateRegionPalette(clut); + break; + } + case OBJECT_DATA_SEGMENT: { + dbgsegments("OBJECT_DATA_SEGMENT\n"); + cSubtitleObject *object = page->GetObjectById((Data[6] << 8) | Data[6 + 1]); + if (!object) + break; + int objectVersion = (Data[6 + 2] & 0xF0) >> 4; + if (objectVersion == object->Version()) + break; // no update + object->SetVersion(objectVersion); + int codingMethod = (Data[6 + 2] & 0x0C) >> 2; + object->SetNonModifyingColorFlag(Data[6 + 2] & 0x01); + dbgobjects("Object pageId %d id %d version %d method %d modify %d\n", pageId, object->ObjectId(), object->Version(), object->CodingMethod(), object->NonModifyingColorFlag()); + if (codingMethod == 0) { // coding of pixels + int i = 6 + 3; + int topFieldLength = (Data[i] << 8) | Data[i + 1]; + int bottomFieldLength = (Data[i + 2] << 8) | Data[i + 3]; + object->DecodeSubBlock(Data + i + 4, topFieldLength, true); + if (bottomFieldLength) + object->DecodeSubBlock(Data + i + 4 + topFieldLength, bottomFieldLength, false); + else + object->DecodeSubBlock(Data + i + 4, topFieldLength, false); + } + else if (codingMethod == 1) { // coded as a string of characters + //TODO implement textual subtitles + } + break; + } + case END_OF_DISPLAY_SET_SEGMENT: { + dbgsegments("END_OF_DISPLAY_SET_SEGMENT\n"); + FinishPage(page); + } + default: + dbgsegments("*** unknown segment type: %02X\n", segmentType); + } + return segmentLength; + } + return -1; +} + +void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) +{ + if (!AssertOsd()) + return; + tArea *Areas = Page->GetAreas(); + int NumAreas = Page->regions.Count(); + int Bpp = 8; + bool Reduced = false; + while (osd->CanHandleAreas(Areas, NumAreas) != oeOk) { + int HalfBpp = Bpp / 2; + if (HalfBpp >= 2) { + for (int i = 0; i < NumAreas; i++) { + if (Areas[i].bpp >= Bpp) { + Areas[i].bpp = HalfBpp; + Reduced = true; + } + } + Bpp = HalfBpp; + } + else + return; // unable to draw bitmaps + } + if (Reduced) { + for (int i = 0; i < NumAreas; i++) { + cSubtitleRegion *sr = Page->regions.Get(i); + if (sr->Bpp() != Areas[i].bpp) { + if (sr->Level() <= Areas[i].bpp) { + //TODO this is untested - didn't have any such subtitle stream + cSubtitleClut *Clut = Page->GetClutById(sr->ClutId()); + if (Clut) { + dbgregions("reduce region %d bpp %d level %d area bpp %d\n", sr->RegionId(), sr->Bpp(), sr->Level(), Areas[i].bpp); + sr->ReduceBpp(*Clut->GetPalette(sr->Bpp())); + } + } + else { + dbgregions("condense region %d bpp %d level %d area bpp %d\n", sr->RegionId(), sr->Bpp(), sr->Level(), Areas[i].bpp); + sr->ShrinkBpp(Areas[i].bpp); + } + } + } + } + cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->Pts(), Page->Timeout(), Areas, NumAreas); + bitmaps->Add(Bitmaps); + for (cSubtitleRegion *sr = Page->regions.First(); sr; sr = Page->regions.Next(sr)) { + int posX = sr->HorizontalAddress(); + int posY = sr->VerticalAddress(); + cBitmap *bm = new cBitmap(sr->Width(), sr->Height(), sr->Bpp(), posX, posY); + bm->DrawBitmap(posX, posY, *sr); + Bitmaps->AddBitmap(bm); + } +} diff --git a/dvbsubtitle.h b/dvbsubtitle.h new file mode 100644 index 00000000..d848134d --- /dev/null +++ b/dvbsubtitle.h @@ -0,0 +1,43 @@ +/* + * dvbsubtitle.h: DVB subtitles + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * Original author: Marco Schlüßler + * + * $Id: dvbsubtitle.h 1.1 2007/10/12 14:27:30 kls Exp $ + */ + +#ifndef __DVBSUBTITLE_H +#define __DVBSUBTITLE_H + +#include "osd.h" +#include "thread.h" +#include "tools.h" + +class cDvbSubtitlePage; +class cDvbSubtitleAssembler; +class cDvbSubtitleBitmaps; + +class cDvbSubtitleConverter : public cThread { +private: + static int setupLevel; + cDvbSubtitleAssembler *dvbSubtitleAssembler; + cOsd *osd; + cList *pages; + cList *bitmaps; + tColor yuv2rgb(int Y, int Cb, int Cr); + bool AssertOsd(void); + int ExtractSegment(const uchar *Data, int Length, int64_t Pts); + void FinishPage(cDvbSubtitlePage *Page); +public: + cDvbSubtitleConverter(void); + virtual ~cDvbSubtitleConverter(); + void Action(void); + void Reset(void); + int Convert(const uchar *Data, int Length); + static void SetupChanged(void); + }; + +#endif //__DVBSUBTITLE_H diff --git a/eit.c b/eit.c index dcac1164..b38e3a8a 100644 --- a/eit.c +++ b/eit.c @@ -8,7 +8,7 @@ * Robert Schneider and Rolf Hakenes . * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg . * - * $Id: eit.c 1.125 2007/07/28 13:16:43 kls Exp $ + * $Id: eit.c 1.126 2007/09/26 10:56:33 kls Exp $ */ #include "eit.h" @@ -219,11 +219,11 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bo SI::ComponentDescriptor *cd = (SI::ComponentDescriptor *)d; uchar Stream = cd->getStreamContent(); uchar Type = cd->getComponentType(); - if (1 <= Stream && Stream <= 2 && Type != 0) { + if (1 <= Stream && Stream <= 3 && Type != 0) { // 1=video, 2=audio, 3=subtitles if (!Components) Components = new cComponents; char buffer[Utf8BufSize(256)]; - Components->SetComponent(Components->NumComponents(), cd->getStreamContent(), cd->getComponentType(), I18nNormalizeLanguageCode(cd->languageCode), cd->description.getText(buffer, sizeof(buffer))); + Components->SetComponent(Components->NumComponents(), Stream, Type, I18nNormalizeLanguageCode(cd->languageCode), cd->description.getText(buffer, sizeof(buffer))); } } break; diff --git a/keys.c b/keys.c index 58ae1fa6..d123705f 100644 --- a/keys.c +++ b/keys.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: keys.c 1.15 2007/08/11 11:30:18 kls Exp $ + * $Id: keys.c 1.16 2007/09/26 12:35:21 kls Exp $ */ #include "keys.h" @@ -49,6 +49,7 @@ static tKey keyTable[] = { // "Up" and "Down" must be the first two keys! { kVolDn, trNOOP("Key$Volume-") }, { kMute, trNOOP("Key$Mute") }, { kAudio, trNOOP("Key$Audio") }, + { kSubtitles, trNOOP("Key$Subtitles") }, { kSchedule, trNOOP("Key$Schedule") }, { kChannels, trNOOP("Key$Channels") }, { kTimers, trNOOP("Key$Timers") }, diff --git a/keys.h b/keys.h index 2adcad8a..b456c7ba 100644 --- a/keys.h +++ b/keys.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: keys.h 1.13 2007/08/24 13:15:48 kls Exp $ + * $Id: keys.h 1.14 2007/09/26 12:34:50 kls Exp $ */ #ifndef __KEYS_H @@ -43,6 +43,7 @@ enum eKeys { // "Up" and "Down" must be the first two keys! kVolDn, kMute, kAudio, + kSubtitles, kSchedule, kChannels, kTimers, diff --git a/menu.c b/menu.c index 444fd750..9a0123cc 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.461 2007/08/24 13:15:48 kls Exp $ + * $Id: menu.c 1.462 2007/10/12 14:30:29 kls Exp $ */ #include "menu.h" @@ -260,6 +260,8 @@ void cMenuEditChannel::Setup(void) Add(new cMenuEditIntItem( tr("Apid2"), &data.apids[1], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Dpid1"), &data.dpids[0], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Dpid2"), &data.dpids[1], 0, 0x1FFF)); + Add(new cMenuEditIntItem( tr("Spid1"), &data.spids[0], 0, 0x1FFF)); + Add(new cMenuEditIntItem( tr("Spid2"), &data.spids[1], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Tpid"), &data.tpid, 0, 0x1FFF)); Add(new cMenuEditCaItem( tr("CA"), &data.caids[0])); Add(new cMenuEditIntItem( tr("Sid"), &data.sid, 1, 0xFFFF)); @@ -2400,6 +2402,8 @@ class cMenuSetupDVB : public cMenuSetupBase { private: int originalNumAudioLanguages; int numAudioLanguages; + int originalNumSubtitleLanguages; + int numSubtitleLanguages; void Setup(void); const char *videoDisplayFormatTexts[3]; const char *updateChannelsTexts[6]; @@ -2412,7 +2416,10 @@ cMenuSetupDVB::cMenuSetupDVB(void) { for (numAudioLanguages = 0; numAudioLanguages < I18nLanguages()->Size() && data.AudioLanguages[numAudioLanguages] >= 0; numAudioLanguages++) ; + for (numSubtitleLanguages = 0; numSubtitleLanguages < I18nLanguages()->Size() && data.SubtitleLanguages[numSubtitleLanguages] >= 0; numSubtitleLanguages++) + ; originalNumAudioLanguages = numAudioLanguages; + originalNumSubtitleLanguages = numSubtitleLanguages; videoDisplayFormatTexts[0] = tr("pan&scan"); videoDisplayFormatTexts[1] = tr("letterbox"); videoDisplayFormatTexts[2] = tr("center cut out"); @@ -2442,6 +2449,15 @@ void cMenuSetupDVB::Setup(void) Add(new cMenuEditIntItem( tr("Setup.DVB$Audio languages"), &numAudioLanguages, 0, I18nLanguages()->Size())); for (int i = 0; i < numAudioLanguages; i++) Add(new cMenuEditStraItem(tr("Setup.DVB$Audio language"), &data.AudioLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0))); + Add(new cMenuEditBoolItem(tr("Setup.DVB$Display subtitles"), &data.DisplaySubtitles)); + if (data.DisplaySubtitles) { + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle languages"), &numSubtitleLanguages, 0, I18nLanguages()->Size())); + for (int i = 0; i < numSubtitleLanguages; i++) + Add(new cMenuEditStraItem(tr("Setup.DVB$Subtitle language"), &data.SubtitleLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0))); + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle offset"), &data.SubtitleOffset, -50, 50)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle foreground transparency"), &data.SubtitleFgTransparency, 0, 9)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle background transparency"), &data.SubtitleBgTransparency, 0, 10)); + } SetCurrent(Get(current)); Display(); @@ -2453,11 +2469,15 @@ eOSState cMenuSetupDVB::ProcessKey(eKeys Key) int oldVideoDisplayFormat = ::Setup.VideoDisplayFormat; bool oldVideoFormat = ::Setup.VideoFormat; bool newVideoFormat = data.VideoFormat; + bool oldDisplaySubtitles = ::Setup.DisplaySubtitles; + bool newDisplaySubtitles = data.DisplaySubtitles; int oldnumAudioLanguages = numAudioLanguages; + int oldnumSubtitleLanguages = numSubtitleLanguages; eOSState state = cMenuSetupBase::ProcessKey(Key); if (Key != kNone) { bool DoSetup = data.VideoFormat != newVideoFormat; + DoSetup |= data.DisplaySubtitles != newDisplaySubtitles; if (numAudioLanguages != oldnumAudioLanguages) { for (int i = oldnumAudioLanguages; i < numAudioLanguages; i++) { data.AudioLanguages[i] = 0; @@ -2476,6 +2496,24 @@ eOSState cMenuSetupDVB::ProcessKey(eKeys Key) data.AudioLanguages[numAudioLanguages] = -1; DoSetup = true; } + if (numSubtitleLanguages != oldnumSubtitleLanguages) { + for (int i = oldnumSubtitleLanguages; i < numSubtitleLanguages; i++) { + data.SubtitleLanguages[i] = 0; + for (int l = 0; l < I18nLanguages()->Size(); l++) { + int k; + for (k = 0; k < oldnumSubtitleLanguages; k++) { + if (data.SubtitleLanguages[k] == l) + break; + } + if (k >= oldnumSubtitleLanguages) { + data.SubtitleLanguages[i] = l; + break; + } + } + } + data.SubtitleLanguages[numSubtitleLanguages] = -1; + DoSetup = true; + } if (DoSetup) Setup(); } @@ -2486,6 +2524,9 @@ eOSState cMenuSetupDVB::ProcessKey(eKeys Key) cDevice::PrimaryDevice()->SetVideoDisplayFormat(eVideoDisplayFormat(::Setup.VideoDisplayFormat)); if (::Setup.VideoFormat != oldVideoFormat) cDevice::PrimaryDevice()->SetVideoFormat(::Setup.VideoFormat); + if (::Setup.DisplaySubtitles != oldDisplaySubtitles) + cDevice::PrimaryDevice()->EnsureSubtitleTrack(); + cDvbSubtitleConverter::SetupChanged(); } return state; } @@ -3128,14 +3169,18 @@ static void SetTrackDescriptions(int LiveChannel) if (Components) { int indexAudio = 0; int indexDolby = 0; + int indexSubtitle = 0; for (int i = 0; i < Components->NumComponents(); i++) { const tComponent *p = Components->Component(i); - if (p->stream == 2) { - if (p->type == 0x05) - cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description); - else - cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, indexAudio++, 0, LiveChannel ? NULL : p->language, p->description); - } + switch (p->stream) { + case 2: if (p->type == 0x05) + cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description); + else + cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, indexAudio++, 0, LiveChannel ? NULL : p->language, p->description); + break; + case 3: cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, indexSubtitle++, 0, LiveChannel ? NULL : p->language, p->description); + break; + } } } } @@ -3491,7 +3536,7 @@ cDisplayTracks::cDisplayTracks(void) numTracks++; } } - descriptions[numTracks] = 0; + descriptions[numTracks] = NULL; timeout.Set(TRACKTIMEOUT); displayTracks = Skins.Current()->DisplayTracks(tr("Button$Audio"), numTracks, descriptions); Show(); @@ -3569,7 +3614,7 @@ eOSState cDisplayTracks::ProcessKey(eKeys Key) timeout.Set(TRACKTIMEOUT); break; case kOk: - if (track != cDevice::PrimaryDevice()->GetCurrentAudioTrack()) + if (types[track] != cDevice::PrimaryDevice()->GetCurrentAudioTrack()) oldTrack = -1; // make sure we explicitly switch to that track timeout.Set(); break; @@ -3588,6 +3633,105 @@ eOSState cDisplayTracks::ProcessKey(eKeys Key) return timeout.TimedOut() ? osEnd : osContinue; } +// --- cDisplaySubtitleTracks ------------------------------------------------ + +cDisplaySubtitleTracks *cDisplaySubtitleTracks::currentDisplayTracks = NULL; + +cDisplaySubtitleTracks::cDisplaySubtitleTracks(void) +:cOsdObject(true) +{ + SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0); + currentDisplayTracks = this; + numTracks = track = 0; + types[numTracks] = ttNone; + descriptions[numTracks] = strdup(tr("No subtitles")); + numTracks++; + eTrackType CurrentSubtitleTrack = cDevice::PrimaryDevice()->GetCurrentSubtitleTrack(); + for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) { + const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i)); + if (TrackId && TrackId->id) { + types[numTracks] = eTrackType(i); + descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i)); + if (i == CurrentSubtitleTrack) + track = numTracks; + numTracks++; + } + } + descriptions[numTracks] = NULL; + timeout.Set(TRACKTIMEOUT); + displayTracks = Skins.Current()->DisplayTracks(tr("Button$Subtitles"), numTracks, descriptions); + Show(); +} + +cDisplaySubtitleTracks::~cDisplaySubtitleTracks() +{ + delete displayTracks; + currentDisplayTracks = NULL; + for (int i = 0; i < numTracks; i++) + free(descriptions[i]); + cStatus::MsgOsdClear(); +} + +void cDisplaySubtitleTracks::Show(void) +{ + displayTracks->SetTrack(track, descriptions); + displayTracks->Flush(); + //cStatus::MsgSetSubtitleTrack(track, descriptions); //TODO better make a more general cStatus::MsgSetTrack(tr("Subtitles"), track, descriptions) +} + +cDisplaySubtitleTracks *cDisplaySubtitleTracks::Create(void) +{ + if (cDevice::PrimaryDevice()->NumSubtitleTracks() > 0) { + if (!currentDisplayTracks) + new cDisplaySubtitleTracks; + return currentDisplayTracks; + } + Skins.Message(mtWarning, tr("No subtitles available!")); + return NULL; +} + +void cDisplaySubtitleTracks::Process(eKeys Key) +{ + if (currentDisplayTracks) + currentDisplayTracks->ProcessKey(Key); +} + +eOSState cDisplaySubtitleTracks::ProcessKey(eKeys Key) +{ + int oldTrack = track; + switch (Key) { + case kUp|k_Repeat: + case kUp: + case kDown|k_Repeat: + case kDown: + if (NORMALKEY(Key) == kUp && track > 0) + track--; + else if (NORMALKEY(Key) == kDown && track < numTracks - 1) + track++; + timeout.Set(TRACKTIMEOUT); + break; + case kSubtitles|k_Repeat: + case kSubtitles: + if (++track >= numTracks) + track = 0; + timeout.Set(TRACKTIMEOUT); + break; + case kOk: + if (types[track] != cDevice::PrimaryDevice()->GetCurrentSubtitleTrack()) + oldTrack = -1; // make sure we explicitly switch to that track + timeout.Set(); + break; + case kNone: break; + default: if ((Key & k_Release) == 0) + return osEnd; + } + if (track != oldTrack) { + Show(); + cDevice::PrimaryDevice()->SetCurrentSubtitleTrack(types[track], true); + } + return timeout.TimedOut() ? osEnd : osContinue; +} + // --- cRecordControl -------------------------------------------------------- cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) diff --git a/menu.h b/menu.h index 1613aa4e..62a3868a 100644 --- a/menu.h +++ b/menu.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 1.88 2007/08/12 10:35:42 kls Exp $ + * $Id: menu.h 1.89 2007/09/26 14:35:57 kls Exp $ */ #ifndef __MENU_H @@ -128,6 +128,24 @@ public: eOSState ProcessKey(eKeys Key); }; +class cDisplaySubtitleTracks : public cOsdObject { +private: + cSkinDisplayTracks *displayTracks; + cTimeMs timeout; + eTrackType types[ttMaxTrackTypes]; + char *descriptions[ttMaxTrackTypes + 1]; // list is NULL terminated + int numTracks, track; + static cDisplaySubtitleTracks *currentDisplayTracks; + virtual void Show(void); + cDisplaySubtitleTracks(void); +public: + virtual ~cDisplaySubtitleTracks(); + static bool IsOpen(void) { return currentDisplayTracks != NULL; } + static cDisplaySubtitleTracks *Create(void); + static void Process(eKeys Key); + eOSState ProcessKey(eKeys Key); + }; + cOsdObject *CamControl(void); class cMenuRecordingItem; diff --git a/osd.c b/osd.c index 1c91184d..47a11b21 100644 --- a/osd.c +++ b/osd.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.c 1.74 2007/08/26 09:44:50 kls Exp $ + * $Id: osd.c 1.75 2007/10/12 12:38:36 kls Exp $ */ #include "osd.h" @@ -82,7 +82,7 @@ void cPalette::SetColor(int Index, tColor Color) } } -const tColor *cPalette::Colors(int &NumColors) +const tColor *cPalette::Colors(int &NumColors) const { NumColors = numColors; return numColors ? color : NULL; @@ -112,7 +112,7 @@ void cPalette::Replace(const cPalette &Palette) antiAliasGranularity = Palette.antiAliasGranularity; } -tColor cPalette::Blend(tColor ColorFg, tColor ColorBg, uint8_t Level) +tColor cPalette::Blend(tColor ColorFg, tColor ColorBg, uint8_t Level) const { if (antiAliasGranularity > 0) Level = uint8_t(int(Level / antiAliasGranularity + 0.5) * antiAliasGranularity); @@ -131,7 +131,7 @@ tColor cPalette::Blend(tColor ColorFg, tColor ColorBg, uint8_t Level) return (A << 24) | (R << 16) | (G << 8) | B; } -int cPalette::ClosestColor(tColor Color, int MaxDiff) +int cPalette::ClosestColor(tColor Color, int MaxDiff) const { int n = 0; int d = INT_MAX; @@ -640,6 +640,79 @@ const tIndex *cBitmap::Data(int x, int y) return &bitmap[y * width + x]; } +void cBitmap::ReduceBpp(const cPalette &Palette) +{ + int NewBpp = Palette.Bpp(); + if (Bpp() == 4 && NewBpp == 2) { + for (int i = width * height; i--; ) { + tIndex p = bitmap[i]; + bitmap[i] = (p >> 2) | ((p & 0x03) != 0); + } + } + else if (Bpp() == 8) { + if (NewBpp == 2) { + for (int i = width * height; i--; ) { + tIndex p = bitmap[i]; + bitmap[i] = (p >> 6) | ((p & 0x30) != 0); + } + } + else if (NewBpp == 4) { + for (int i = width * height; i--; ) { + tIndex p = bitmap[i]; + bitmap[i] = p >> 4; + } + } + else + return; + } + else + return; + SetBpp(NewBpp); + Replace(Palette); +} + +void cBitmap::ShrinkBpp(int NewBpp) +{ + int NumOldColors; + const tColor *Colors = this->Colors(NumOldColors); + if (Colors) { + // Find the most frequently used colors and create a map table: + int Used[MAXNUMCOLORS] = { 0 }; + int Map[MAXNUMCOLORS] = { 0 }; + for (int i = width * height; i--; ) + Used[bitmap[i]]++; + int MaxNewColors = (NewBpp == 4) ? 16 : 4; + cPalette NewPalette(NewBpp); + for (int i = 0; i < MaxNewColors; i++) { + int Max = 0; + int Index = -1; + for (int n = 0; n < NumOldColors; n++) { + if (Used[n] > Max) { + Max = Used[n]; + Index = n; + } + } + if (Index >= 0) { + Used[Index] = 0; + Map[Index] = i; + NewPalette.SetColor(i, Colors[Index]); + } + else + break; + } + // Complete the map table for all other colors (will be set to closest match): + for (int n = 0; n < NumOldColors; n++) { + if (Used[n]) + Map[n] = NewPalette.Index(Colors[n]); + } + // Do the actual index mapping: + for (int i = width * height; i--; ) + bitmap[i] = Map[bitmap[i]]; + SetBpp(NewBpp); + Replace(NewPalette); + } +} + // --- cOsd ------------------------------------------------------------------ int cOsd::osdLeft = 0; @@ -720,19 +793,18 @@ eOsdError cOsd::CanHandleAreas(const tArea *Areas, int NumAreas) eOsdError cOsd::SetAreas(const tArea *Areas, int NumAreas) { - eOsdError Result = oeUnknown; - if (numBitmaps == 0) { - Result = CanHandleAreas(Areas, NumAreas); - if (Result == oeOk) { - width = height = 0; - for (int i = 0; i < NumAreas; i++) { - bitmaps[numBitmaps++] = new cBitmap(Areas[i].Width(), Areas[i].Height(), Areas[i].bpp, Areas[i].x1, Areas[i].y1); - width = max(width, Areas[i].x2 + 1); - height = max(height, Areas[i].y2 + 1); - } - } + eOsdError Result = CanHandleAreas(Areas, NumAreas); + if (Result == oeOk) { + while (numBitmaps) + delete bitmaps[--numBitmaps]; + width = height = 0; + for (int i = 0; i < NumAreas; i++) { + bitmaps[numBitmaps++] = new cBitmap(Areas[i].Width(), Areas[i].Height(), Areas[i].bpp, Areas[i].x1, Areas[i].y1); + width = max(width, Areas[i].x2 + 1); + height = max(height, Areas[i].y2 + 1); + } } - if (Result != oeOk) + else esyslog("ERROR: cOsd::SetAreas returned %d", Result); return Result; } @@ -818,7 +890,7 @@ cOsdProvider::~cOsdProvider() cOsd *cOsdProvider::NewOsd(int Left, int Top, uint Level) { - if (Level == 0 && cOsd::IsOpen()) + if (Level == OSD_LEVEL_DEFAULT && cOsd::IsOpen()) esyslog("ERROR: attempt to open OSD while it is already open - using dummy OSD!"); else if (osdProvider) { cOsd *ActiveOsd = cOsd::Osds.Size() ? cOsd::Osds[0] : NULL; diff --git a/osd.h b/osd.h index 604b2d22..42ebc1aa 100644 --- a/osd.h +++ b/osd.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.h 1.57 2007/08/26 09:45:38 kls Exp $ + * $Id: osd.h 1.58 2007/10/12 14:28:44 kls Exp $ */ #ifndef __OSD_H @@ -17,6 +17,9 @@ #include "font.h" #include "tools.h" +#define OSD_LEVEL_DEFAULT 0 +#define OSD_LEVEL_SUBTITLES 10 + #define MAXNUMCOLORS 256 enum { @@ -69,7 +72,7 @@ public: ///< a single color combination, and may not be able to serve all ///< requested colors. By default the palette assumes there will be ///< 10 fixed colors and 10 color combinations. - int Bpp(void) { return bpp; } + int Bpp(void) const { return bpp; } void Reset(void); ///< Resets the palette, making it contain no colors. int Index(tColor Color); @@ -77,7 +80,7 @@ public: ///< If Color is not yet contained in this palette, it will be added if ///< there is a free slot. If the color can't be added to this palette, ///< the closest existing color will be returned. - tColor Color(int Index) { return Index < maxColors ? color[Index] : 0; } + tColor Color(int Index) const { return Index < maxColors ? color[Index] : 0; } ///< Returns the color at the given Index. If Index is outside the valid ///< range, 0 will be returned. void SetBpp(int Bpp); @@ -87,7 +90,7 @@ public: ///< Sets the palette entry at Index to Color. If Index is larger than ///< the number of currently used entries in this palette, the entries ///< in between will have undefined values. - const tColor *Colors(int &NumColors); + const tColor *Colors(int &NumColors) const; ///< Returns a pointer to the complete color table and stores the ///< number of valid entries in NumColors. If no colors have been ///< stored yet, NumColors will be set to 0 and the function will @@ -102,14 +105,14 @@ public: void Replace(const cPalette &Palette); ///< Replaces the colors of this palette with the colors from the given ///< palette. - tColor Blend(tColor ColorFg, tColor ColorBg, uint8_t Level); + tColor Blend(tColor ColorFg, tColor ColorBg, uint8_t Level) const; ///< Determines a color that consists of a linear blend between ColorFg ///< and ColorBg. If Level is 0, the result is ColorBg, if it is 255, ///< the result is ColorFg. If SetAntiAliasGranularity() has been called previously, ///< Level will be mapped to a limited range of levels that allow to make best ///< use of the palette entries. - int ClosestColor(tColor Color, int MaxDiff = INT_MAX); - ///< Returns the index of a color in this paltte that is closest to the given + int ClosestColor(tColor Color, int MaxDiff = INT_MAX) const; + ///< Returns the index of a color in this palette that is closest to the given ///< Color. MaxDiff can be used to control the maximum allowed color difference. ///< If no color with a maximum difference of MaxDiff can be found, -1 will ///< be returned. With the default value of INT_MAX, there will always be @@ -232,6 +235,17 @@ public: ///< Returns the address of the index byte at the given coordinates. tColor GetColor(int x, int y) { return Color(*Data(x, y)); } ///< Returns the color at the given coordinates. + void ReduceBpp(const cPalette &Palette); + ///< Reduces the color depth of the bitmap to that of the given Palette. + ///< If Palette's color depth is not smaller than the bitmap's current + ///< color depth, or if it is not one of 4bpp or 2bpp, nothing happens. After + ///< reducing the color depth the current palette is replaced with + ///< the given one. + void ShrinkBpp(int NewBpp); + ///< Shrinks the color depth of the bitmap to NewBpp by keeping only + ///< the 2^NewBpp most frequently used colors as defined in the current palette. + ///< If NewBpp is not smaller than the bitmap's current color depth, + ///< or if it is not one of 4bpp or 2bpp, nothing happens. }; struct tArea { @@ -292,7 +306,7 @@ public: ///< This may be useful for plugins that determine the scaling of the ///< video image and need to scale the OSD accordingly to fit on the ///< screen. - static int IsOpen(void) { return Osds.Size() && Osds[0]->level == 0; } + static int IsOpen(void) { return Osds.Size() && Osds[0]->level == OSD_LEVEL_DEFAULT; } ///< Returns true if there is currently a level 0 OSD open. int Left(void) { return left; } int Top(void) { return top; } @@ -327,6 +341,7 @@ public: ///< If the OSD has been divided into several sub-areas, all areas that ///< are part of the rectangle that surrounds a given drawing operation ///< will be drawn into, with the proper offsets. + ///< A new call overwrites any previous settings virtual void SaveRegion(int x1, int y1, int x2, int y2); ///< Saves the region defined by the given coordinates for later restoration ///< through RestoreRegion(). Only one saved region can be active at any @@ -398,7 +413,7 @@ public: cOsdProvider(void); //XXX maybe parameter to make this one "sticky"??? (frame-buffer etc.) virtual ~cOsdProvider(); - static cOsd *NewOsd(int Left, int Top, uint Level = 0); + static cOsd *NewOsd(int Left, int Top, uint Level = OSD_LEVEL_DEFAULT); ///< Returns a pointer to a newly created cOsd object, which will be located ///< at the given coordinates. When the cOsd object is no longer needed, the ///< caller must delete it. If the OSD is already in use, or there is no OSD diff --git a/pat.c b/pat.c index 1f5636b9..a71f2d5c 100644 --- a/pat.c +++ b/pat.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: pat.c 1.17 2007/01/07 14:41:55 kls Exp $ + * $Id: pat.c 1.18 2007/09/02 10:44:19 kls Exp $ */ #include "pat.h" @@ -331,11 +331,14 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length int Ppid = pmt.getPCRPid(); int Apids[MAXAPIDS + 1] = { 0 }; // these lists are zero-terminated int Dpids[MAXDPIDS + 1] = { 0 }; + int Spids[MAXSPIDS + 1] = { 0 }; char ALangs[MAXAPIDS][MAXLANGCODE2] = { "" }; char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" }; + char SLangs[MAXSPIDS][MAXLANGCODE2] = { "" }; int Tpid = 0; int NumApids = 0; int NumDpids = 0; + int NumSpids = 0; for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) { switch (stream.getStreamType()) { case 1: // STREAMTYPE_11172_VIDEO @@ -387,6 +390,26 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length case SI::AC3DescriptorTag: dpid = stream.getPid(); break; + case SI::SubtitlingDescriptorTag: + if (NumSpids < MAXSPIDS) { + Spids[NumSpids] = stream.getPid(); + SI::SubtitlingDescriptor *sd = (SI::SubtitlingDescriptor *)d; + SI::SubtitlingDescriptor::Subtitling sub; + char *s = SLangs[NumSpids]; + int n = 0; + for (SI::Loop::Iterator it; sd->subtitlingLoop.getNext(sub, it); ) { + if (sub.languageCode[0]) { + if (n > 0) + *s++ = '+'; + strn0cpy(s, I18nNormalizeLanguageCode(sub.languageCode), MAXLANGCODE1); + s += strlen(s); + if (n++ > 1) + break; + } + } + NumSpids++; + } + break; case SI::TeletextDescriptorTag: Tpid = stream.getPid(); break; @@ -416,7 +439,7 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length } } if (Setup.UpdateChannels >= 2) { - Channel->SetPids(Vpid, Vpid ? Ppid : 0, Apids, ALangs, Dpids, DLangs, Tpid); + Channel->SetPids(Vpid, Vpid ? Ppid : 0, Apids, ALangs, Dpids, DLangs, Spids, SLangs, Tpid); Channel->SetCaIds(CaDescriptors->CaIds()); } Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors)); diff --git a/recording.c b/recording.c index 22bb9556..7886adaa 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 1.154 2007/06/17 13:10:12 kls Exp $ + * $Id: recording.c 1.155 2007/10/12 14:48:20 kls Exp $ */ #include "recording.h" @@ -296,6 +296,17 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) strn0cpy(Component->language, s, sizeof(Component->language)); } } + // The same applies to subtitles: + for (int i = 0; i < MAXSPIDS; i++) { + const char *s = Channel->Slang(i); + if (*s) { + tComponent *Component = Components->GetComponent(i, 3, 3); + if (!Component) + Components->SetComponent(Components->NumComponents(), 3, 3, s, NULL); + else if (strlen(s) > strlen(Component->language)) + strn0cpy(Component->language, s, sizeof(Component->language)); + } + } if (Components != event->Components()) ((cEvent *)event)->SetComponents(Components); } diff --git a/remux.c b/remux.c index ccc5b00d..aed38298 100644 --- a/remux.c +++ b/remux.c @@ -11,7 +11,7 @@ * The cRepacker family's code was originally written by Reinhard Nissl , * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. * - * $Id: remux.c 1.58 2007/02/24 16:36:10 kls Exp $ + * $Id: remux.c 1.59 2007/09/22 12:08:22 kls Exp $ */ #include "remux.h" @@ -1555,6 +1555,13 @@ void cTS2PES::send_ipack(void) buf[7] = 0x00; buf[8] = 0x00; count = 9; + if (!repacker && subStreamId) { + buf[9] = subStreamId; + buf[10] = 1; + buf[11] = 0; + buf[12] = 1; + count = 13; + } break; case 1: buf[6] = 0x0F; @@ -1766,6 +1773,19 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) return; } + if (!repacker && subStreamId) { + while (c < Count && found < (hlength + 9) && found < plength + 6) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (found == (hlength + 9)) { + uchar sbuf[] = { 0x01, 0x00, 0x00 }; + write_ipack(&subStreamId, 1); + write_ipack(sbuf, 3); + } + } + while (c < Count && found < plength + 6) { int l = Count - c; if (l + found > plength + 6) @@ -1856,7 +1876,7 @@ void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure) { exitOnFailure = ExitOnFailure; - isRadio = VPid == 0 || VPid == 1 || VPid == 0x1FFF; + noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; numUPTerrors = 0; synced = false; skipped = 0; @@ -1888,13 +1908,11 @@ cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, b while (*DPids && numTracks < MAXTRACKS && n < MAXDPIDS) ts2pes[numTracks++] = new cTS2PES(*DPids++, resultBuffer, IPACKS, 0x00, 0x80 + n++, new cDolbyRepacker); } - /* future... if (SPids) { int n = 0; while (*SPids && numTracks < MAXTRACKS && n < MAXSPIDS) - ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x28 + n++); + ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x20 + n++); } - */ } cRemux::~cRemux() @@ -2080,7 +2098,7 @@ uchar *cRemux::Get(int &Count, uchar *PictureType) l = GetPacketLength(data, resultCount, i); if (l < 0) return resultData; - if (isRadio) { + if (noVideo) { if (!synced) { if (PictureType) *PictureType = I_FRAME; diff --git a/remux.h b/remux.h index 2f117563..1b7727ec 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 1.16 2006/03/25 12:27:30 kls Exp $ + * $Id: remux.h 1.17 2007/09/02 10:19:06 kls Exp $ */ #ifndef __REMUX_H @@ -37,7 +37,7 @@ class cTS2PES; class cRemux { private: bool exitOnFailure; - bool isRadio; + bool noVideo; int numUPTerrors; bool synced; int skipped; diff --git a/vdr.5 b/vdr.5 index 6ea1e49d..405088f7 100644 --- a/vdr.5 +++ b/vdr.5 @@ -8,7 +8,7 @@ .\" License as specified in the file COPYING that comes with the .\" vdr distribution. .\" -.\" $Id: vdr.5 1.63 2007/08/05 12:58:35 kls Exp $ +.\" $Id: vdr.5 1.64 2007/09/26 10:57:08 kls Exp $ .\" .TH vdr 5 "07 Jan 2007" "1.4.5" "Video Disk Recorder Files" .SH NAME @@ -557,6 +557,8 @@ a recording is split into several files. The contents of these files is 0xC0...0xDF for audio 1...32 (up to 32 audio tracks may occur). Dolby Digital data is stored in packets with ids 0xBD ("Private Stream 1") and substream ids 0x80...0x87. +DVB subtitle data is stored in packets with ids 0xBD ("Private Stream 1") +and substream ids 0x20...0x27. .SS INDEX The file \fIindex.vdr\fR (if present in a recording directory) contains the (binary) index data into each of the the recording files @@ -646,7 +648,7 @@ l l. @is the title of the event <short text> @is the short text of the event (typically the name of the episode etc.) <description> @is the description of the event (any '|' characters will be interpreted as newlines) -<stream> @is the stream content (1 = video, 2 = audio) +<stream> @is the stream content (1 = video, 2 = audio, 3 = subtitles) <type> @is the stream type according to ETSI EN 300 468 <language> @is the three letter language code (optionally two codes, separated by '+') <descr> @is the description of this stream component diff --git a/vdr.c b/vdr.c index 81ae6ecb..e6347878 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/vdr * - * $Id: vdr.c 1.299 2007/08/25 08:51:13 kls Exp $ + * $Id: vdr.c 1.300 2007/09/26 14:36:48 kls Exp $ */ #include <getopt.h> @@ -1009,6 +1009,18 @@ int main(int argc, char *argv[]) cDisplayTracks::Process(key); key = kNone; break; + // Subtitle track control: + case kSubtitles: + if (cControl::Control()) + cControl::Control()->Hide(); + if (!cDisplaySubtitleTracks::IsOpen()) { + DELETE_MENU; + Menu = cDisplaySubtitleTracks::Create(); + } + else + cDisplaySubtitleTracks::Process(key); + key = kNone; + break; // Pausing live video: case kPause: if (!cControl::Control()) {