396 lines
11 KiB
C#
396 lines
11 KiB
C#
using skyscraper5.Mpeg2;
|
|
using skyscraper5.Skyscraper.Scraper;
|
|
using skyscraper5.Skyscraper.Scraper.Storage;
|
|
using skyscraper5.UI.Overrides;
|
|
using skyscraper5.UI.StreamAcquisition;
|
|
using skyscraper8.Skyscraper.Scraper.Storage;
|
|
|
|
namespace skyscraper5.UI
|
|
{
|
|
public partial class Form1 : Form, INodeEngine
|
|
{
|
|
public Form1()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
public Form1(StreamSource acquiredStream)
|
|
: this()
|
|
{
|
|
AcquiredStream = acquiredStream;
|
|
tsContext = new TsContext();
|
|
tsContext.OnPacketLoss += TsContext_OnPacketLoss;
|
|
skyscraperContext = new SkyscraperContext(tsContext, dataStorage, objectStorage);
|
|
skyscraperContext.UiJunction = uiJunction;
|
|
}
|
|
|
|
private void TsContext_OnPacketLoss(uint pid, uint oldContinuity, uint newContinuity, long packetLossEvents)
|
|
{
|
|
continuityErrors = packetLossEvents;
|
|
}
|
|
|
|
public StreamSource AcquiredStream { get; }
|
|
private TsContext tsContext;
|
|
private SkyscraperContext skyscraperContext;
|
|
|
|
private DataStorage dataStorage;
|
|
private ObjectStorage objectStorage;
|
|
|
|
private Form1UiJunction uiJunction;
|
|
private DateTime windowOpenedTimestamp;
|
|
private TimeSpan windowOpenDuration;
|
|
private long continuityErrors;
|
|
private TeiCounter teiCounterFilter;
|
|
private PmtCounter pmtCounter;
|
|
private PacketsPerSecondCounter packetsPerSecondCounter;
|
|
private ScrambleCounter scrambleCounter;
|
|
|
|
|
|
private void Form1_Load(object sender, EventArgs e)
|
|
{
|
|
scrambleCounter = new ScrambleCounter();
|
|
packetsPerSecondCounter = new PacketsPerSecondCounter();
|
|
teiCounterFilter = new TeiCounter();
|
|
skyscraperContext.InitalizeFilterChain(teiCounterFilter, packetsPerSecondCounter, scrambleCounter);
|
|
|
|
uiJunction = new Form1UiJunction(this);
|
|
skyscraperContext.UiJunction = uiJunction;
|
|
|
|
|
|
windowOpenedTimestamp = DateTime.Now;
|
|
windowOpenDuration = new TimeSpan(0, 0, 0);
|
|
bottomLeft.Items[0].SubItems[1].Text = AcquiredStream.GetSourceName();
|
|
|
|
skyscraperContext.PatDecoder.OnValidSection += PsiDecoder_OnValidSection;
|
|
skyscraperContext.PatDecoder.OnCrcError += PsiDecoder_OnCrcError;
|
|
pmtCounter = new PmtCounter();
|
|
skyscraperContext.PmtDecoderTransformer = pmtCounter;
|
|
skyscraperContext.CatDecoder.OnValidSection += PsiDecoder_OnValidSection;
|
|
skyscraperContext.CatDecoder.OnCrcError += PsiDecoder_OnCrcError;
|
|
skyscraperContext.NitDecoder.OnValidSection += PsiDecoder_OnValidSection;
|
|
skyscraperContext.NitDecoder.OnCrcError += PsiDecoder_OnCrcError;
|
|
skyscraperContext.Pid0x11Decoder.OnValidSection += PsiDecoder_OnValidSection;
|
|
skyscraperContext.Pid0x11Decoder.OnCrcError += PsiDecoder_OnCrcError;
|
|
skyscraperContext.Pid0x12Decoder.OnValidSection += PsiDecoder_OnValidSection;
|
|
skyscraperContext.Pid0x12Decoder.OnCrcError += PsiDecoder_OnCrcError;
|
|
DrawBottomCenter();
|
|
|
|
pidListView_ColumnClick(this, new ColumnClickEventArgs(0));
|
|
|
|
AcquiredStream.StartSource(skyscraperContext);
|
|
}
|
|
|
|
private long patErrors, catErrors, nitErrors, sdtErrors, batErrors, eitErrors;
|
|
private void PsiDecoder_OnCrcError(PsiSection section, int pid, long available)
|
|
{
|
|
switch (section.TableId)
|
|
{
|
|
case 0: patErrors++; break;
|
|
case 1: catErrors++; break;
|
|
case 0x40: nitErrors++; break;
|
|
case 0x41: nitErrors++; break;
|
|
case 0x42: sdtErrors++; break;
|
|
case 0x46: sdtErrors++; break;
|
|
case 0x4a: batErrors++; break;
|
|
default:
|
|
if (section.TableId >= 0x4e && section.TableId <= 0x6f)
|
|
{
|
|
eitErrors++;
|
|
break;
|
|
}
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private long patValids, catValids, nitValids, sdtValids, batValids, eitValids;
|
|
private void PsiDecoder_OnValidSection(PsiSection section, int pid, long available)
|
|
{
|
|
switch (section.TableId)
|
|
{
|
|
case 0: patValids++; break;
|
|
case 1: catValids++; break;
|
|
case 0x40: nitValids++; break;
|
|
case 0x41: nitValids++; break;
|
|
case 0x42: sdtValids++; break;
|
|
case 0x46: sdtValids++; break;
|
|
case 0x4a: batValids++; break;
|
|
default:
|
|
if (section.TableId >= 0x4e && section.TableId <= 0x6f)
|
|
{
|
|
eitValids++;
|
|
break;
|
|
}
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
|
|
{
|
|
AcquiredStream.StopSource();
|
|
}
|
|
|
|
private void timer1_Tick(object sender, EventArgs e)
|
|
{
|
|
windowOpenDuration = windowOpenDuration.Add(new TimeSpan(0, 0, 1));
|
|
bottomLeft.Items[1].SubItems[1].Text = windowOpenDuration.ToString();
|
|
DrawBottomCenter();
|
|
RenderPidList();
|
|
bottomLeft.Items[2].SubItems[1].Text = AcquiredStream.State.ToString();
|
|
}
|
|
|
|
private string bottomCenterTemplate;
|
|
private void DrawBottomCenter()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(bottomCenterTemplate))
|
|
{
|
|
bottomCenterTemplate = bottomCenter.Text;
|
|
}
|
|
|
|
long patSections = patValids;
|
|
long pmtSections = pmtCounter.ValidSection; ;
|
|
long catSections = catValids;
|
|
long nitSections = nitValids;
|
|
long sdtSections = sdtValids;
|
|
long eitSections = eitValids;
|
|
long patCrcErrors = patErrors;
|
|
long pmtCrcErrors = pmtCounter.CrcErrors;
|
|
long catCrcErrors = catErrors;
|
|
long nitCrcErrors = nitErrors;
|
|
long sdtCrcErrors = sdtErrors;
|
|
long eitCrcErrors = eitErrors;
|
|
long continuityErrors = this.continuityErrors;
|
|
string muxBitrate = "???";
|
|
long teiErrors = this.teiCounterFilter.TransportErrors;
|
|
string lastSec = RenderPacketsPerSecond();
|
|
long syncLosses = 0;
|
|
long inBuffer = 0;
|
|
long outBuffer = 0;
|
|
|
|
bottomCenter.Text = String.Format(bottomCenterTemplate, patSections, pmtSections, catSections, nitSections, sdtSections, eitSections, patCrcErrors,
|
|
pmtCrcErrors, catCrcErrors, nitCrcErrors, sdtCrcErrors, eitCrcErrors, continuityErrors, muxBitrate, teiErrors, lastSec, syncLosses, inBuffer, outBuffer);
|
|
}
|
|
|
|
private char[] dataUnits = { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' };
|
|
private string RenderPacketsPerSecond()
|
|
{
|
|
if (!packetsPerSecondCounter.Ready)
|
|
{
|
|
return "???";
|
|
}
|
|
|
|
long packetsPerSecond = packetsPerSecondCounter.PacketsPerSecond;
|
|
long bytesPerSecond = packetsPerSecond * 188;
|
|
long bitsPerSecond = bytesPerSecond * 8;
|
|
if (bitsPerSecond < 1000)
|
|
{
|
|
return String.Format("{0} bits/s", bytesPerSecond);
|
|
}
|
|
|
|
int unitOffset = -1;
|
|
while (bitsPerSecond > 1000)
|
|
{
|
|
bitsPerSecond /= 1000;
|
|
unitOffset++;
|
|
}
|
|
return String.Format("{0} {1}bits/s", bitsPerSecond, dataUnits[unitOffset]);
|
|
}
|
|
|
|
private PidListViewItem[] listViewItems;
|
|
private Color scrambledColor = Color.FromArgb(255, 255, 128, 128);
|
|
private Color unscrambledColor = Color.FromArgb(255, 128, 255, 128);
|
|
private void RenderPidList()
|
|
{
|
|
if (listViewItems == null)
|
|
listViewItems = new PidListViewItem[0x2000];
|
|
|
|
ulong[] ulongs = tsContext.GetPidStatistics();
|
|
for (int i = 0; i < 0x2000; i++)
|
|
{
|
|
if (ulongs[i] != 0)
|
|
{
|
|
if (listViewItems[i] == null)
|
|
{
|
|
listViewItems[i] = new PidListViewItem(i);
|
|
pidListView.Items.Add(listViewItems[i]);
|
|
}
|
|
listViewItems[i].SetPackageStats(ulongs[i], packetsPerSecondCounter.TotalPacketsOverall);
|
|
listViewItems[i].BackColor = scrambleCounter.IsScrambled(i) ? scrambledColor : unscrambledColor;
|
|
}
|
|
|
|
if (uiJunction.PidToPrograms != null && uiJunction.SdtNames != null)
|
|
{
|
|
if (uiJunction.PidToPrograms.ContainsKey(i))
|
|
{
|
|
uint program = uiJunction.PidToPrograms[i];
|
|
if (uiJunction.SdtNames.ContainsKey(program))
|
|
{
|
|
listViewItems[i].ProgramName = uiJunction.SdtNames[program];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uiJunction.UsageLabels != null)
|
|
{
|
|
if (uiJunction.UsageLabels.ContainsKey(i))
|
|
{
|
|
if (listViewItems[i] != null)
|
|
{
|
|
listViewItems[i].UsageLabel = uiJunction.UsageLabels[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private PidListViewItemSorter lvwColumnSorter;
|
|
private void pidListView_ColumnClick(object sender, ColumnClickEventArgs e)
|
|
{
|
|
if (lvwColumnSorter == null)
|
|
{
|
|
lvwColumnSorter = new PidListViewItemSorter();
|
|
pidListView.ListViewItemSorter = lvwColumnSorter;
|
|
}
|
|
|
|
if (e.Column == lvwColumnSorter.SortColumn)
|
|
{
|
|
if (lvwColumnSorter.Order == SortOrder.Ascending)
|
|
{
|
|
lvwColumnSorter.Order = SortOrder.Descending;
|
|
}
|
|
else
|
|
{
|
|
lvwColumnSorter.Order = SortOrder.Ascending;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set the column number that is to be sorted; default to ascending.
|
|
lvwColumnSorter.SortColumn = e.Column;
|
|
lvwColumnSorter.Order = SortOrder.Ascending;
|
|
}
|
|
|
|
pidListView.Sort();
|
|
}
|
|
|
|
SkyscraperUiNode INodeEngine.EnsureNodeExists(params string[] path)
|
|
{
|
|
SkyscraperUiNode result = null;
|
|
if (IsDisposed)
|
|
return null;
|
|
Invoke(new Action(() => { result = EnsureNodeExistsEx(path); }));
|
|
return result;
|
|
}
|
|
|
|
private SkyscraperUiNode EnsureNodeExistsEx(params string[] path)
|
|
{
|
|
TreeNodeCollection rootNodes = treeView1.Nodes;
|
|
|
|
SkyscraperUiNode parent = null;
|
|
for (int i = 0; i < path.Length; i++)
|
|
{
|
|
SkyscraperUiNode foundNode = null;
|
|
if (i == 0)
|
|
{
|
|
foreach (TreeNode rootNode in rootNodes)
|
|
{
|
|
if (rootNode.Text.Equals(path[i]))
|
|
{
|
|
foundNode = (SkyscraperUiNode)rootNode;
|
|
break;
|
|
}
|
|
}
|
|
if (foundNode == null)
|
|
{
|
|
foundNode = new SkyscraperUiNode();
|
|
foundNode.Text = path[i];
|
|
rootNodes.Add(foundNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (TreeNode childNode in parent.Nodes)
|
|
{
|
|
if (childNode.Text.Equals(path[i]))
|
|
{
|
|
foundNode = (SkyscraperUiNode)childNode;
|
|
break;
|
|
}
|
|
}
|
|
if (foundNode == null)
|
|
{
|
|
foundNode = new SkyscraperUiNode();
|
|
foundNode.Text = path[i];
|
|
parent.Nodes.Add(foundNode);
|
|
}
|
|
}
|
|
parent = foundNode;
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
public void RunOnUiThread(Action action)
|
|
{
|
|
Invoke(action);
|
|
}
|
|
|
|
private void treeView1_AfterSelect_1(object sender, TreeViewEventArgs e)
|
|
{
|
|
SkyscraperUiNode node = e.Node as SkyscraperUiNode;
|
|
if (node == null)
|
|
return;
|
|
textBox1.Text = node.DetailedText;
|
|
}
|
|
|
|
public bool TestForNode(params string[] path)
|
|
{
|
|
TreeNodeCollection rootNodes = treeView1.Nodes;
|
|
|
|
SkyscraperUiNode parent = null;
|
|
for (int i = 0; i < path.Length; i++)
|
|
{
|
|
SkyscraperUiNode foundNode = null;
|
|
if (i == 0)
|
|
{
|
|
foreach (TreeNode rootNode in rootNodes)
|
|
{
|
|
if (rootNode.Text.Equals(path[i]))
|
|
{
|
|
foundNode = (SkyscraperUiNode)rootNode;
|
|
break;
|
|
}
|
|
}
|
|
if (foundNode == null)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (TreeNode childNode in parent.Nodes)
|
|
{
|
|
if (childNode.Text.Equals(path[i]))
|
|
{
|
|
foundNode = (SkyscraperUiNode)childNode;
|
|
break;
|
|
}
|
|
}
|
|
if (foundNode == null)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
parent = foundNode;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void INodeEngine.SetIcon(SkyscraperUiNode node, int index)
|
|
{
|
|
RunOnUiThread(() => {
|
|
node.ImageIndex = index;
|
|
node.SelectedImageIndex = index;
|
|
});
|
|
}
|
|
}
|
|
}
|