/// <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>
        /// Beginnt die Aufzeichnung in einen <i>Transport Stream</i> - optional als
        /// Datei.
        /// </summary>
        /// <param name="nextPID">Die erste Datenstromkennung (PID), die in der Aufzeichnungsdatei verwendet werden darf.</param>
        /// <param name="recreate">Gesetzt, wenn ein Neustart aufgrund veränderter Nutzdatenströme erforderlich wurde.</param>
        /// <exception cref="ArgumentException">Eine Aufzeichnung der angegebenen Quelle ist nicht möglich.</exception>
        private void CreateStream(short nextPID, bool recreate)
        {
            // Try to get the full name
            var filePath = m_OriginalPath;

            if (filePath != null)
            {
                if (m_FileCount > 0)
                {
                    // Split off the parts
                    var name = Path.GetFileNameWithoutExtension(filePath);
                    var dir  = Path.GetDirectoryName(filePath);
                    var ext  = Path.GetExtension(filePath);

                    // Construct new name
                    filePath = Path.Combine(dir, $"{name} - {m_FileCount}{ext}");
                }
            }

            // Try to decrypt
            if (m_Decrypting = m_OriginalSettings.IsEncrypted)
            {
                try
                {
                    // Process
                    Hardware.Decrypt(Source);
                }
                catch
                {
                    // Ignore any error
                    m_Decrypting = false;
                }
            }

            // Type of the video
            EPG.StreamTypes?videoType;

            // Video first
            if (m_OriginalSettings.VideoStream == 0)
            {
                videoType = null;
            }
            else
            {
                videoType = (m_OriginalSettings.VideoType == VideoTypes.H264) ? EPG.StreamTypes.H264 : EPG.StreamTypes.Video13818;
            }

            // Get the buffer size
            var bufferSize = (FileBufferSizeChooser == null) ? null : FileBufferSizeChooser(videoType);

            // Create the new stream
            m_TransportStream = new Manager(filePath, nextPID, bufferSize.GetValueOrDefault(Manager.DefaultBufferSize));

            // Attach PCR sink
            m_TransportStream.OnWritingPCR = m_WritePCRSink;

            // Report of the actually selected streams
            StreamSelection result = new StreamSelection();

            // Cleanup on any error
            try
            {
                // Video first
                if (videoType.HasValue)
                {
                    AddConsumer(m_OriginalSettings.VideoStream, StreamTypes.Video, m_TransportStream.AddVideo((byte)videoType.Value));
                }

                // Select audio
                ProcessAudioSelection(AudioTypes.MP2, result.MP2Tracks, StreamSelection.MP2Tracks);
                ProcessAudioSelection(AudioTypes.AC3, result.AC3Tracks, StreamSelection.AC3Tracks);

                // Videotext
                if (StreamSelection.Videotext)
                {
                    if (0 != m_OriginalSettings.TextStream)
                    {
                        // Register
                        AddConsumer(m_OriginalSettings.TextStream, StreamTypes.VideoText, m_TransportStream.AddTeleText());

                        // Remember
                        result.Videotext = true;
                    }
                }

                // Subtitle streams
                var subtitles = new Dictionary <ushort, List <EPG.SubtitleInfo> >();

                // Preset mode
                result.SubTitles.LanguageMode = LanguageModes.All;

                // All audio
                // Subtitles
                foreach (var subtitle in m_OriginalSettings.Subtitles)
                {
                    // Check for primary
                    if (StreamSelection.SubTitles.LanguageMode == LanguageModes.Primary)
                    {
                        // Attach to the list
                        AddSubtitleInformation(subtitle, subtitles);

                        // Copy over
                        result.SubTitles.LanguageMode = LanguageModes.Primary;

                        // Remember
                        result.SubTitles.Languages.Add(subtitle.Language);

                        // Done
                        break;
                    }

                    // Standard selection
                    if (StreamSelection.SubTitles.Contains(subtitle.Language))
                    {
                        // Attach to the list
                        AddSubtitleInformation(subtitle, subtitles);

                        // Remember
                        result.SubTitles.Languages.Add(subtitle.Language);
                    }
                    else
                    {
                        // At least one is excluded
                        result.SubTitles.LanguageMode = LanguageModes.Selection;
                    }
                }

                // Clear flag if no audio is used
                if (LanguageModes.All == result.SubTitles.LanguageMode)
                {
                    if (result.SubTitles.Languages.Count < 1)
                    {
                        result.SubTitles.LanguageMode = LanguageModes.Selection;
                    }
                }

                // Process all subtitles
                foreach (var current in subtitles)
                {
                    AddConsumer(current.Key, StreamTypes.SubTitle, m_TransportStream.AddSubtitles(current.Value.ToArray()));
                }

                // See if program guide is requested
                bool epg = StreamSelection.ProgramGuide;

                // May want to disable
                if (epg)
                {
                    if ((Hardware.Profile != null) && Hardware.Profile.DisableProgramGuide)
                    {
                        epg = false;
                    }
                    else if (Profile != null)
                    {
                        if (Profile.GetFilter(Source).DisableProgramGuide)
                        {
                            epg = false;
                        }
                    }
                }

                // EPG
                if (epg)
                {
                    // Activate dispatch
                    m_TransportStream.SetEPGMapping(Source.Network, Source.TransportStream, Source.Service);

                    // Start it
                    Hardware.AddProgramGuideConsumer(DispatchEPG);

                    // Remember
                    result.ProgramGuide = true;
                }

                // Counter
                int started = 0;
                try
                {
                    // Start all
                    foreach (var consumer in m_Consumers)
                    {
                        try
                        {
                            // Forward
                            Hardware.SetConsumerState(consumer, true);

                            // Count it
                            ++started;
                        }
                        catch (OutOfConsumersException)
                        {
                            // Translate
                            throw new OutOfConsumersException(m_Consumers.Count, started)
                                  {
                                      RequestedSelection = result
                                  };
                        }
                    }
                }
                catch
                {
                    // Detach EPG
                    Hardware.RemoveProgramGuideConsumer(DispatchEPG);

                    // Cleanup on all errors
                    foreach (var consumer in m_Consumers)
                    {
                        try
                        {
                            // Remove
                            Hardware.SetConsumerState(consumer, null);
                        }
                        catch
                        {
                            // Ignore any error
                        }
                    }

                    // Forward
                    throw;
                }

                // Remember path
                if (filePath != null)
                {
                    m_AllFiles.Add(new FileStreamInformation {
                        FilePath = filePath, VideoType = m_OriginalSettings.VideoType
                    });
                }

                // Remember the time
                LastActivationTime = DateTime.UtcNow;
            }
            catch
            {
                // Simply forget
                m_TransportStream.Dispose();
                m_TransportStream = null;

                // Forward
                throw;
            }

            // Get the next free PID
            NextStreamIdentifier = m_TransportStream.NextPID;

            // Remember the streams we use
            ActiveSelection = result;

            // Attach to clients
            var createNotify = OnCreatedStream;

            // Report
            if (createNotify != null)
            {
                createNotify(this);
            }
        }