/// <summary> /// Beginnt die Aufzeichnung in einen <i>Transport Stream</i> - optional als /// Datei. /// </summary> /// <param name="filePath">Optional die Angabe eines Dateinames.</param> /// <param name="info">Die aktuelle Konfiguration der Quelle oder <i>null</i>, wenn keine bekannt ist.</param> /// <returns>Gesetzt, wenn die Quelle bekannt ist und der Empfang aktiviert wurde.</returns> /// <exception cref="InvalidOperationException">Es ist bereits eine Aufzeichnung aktiv.</exception> public bool CreateStream(string filePath, SourceInformation info) { // Validate if (m_TransportStream != null) { throw new InvalidOperationException(); } // Load if (info == null) { info = GetCurrentInformation(); } // Remember settings m_OriginalSettings = info; m_OriginalPath = filePath; m_FileCount = 0; // Validate if (m_OriginalSettings == null) { return(false); } // Forward CreateStream(NextStreamIdentifier, false); // Update signature m_CurrentSignature = CreateRecordingSignature(m_OriginalSettings); // Activate streaming if (!string.IsNullOrEmpty(m_LastStreamTarget)) { StreamingTarget = m_LastStreamTarget; } // Did it return(true); }
/// <summary> /// Prüft, ob sich an der Konfiguration der Quelle etwas verändert hat. /// </summary> /// <param name="information">Die zugehörige aktuelle Konfiguration der Quelle.</param> /// <returns>Gesetzt, wenn nun der Empfang aktiv ist.</returns> /// <exception cref="ArgumentNullException">Der angegebenen Konfiguration ist keine Quelle zugeordnet.</exception> /// <exception cref="ArgumentException">Die Konfiguration gehört zu einer anderen Quelle als der aktuellen.</exception> public bool RetestSourceInformation( SourceInformation information ) { // Must get the current source information if (information == null) { // Close any active stream CloseStream(); // Not active return false; } // Validate if (information.Source == null) throw new ArgumentNullException( "information.Source" ); if (!information.Source.Equals( Source )) throw new ArgumentException( information.Source.ToString(), "information.Source" ); // Nothing changed var signature = CreateRecordingSignature( information ); if (m_TransportStream != null) { // No need to switch if (signature.Equals( m_CurrentSignature )) return true; // Report to see what happened if (!string.IsNullOrEmpty( m_CurrentSignature )) try { // Report and ignore EventLog.WriteEntry( "DVB.NET", $"Detected PSI Change: {m_CurrentSignature} => {signature}", EventLogEntryType.Information ); } catch { } } // Get the streaming data var streamTarget = StreamingTarget; // Stop the current recording CloseStream(); // Update all m_OriginalSettings = information; m_CurrentSignature = signature; // Fire notifications if (BeforeRecreateStream != null) { // Request new stream configuration var newSelection = BeforeRecreateStream( this ); if (newSelection == null) return false; // Remember StreamSelection = newSelection.Clone(); } // Forward CreateStream( NextStreamIdentifier, true ); // Reactivate streaming if (streamTarget != null) StreamingTarget = streamTarget; // Did it return true; }
/// <summary> /// Ermittelt anhand der aktuellen Konfiguration, welche Datenströme wie aufgezeichnet /// werden. Die gemeldete Zeichenkette hat keine weitere Bedeutung, nur dass sie zum /// schnellen Vergleich verwemdet werden kann. /// </summary> /// <param name="information">Die aktuellen Daten zur zugehörigen Quelle.</param> /// <returns>Ein Schlüssel passend zur aktuellen Aufzeichnung.</returns> private string CreateRecordingSignature( SourceInformation information ) { // All keys - will be sorted to make sure that lists are ordered var flags = new List<int>(); // Various offset - PIDs are 12 Bit only! const int Offset_VideoType = 0x10000; const int Offset_VideoPID = 0x20000; const int Offset_MP2PID = 0x30000; const int Offset_AC3PID = 0x40000; const int Offset_TTXPID = 0x50000; const int Offset_SUBPID = 0x60000; // Video first if (0 != information.VideoStream) { // Get the type var videoType = (information.VideoType == VideoTypes.H264) ? EPG.StreamTypes.H264 : EPG.StreamTypes.Video13818; // Register flags.Add( Offset_VideoType + (int) videoType ); flags.Add( Offset_VideoPID + (int) information.VideoStream ); } // All MP2 audio foreach (var audio in information.AudioTracks) if (AudioTypes.MP2 == audio.AudioType) if (StreamSelection.MP2Tracks.LanguageMode == LanguageModes.Primary) { // Add as is flags.Add( Offset_MP2PID + (int) audio.AudioStream ); // Finish break; } else if (StreamSelection.MP2Tracks.Contains( audio.Language )) { // Add as is flags.Add( Offset_MP2PID + (int) audio.AudioStream ); } // All AC3 audio foreach (var audio in information.AudioTracks) if (AudioTypes.AC3 == audio.AudioType) if (StreamSelection.AC3Tracks.LanguageMode == LanguageModes.Primary) { // Add as is flags.Add( Offset_AC3PID + (int) audio.AudioStream ); // Finish break; } else if (StreamSelection.AC3Tracks.Contains( audio.Language )) { // Add as is flags.Add( Offset_AC3PID + (int) audio.AudioStream ); } // Videotext if (StreamSelection.Videotext) if (0 != information.TextStream) flags.Add( Offset_TTXPID + (int) information.TextStream ); // Subtitle streams var subtitles = new Dictionary<ushort, bool>(); // Subtitles foreach (var subtitle in information.Subtitles) if (StreamSelection.SubTitles.LanguageMode == LanguageModes.Primary) { // Add as is subtitles[subtitle.SubtitleStream] = true; // Finish break; } else if (StreamSelection.SubTitles.Contains( subtitle.Language )) { // Add as is subtitles[subtitle.SubtitleStream] = true; } // Process all subtitles foreach (var subtitleStream in subtitles.Keys) flags.Add( Offset_SUBPID + (int) subtitleStream ); // Create the key return (information.IsEncrypted ? "+" : "-") + string.Join( ".", flags.ConvertAll( f => f.ToString( "x5" ) ).ToArray() ); }
/// <summary> /// Beginnt die Aufzeichnung in einen <i>Transport Stream</i> - optional als /// Datei. /// </summary> /// <param name="filePath">Optional die Angabe eines Dateinames.</param> /// <param name="info">Die aktuelle Konfiguration der Quelle oder <i>null</i>, wenn keine bekannt ist.</param> /// <returns>Gesetzt, wenn die Quelle bekannt ist und der Empfang aktiviert wurde.</returns> /// <exception cref="InvalidOperationException">Es ist bereits eine Aufzeichnung aktiv.</exception> public bool CreateStream( string filePath, SourceInformation info ) { // Validate if (m_TransportStream != null) throw new InvalidOperationException(); // Load if (info == null) info = GetCurrentInformation(); // Remember settings m_OriginalSettings = info; m_OriginalPath = filePath; m_FileCount = 0; // Validate if (m_OriginalSettings == null) return false; // Forward CreateStream( NextStreamIdentifier, false ); // Update signature m_CurrentSignature = CreateRecordingSignature( m_OriginalSettings ); // Activate streaming if (!string.IsNullOrEmpty( m_LastStreamTarget )) StreamingTarget = m_LastStreamTarget; // Did it return true; }
/// <summary> /// Beginnt mit dem Auslesen der Quelldaten. /// </summary> /// <param name="source">Die gewünschte Quelle.</param> /// <param name="device">Das zu verwendende Gerät.</param> /// <param name="profile">Optional das zu berücksichtigende Geräteprofil.</param> /// <returns>Die Hintergrundaufgabe zum Auslesen der Quelledaten.</returns> public static CancellableTask<SourceInformation> GetSourceInformationAsync( this Hardware device, SourceIdentifier source, Profile profile = null ) { // Validate if (device == null) throw new ArgumentNullException( "no hardware to use", "device" ); if (source == null) throw new ArgumentException( "no source to get information for", "source" ); // Attach to tasks var patReader = device.AssociationTableReader; var groupReader = device.GroupReader; // Start return CancellableTask<SourceInformation>.Run( cancel => { // Check tasks if (groupReader == null) return null; if (patReader == null) return null; // Wait on tasks if (!groupReader.CancellableWait( cancel )) return null; if (!patReader.CancellableWait( cancel )) return null; // Get the current group information var groupInfo = groupReader.Result; if (groupInfo == null) return null; // See if group exists if (!groupInfo.Sources.Any( source.Equals )) return null; // Find the stream identifier for the service var pmtIdentifier = patReader.Result.FindService( source.Service ); if (!pmtIdentifier.HasValue) return null; // Wait for mapping table var pmtReader = device.GetTableAsync<PMT>( pmtIdentifier.Value ); if (!pmtReader.CancellableWait( cancel )) return null; // Request table var pmts = pmtReader.Result; if (pmts == null) return null; // Create dummy var currentSettings = new SourceInformation { Source = source, VideoType = VideoTypes.NoVideo }; // Process all PMT - actually should be only one foreach (var pmt in pmts) { // Overwrite encryption if CA descriptor is present if (pmt.Table.Descriptors != null) currentSettings.IsEncrypted = pmt.Table.Descriptors.Any( descriptor => EPG.DescriptorTags.CA == descriptor.Tag ); // Process the program entries foreach (var program in pmt.Table.ProgramEntries) currentSettings.Update( program ); } // Find the related station information var station = groupInfo.Sources.FirstOrDefault( source.Equals ) as Station; if (station != null) { // Take data from there currentSettings.Provider = station.Provider; currentSettings.Name = station.Name; // See if this is a service currentSettings.IsService = station.IsService; // Overwrite encryption if regular service entry exists if (!currentSettings.IsService) currentSettings.IsEncrypted = station.IsEncrypted; } // See if profile is attached if (profile != null) { // Apply the modifier var modifier = profile.GetFilter( currentSettings.Source ); if (modifier != null) modifier.ApplyTo( currentSettings ); } // Report return currentSettings; } ); }
/// <summary> /// Beginnt mit dem Auslesen der Quelldaten. /// </summary> /// <param name="source">Die gewünschte Quelle.</param> /// <param name="device">Das zu verwendende Gerät.</param> /// <param name="profile">Optional das zu berücksichtigende Geräteprofil.</param> /// <returns>Die Hintergrundaufgabe zum Auslesen der Quelledaten.</returns> public static CancellableTask <SourceInformation> GetSourceInformationAsync(this Hardware device, SourceIdentifier source, Profile profile = null) { // Validate if (device == null) { throw new ArgumentNullException("no hardware to use", "device"); } if (source == null) { throw new ArgumentException("no source to get information for", "source"); } // Attach to tasks var patReader = device.AssociationTableReader; var groupReader = device.GroupReader; // Start return (CancellableTask <SourceInformation> .Run(cancel => { // Check tasks if (groupReader == null) { return null; } if (patReader == null) { return null; } // Wait on tasks if (!groupReader.CancellableWait(cancel)) { return null; } if (!patReader.CancellableWait(cancel)) { return null; } // Get the current group information var groupInfo = groupReader.Result; if (groupInfo == null) { return null; } // See if group exists if (!groupInfo.Sources.Any(source.Equals)) { return null; } // Find the stream identifier for the service var pmtIdentifier = patReader.Result.FindService(source.Service); if (!pmtIdentifier.HasValue) { return null; } // Wait for mapping table var pmtReader = device.GetTableAsync <PMT>(pmtIdentifier.Value); if (!pmtReader.CancellableWait(cancel)) { return null; } // Request table var pmts = pmtReader.Result; if (pmts == null) { return null; } // Create dummy var currentSettings = new SourceInformation { Source = source, VideoType = VideoTypes.NoVideo }; // Process all PMT - actually should be only one foreach (var pmt in pmts) { // Overwrite encryption if CA descriptor is present if (pmt.Table.Descriptors != null) { currentSettings.IsEncrypted = pmt.Table.Descriptors.Any(descriptor => EPG.DescriptorTags.CA == descriptor.Tag); } // Process the program entries foreach (var program in pmt.Table.ProgramEntries) { currentSettings.Update(program); } } // Find the related station information var station = groupInfo.Sources.FirstOrDefault(source.Equals) as Station; if (station != null) { // Take data from there currentSettings.Provider = station.Provider; currentSettings.Name = station.Name; // See if this is a service currentSettings.IsService = station.IsService; // Overwrite encryption if regular service entry exists if (!currentSettings.IsService) { currentSettings.IsEncrypted = station.IsEncrypted; } } // See if profile is attached if (profile != null) { // Apply the modifier var modifier = profile.GetFilter(currentSettings.Source); if (modifier != null) { modifier.ApplyTo(currentSettings); } } // Report return currentSettings; })); }
/// <summary> /// Verändert die Daten einer Quelle gemäß der Konfiguration dieser Instanz. /// </summary> /// <param name="source">Die zu verändernde Quelle.</param> /// <exception cref="ArgumentNullException">Es wurde keine Quelle angegeben.</exception> public void ApplyTo( SourceInformation source ) { // Validate if (null == source) throw new ArgumentNullException( "source" ); // Blind apply - even if identifier would not match if (!string.IsNullOrEmpty( Name )) source.Name = Name; if (!string.IsNullOrEmpty( Provider )) source.Provider = Provider; if (IsEncrypted.HasValue) source.IsEncrypted = IsEncrypted.Value; if (IsService.HasValue) source.IsService = IsService.Value; if (VideoStream.HasValue) source.VideoStream = VideoStream.Value; if (VideoType.HasValue) source.VideoType = VideoType.Value; if (TextStream.HasValue) source.TextStream = TextStream.Value; // Audio if (null != AudioStreams) { // Wipe out source.AudioTracks.Clear(); // Add all foreach (AudioInformation audio in AudioStreams) source.AudioTracks.Add( audio.Clone() ); } // Subtitles if (null != SubtitlesStreams) { // Wipe out source.Subtitles.Clear(); // Add all foreach (SubtitleInformation sub in SubtitlesStreams) source.Subtitles.Add( sub.Clone() ); } }
/// <summary> /// Verändert die Daten einer Quelle gemäß der Konfiguration dieser Instanz. /// </summary> /// <param name="source">Die zu verändernde Quelle.</param> /// <exception cref="ArgumentNullException">Es wurde keine Quelle angegeben.</exception> public void ApplyTo(SourceInformation source) { // Validate if (null == source) { throw new ArgumentNullException("source"); } // Blind apply - even if identifier would not match if (!string.IsNullOrEmpty(Name)) { source.Name = Name; } if (!string.IsNullOrEmpty(Provider)) { source.Provider = Provider; } if (IsEncrypted.HasValue) { source.IsEncrypted = IsEncrypted.Value; } if (IsService.HasValue) { source.IsService = IsService.Value; } if (VideoStream.HasValue) { source.VideoStream = VideoStream.Value; } if (VideoType.HasValue) { source.VideoType = VideoType.Value; } if (TextStream.HasValue) { source.TextStream = TextStream.Value; } // Audio if (null != AudioStreams) { // Wipe out source.AudioTracks.Clear(); // Add all foreach (AudioInformation audio in AudioStreams) { source.AudioTracks.Add(audio.Clone()); } } // Subtitles if (null != SubtitlesStreams) { // Wipe out source.Subtitles.Clear(); // Add all foreach (SubtitleInformation sub in SubtitlesStreams) { source.Subtitles.Add(sub.Clone()); } } }
/// <summary> /// Prüft, ob sich an der Konfiguration der Quelle etwas verändert hat. /// </summary> /// <param name="information">Die zugehörige aktuelle Konfiguration der Quelle.</param> /// <returns>Gesetzt, wenn nun der Empfang aktiv ist.</returns> /// <exception cref="ArgumentNullException">Der angegebenen Konfiguration ist keine Quelle zugeordnet.</exception> /// <exception cref="ArgumentException">Die Konfiguration gehört zu einer anderen Quelle als der aktuellen.</exception> public bool RetestSourceInformation(SourceInformation information) { // Must get the current source information if (information == null) { // Close any active stream CloseStream(); // Not active return(false); } // Validate if (information.Source == null) { throw new ArgumentNullException("information.Source"); } if (!information.Source.Equals(Source)) { throw new ArgumentException(information.Source.ToString(), "information.Source"); } // Nothing changed var signature = CreateRecordingSignature(information); if (m_TransportStream != null) { // No need to switch if (signature.Equals(m_CurrentSignature)) { return(true); } // Report to see what happened if (!string.IsNullOrEmpty(m_CurrentSignature)) { try { // Report and ignore EventLog.WriteEntry("DVB.NET", $"Detected PSI Change: {m_CurrentSignature} => {signature}", EventLogEntryType.Information); } catch { } } } // Get the streaming data var streamTarget = StreamingTarget; // Stop the current recording CloseStream(); // Update all m_OriginalSettings = information; m_CurrentSignature = signature; // Fire notifications if (BeforeRecreateStream != null) { // Request new stream configuration var newSelection = BeforeRecreateStream(this); if (newSelection == null) { return(false); } // Remember StreamSelection = newSelection.Clone(); } // Forward CreateStream(NextStreamIdentifier, true); // Reactivate streaming if (streamTarget != null) { StreamingTarget = streamTarget; } // Did it return(true); }
/// <summary> /// Ermittelt anhand der aktuellen Konfiguration, welche Datenströme wie aufgezeichnet /// werden. Die gemeldete Zeichenkette hat keine weitere Bedeutung, nur dass sie zum /// schnellen Vergleich verwemdet werden kann. /// </summary> /// <param name="information">Die aktuellen Daten zur zugehörigen Quelle.</param> /// <returns>Ein Schlüssel passend zur aktuellen Aufzeichnung.</returns> private string CreateRecordingSignature(SourceInformation information) { // All keys - will be sorted to make sure that lists are ordered var flags = new List <int>(); // Various offset - PIDs are 12 Bit only! const int Offset_VideoType = 0x10000; const int Offset_VideoPID = 0x20000; const int Offset_MP2PID = 0x30000; const int Offset_AC3PID = 0x40000; const int Offset_TTXPID = 0x50000; const int Offset_SUBPID = 0x60000; // Video first if (0 != information.VideoStream) { // Get the type var videoType = (information.VideoType == VideoTypes.H264) ? EPG.StreamTypes.H264 : EPG.StreamTypes.Video13818; // Register flags.Add(Offset_VideoType + (int)videoType); flags.Add(Offset_VideoPID + (int)information.VideoStream); } // All MP2 audio foreach (var audio in information.AudioTracks) { if (AudioTypes.MP2 == audio.AudioType) { if (StreamSelection.MP2Tracks.LanguageMode == LanguageModes.Primary) { // Add as is flags.Add(Offset_MP2PID + (int)audio.AudioStream); // Finish break; } else if (StreamSelection.MP2Tracks.Contains(audio.Language)) { // Add as is flags.Add(Offset_MP2PID + (int)audio.AudioStream); } } } // All AC3 audio foreach (var audio in information.AudioTracks) { if (AudioTypes.AC3 == audio.AudioType) { if (StreamSelection.AC3Tracks.LanguageMode == LanguageModes.Primary) { // Add as is flags.Add(Offset_AC3PID + (int)audio.AudioStream); // Finish break; } else if (StreamSelection.AC3Tracks.Contains(audio.Language)) { // Add as is flags.Add(Offset_AC3PID + (int)audio.AudioStream); } } } // Videotext if (StreamSelection.Videotext) { if (0 != information.TextStream) { flags.Add(Offset_TTXPID + (int)information.TextStream); } } // Subtitle streams var subtitles = new Dictionary <ushort, bool>(); // Subtitles foreach (var subtitle in information.Subtitles) { if (StreamSelection.SubTitles.LanguageMode == LanguageModes.Primary) { // Add as is subtitles[subtitle.SubtitleStream] = true; // Finish break; } else if (StreamSelection.SubTitles.Contains(subtitle.Language)) { // Add as is subtitles[subtitle.SubtitleStream] = true; } } // Process all subtitles foreach (var subtitleStream in subtitles.Keys) { flags.Add(Offset_SUBPID + (int)subtitleStream); } // Create the key return((information.IsEncrypted ? "+" : "-") + string.Join(".", flags.ConvertAll(f => f.ToString("x5")).ToArray())); }
/// <summary> /// Übernimmt Informationen aus einer SI Programmbeschreibung in die Informationen /// eines Senders. /// </summary> /// <param name="source">Die Daten zu einer Quelle, die vervollständigt werden sollen.</param> /// <param name="program">Die SI Programmbeschreibung.</param> public static void Update(this SourceInformation source, EPG.ProgramEntry program) { // MPEG-2 Video if (EPG.StreamTypes.Video13818 == program.StreamType) { // Remember source.VideoType = VideoTypes.MPEG2; source.VideoStream = program.ElementaryPID; // Done return; } // H.264 Video if (EPG.StreamTypes.H264 == program.StreamType) { // Remember source.VideoType = VideoTypes.H264; source.VideoStream = program.ElementaryPID; // Done return; } // MP2 Audio if ((EPG.StreamTypes.Audio11172 == program.StreamType) || (EPG.StreamTypes.Audio13818 == program.StreamType)) { // Create new entry AudioInformation audio = new AudioInformation { AudioType = AudioTypes.MP2, AudioStream = program.ElementaryPID, Language = program.ProgrammeName.Trim() }; // Remember it source.AudioTracks.Add(audio); // Done return; } // AC3, TTX or DVB subtitles if (EPG.StreamTypes.PrivateData != program.StreamType) { return; } // Direct processing of descriptor list foreach (var descriptor in program.Descriptors) { // Check for AC3 var ac3 = descriptor as EPG.Descriptors.AC3; if (null != ac3) { // Create new entry AudioInformation audio = new AudioInformation { AudioType = AudioTypes.AC3, AudioStream = program.ElementaryPID, Language = program.ProgrammeName.Trim() }; // Remember it source.AudioTracks.Add(audio); // Done return; } // Check for videotext var ttx = descriptor as EPG.Descriptors.Teletext; if (null != ttx) { // Remember source.TextStream = program.ElementaryPID; // Done return; } // Check for DVB sub-titles var sub = descriptor as EPG.Descriptors.Subtitle; if (null != sub) { // Process all items foreach (var subTitle in sub.Subtitles) { // Create the information var info = new SubtitleInformation { SubtitleStream = program.ElementaryPID, Language = subTitle.Language, SubtitleType = (SubtitleTypes)subTitle.Type, CompositionPage = subTitle.CompositionPage, AncillaryPage = subTitle.AncillaryPage }; // Remember source.Subtitles.Add(info); } // Done return; } } }