Import initial code
This commit is contained in:
commit
787949aea0
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
16
pcap2mpe.sln
Normal file
16
pcap2mpe.sln
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pcap2mpe", "pcap2mpe\pcap2mpe.csproj", "{60779B2D-8351-4588-A6EA-6767677244C2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{60779B2D-8351-4588-A6EA-6767677244C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{60779B2D-8351-4588-A6EA-6767677244C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{60779B2D-8351-4588-A6EA-6767677244C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{60779B2D-8351-4588-A6EA-6767677244C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
12
pcap2mpe/DateTimeExtensions.cs
Normal file
12
pcap2mpe/DateTimeExtensions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public static class DateTimeExtensions
|
||||
{
|
||||
public static long ToUnixTime(this DateTime dt)
|
||||
{
|
||||
double totalSeconds = dt.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
long tv = (long)totalSeconds;
|
||||
return tv;
|
||||
}
|
||||
|
||||
}
|
||||
98
pcap2mpe/DismantledPacket.cs
Normal file
98
pcap2mpe/DismantledPacket.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System.Net.NetworkInformation;
|
||||
using SharpPcap;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class DismantledPacket
|
||||
{
|
||||
public static DismantledPacket Dismantle(byte[] packetCapture)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream(packetCapture, false);
|
||||
DismantledPacket result = new DismantledPacket();
|
||||
result.Destination = ms.ReadMacAddress();
|
||||
result.Source = ms.ReadMacAddress();
|
||||
ushort union = ms.ReadUInt16BigEndian();
|
||||
if (union <= 1500)
|
||||
{
|
||||
result.LlcSnap = true;
|
||||
result.Payload = ms.ReadByteArray(union);
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort ethertype = union;
|
||||
bool moreToRead = true;
|
||||
while (moreToRead)
|
||||
{
|
||||
moreToRead = false;
|
||||
switch (ethertype)
|
||||
{
|
||||
case 0x0800: //IPv4
|
||||
result.Payload = ms.ReadRemainderAsBytes();
|
||||
break;
|
||||
case 0x0806: //ARP
|
||||
//MPE can only handle IP and LLC/SNAP, so unfortunately, we've got to discard these.
|
||||
result.Discard = true;
|
||||
break;
|
||||
case 0x8100: //VLAN
|
||||
if (result.VlanId != 0)
|
||||
throw new NotImplementedException("nested VLAN");
|
||||
result.VlanId = ms.ReadUInt16BigEndian() & 0x0fff;
|
||||
ethertype = ms.ReadUInt16BigEndian();
|
||||
if (ethertype <= 1500)
|
||||
{
|
||||
result.LlcSnap = true;
|
||||
result.Payload = ms.ReadByteArray(ethertype);
|
||||
moreToRead = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
moreToRead = true;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
long llcSize = ms.GetAvailableBytes();
|
||||
llcSize += 8;
|
||||
if (llcSize >= 1500)
|
||||
{
|
||||
//Drop oversized frames
|
||||
continue;
|
||||
}
|
||||
result.LlcSnap = true;
|
||||
result.Payload = BuildLlcSnapFrame(ms.ReadRemainderAsBytes(), ethertype);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] BuildLlcSnapFrame(byte[] packetCapture, ushort ethertype)
|
||||
{
|
||||
byte[] etherTypeBytes = BitConverter.GetBytes(ethertype);
|
||||
|
||||
byte[] buffer = new byte[packetCapture.Length + 8];
|
||||
buffer[0] = 0xaa;
|
||||
buffer[1] = 0xaa;
|
||||
buffer[2] = 0x03;
|
||||
buffer[3] = 0x00;
|
||||
buffer[4] = 0x00;
|
||||
buffer[5] = 0x00;
|
||||
buffer[6] = etherTypeBytes[1];
|
||||
buffer[7] = etherTypeBytes[0];
|
||||
Array.Copy(packetCapture,0,buffer,8,packetCapture.Length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int VlanId { get; set; }
|
||||
|
||||
public bool Discard { get; set; }
|
||||
|
||||
public byte[] Payload { get; set; }
|
||||
|
||||
public bool LlcSnap { get; set; }
|
||||
|
||||
public PhysicalAddress Source { get; set; }
|
||||
|
||||
public PhysicalAddress Destination { get; set; }
|
||||
}
|
||||
141
pcap2mpe/DvbCrc32.cs
Normal file
141
pcap2mpe/DvbCrc32.cs
Normal file
@ -0,0 +1,141 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class DvbCrc32
|
||||
{
|
||||
private DvbCrc32()
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly uint[] table =
|
||||
{
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
|
||||
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
||||
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
|
||||
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
|
||||
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
|
||||
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
|
||||
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
||||
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
|
||||
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
|
||||
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
||||
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
|
||||
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
|
||||
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
||||
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
|
||||
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
|
||||
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
|
||||
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
|
||||
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
|
||||
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
|
||||
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
|
||||
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
|
||||
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
|
||||
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
|
||||
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
|
||||
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
|
||||
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
|
||||
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
|
||||
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
|
||||
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
|
||||
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
|
||||
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
|
||||
public static bool ValidateCrc(MemoryStream ms, int offset, int end)
|
||||
{
|
||||
long restorePosition = ms.Position;
|
||||
|
||||
uint crc = 0xffffffff;
|
||||
while (offset < end)
|
||||
{
|
||||
ms.Position = offset;
|
||||
uint b = (crc >> 24) & 0xff;
|
||||
int c = (ms.ReadUInt8()) & 0xff;
|
||||
crc = (crc << 8) ^ table[b ^ c];
|
||||
offset++;
|
||||
}
|
||||
|
||||
ms.Position = restorePosition;
|
||||
return crc == 0;
|
||||
}
|
||||
|
||||
public static bool ValidateCrc(ReadOnlySpan<byte> data)
|
||||
{
|
||||
uint crc = 0xffffffff;
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
uint b = (crc >> 24) & 0xff;
|
||||
int c = (data[i]) & 0xff;
|
||||
crc = (crc << 8) ^ table[b ^ c];
|
||||
}
|
||||
|
||||
return crc == 0;
|
||||
}
|
||||
|
||||
public static uint CreateCrc(ReadOnlySpan<byte> data)
|
||||
{
|
||||
uint crc = 0xffffffff;
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
uint b = (crc >> 24) & 0xff;
|
||||
int c = (data[i]) & 0xff;
|
||||
crc = (crc << 8) ^ table[b ^ c];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
public static uint CreateCrc(MemoryStream ms, int offset, int end)
|
||||
{
|
||||
long restorePosition = ms.Position;
|
||||
|
||||
uint crc = 0xffffffff;
|
||||
while (offset < end)
|
||||
{
|
||||
ms.Position = offset;
|
||||
uint b = (crc >> 24) & 0xff;
|
||||
int c = (ms.ReadUInt8()) & 0xff;
|
||||
crc = (crc << 8) ^ table[b ^ c];
|
||||
offset++;
|
||||
}
|
||||
|
||||
ms.Position = restorePosition;
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
54
pcap2mpe/HOW_TO_USE.md
Normal file
54
pcap2mpe/HOW_TO_USE.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Running single nodedly
|
||||
|
||||
tsp -v --bitrate 133333 \
|
||||
-I null \
|
||||
-P regulate --packet-burst 14 \
|
||||
-P filter --every 133 --set-label 1 \
|
||||
-P craft --only-label 1 --pid 0x0098 --no-payload --pcr 0 \
|
||||
-P continuity --pid 0x0098 --fix \
|
||||
-P pcradjust --pid 0x0098 \
|
||||
-P merge --transparent "tsp -I ip -l 127.0.0.2 6969" \
|
||||
-P history \
|
||||
-O file --max-size 100000000 sampleC.ts
|
||||
|
||||
# Building for Alpine
|
||||
dotnet publish -c Release --self-contained -r linux-musl-x64
|
||||
|
||||
|
||||
# Building for Alpine on Banana Pi
|
||||
dotnet publish -c Release --self-contained -r linux-musl-arm64
|
||||
|
||||
# TDT example
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tsduck>
|
||||
<TDT UTC_time="1984-01-01 00:13:37"/>
|
||||
</tsduck>
|
||||
|
||||
<?xml version=\"1.0\" encoding=\"UTF-8\"?><tsduck><TDT UTC_time=\"1984-01-01 00:13:37\"/></tsduck>
|
||||
|
||||
# Multis
|
||||
tsp -v --bitrate 230400 \
|
||||
-I null \
|
||||
-P regulate --packet-burst 14 \
|
||||
-P filter --every 230 --set-label 1 \
|
||||
-P craft --only-label 1 --pid 0x0098 --no-payload --pcr 0 \
|
||||
-P continuity --pid 0x0098 --fix \
|
||||
-P pcradjust --pid 0x0098 \
|
||||
-P merge --transparent "tsp -I ip -l 127.0.0.2 6969" \
|
||||
-P pmt --pmt-pid 0x0099 --add-stream-identifier \
|
||||
-P sdt -c --service-id 1 --name "pc-203" --type 0x0C \
|
||||
-P merge "tsp -v -I ip -l 192.168.1.197 6969 -P zap 1 -P svrename --id 2 1 -P remap 0x0098-0x0106=0x0198" \
|
||||
-P pmt --pmt-pid 0x0199 --remove-pid 0x0198 --pcr-pid 0x0098 --add-stream-identifier \
|
||||
-P sdt -c --service-id 2 --name "fsoca" --type 0x0C \
|
||||
-P merge "tsp -v -I ip -l 192.168.1.197 6970 -P zap 1 -P svrename --id 3 1 -P remap 0x0098-0x0106=0x0298" \
|
||||
-P pmt --pmt-pid 0x0299 --remove-pid 0x0298 --pcr-pid 0x0098 --add-stream-identifier \
|
||||
-P sdt -c --service-id 3 --name "fsaca" --type 0x0C \
|
||||
-P merge "tsp -v -I ip -l 192.168.1.197 6971 -P zap 1 -P svrename --id 4 1 -P remap 0x0098-0x0106=0x0398" \
|
||||
-P pmt --pmt-pid 0x0399 --remove-pid 0x0398 --pcr-pid 0x0098 --add-stream-identifier \
|
||||
-P sdt -c --service-id 4 --name "rwwgw1" --type 0x0C \
|
||||
-P inject "<?xml version=\"1.0\" encoding=\"UTF-8\"?><tsduck><TDT UTC_time=\"1984-01-01 00:13:37\"/></tsduck>" --pid 0x14 --bitrate 2000 --stuffing \
|
||||
-P timeref --system-synchronous \
|
||||
-P cat -c \
|
||||
-P nit -c --build-service-list-descriptors \
|
||||
-P history \
|
||||
-O file --max-size 100000000 multisampleBigB.ts
|
||||
34
pcap2mpe/Mpeg2WriterFactory.cs
Normal file
34
pcap2mpe/Mpeg2WriterFactory.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Net;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class Mpeg2WriterFactory
|
||||
{
|
||||
public static Mpeg2Writer CreateWriter()
|
||||
{
|
||||
|
||||
string[] commandLineArgs = Environment.GetCommandLineArgs();
|
||||
if (commandLineArgs.Length == 2 || commandLineArgs.Length == 1 || commandLineArgs.Length == 0)
|
||||
{
|
||||
Mpeg2UdpSender udpSender = new Mpeg2UdpSender(new IPEndPoint(IPAddress.Parse("127.0.0.2"), 6969));
|
||||
return udpSender;
|
||||
}
|
||||
|
||||
switch (commandLineArgs[2])
|
||||
{
|
||||
case "file":
|
||||
string filename = String.Format("cap{0}.ts", DateTime.Now.Ticks);
|
||||
FileInfo fi = new FileInfo(filename);
|
||||
Console.WriteLine(fi.FullName);
|
||||
return new Mpeg2FileWriter(fi);
|
||||
case "udp":
|
||||
IPAddress ip = IPAddress.Parse((commandLineArgs[3]));
|
||||
int port = int.Parse(commandLineArgs[4]);
|
||||
IPEndPoint endPoint = new IPEndPoint(ip, port);
|
||||
Mpeg2UdpSender udpSender = new Mpeg2UdpSender(endPoint);
|
||||
return udpSender;
|
||||
default:
|
||||
throw new NotImplementedException(commandLineArgs[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
70
pcap2mpe/PacketCounter.cs
Normal file
70
pcap2mpe/PacketCounter.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System.Text;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class PacketCounter
|
||||
{
|
||||
private PacketCounter() { }
|
||||
|
||||
private static PacketCounter _instance;
|
||||
public static PacketCounter GetInstance()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new PacketCounter();
|
||||
_instance.ipThisSecond = new int[4096];
|
||||
_instance.llcThisSecond = new int[4096];
|
||||
|
||||
_instance._thread = new Thread(_instance.Run);
|
||||
_instance._thread.Name = "Packet Counter";
|
||||
_instance._thread.Priority = ThreadPriority.Lowest;
|
||||
_instance._thread.Start();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public void Count(int vlan, bool llc)
|
||||
{
|
||||
if (llc)
|
||||
{
|
||||
llcThisSecond[vlan]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ipThisSecond[vlan]++;
|
||||
}
|
||||
}
|
||||
|
||||
private Thread _thread;
|
||||
private int[] ipThisSecond;
|
||||
private int[] llcThisSecond;
|
||||
|
||||
private void Run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
int hits = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendFormat("({0}) ", DateTime.Now.ToLongTimeString());
|
||||
for (int i = 0; i < ipThisSecond.Length; i++)
|
||||
{
|
||||
if (ipThisSecond[i] > 0)
|
||||
{
|
||||
sb.AppendFormat("VLAN {0} IP: {1}, ", i, ipThisSecond[i]);
|
||||
hits++;
|
||||
}
|
||||
|
||||
if (llcThisSecond[i] > 0)
|
||||
{
|
||||
sb.AppendFormat("VLAN {0} LLC: {1}, ", i, llcThisSecond[i]);
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
if (hits > 0)
|
||||
Console.WriteLine(sb);
|
||||
Array.Clear(ipThisSecond, 0, ipThisSecond.Length);
|
||||
Array.Clear(llcThisSecond,0, llcThisSecond.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
114
pcap2mpe/PacketHandlerQueue.cs
Normal file
114
pcap2mpe/PacketHandlerQueue.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using pcap2mpe.Descriptors;
|
||||
using SharpPcap;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class PacketHandlerQueue
|
||||
{
|
||||
public PacketHandlerQueue()
|
||||
{
|
||||
threadStateLocker = new object();
|
||||
queue = new Queue<byte[]>();
|
||||
knownVlans = new bool[4096];
|
||||
}
|
||||
|
||||
private object threadStateLocker;
|
||||
public void HandlePacket(object sender, PacketCapture e)
|
||||
{
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
queue.Enqueue(e.GetPacket().Data);
|
||||
}
|
||||
|
||||
if (packetProcessingThread == null)
|
||||
{
|
||||
packetProcessingThread = new Thread(RunPacketProcessingThread);
|
||||
}
|
||||
|
||||
switch (packetProcessingThread.ThreadState)
|
||||
{
|
||||
case ThreadState.Unstarted:
|
||||
packetProcessingThread.Start();
|
||||
break;
|
||||
case ThreadState.Running:
|
||||
break;
|
||||
case ThreadState.Stopped:
|
||||
packetProcessingThread = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private Queue<byte[]> queue;
|
||||
private Thread packetProcessingThread;
|
||||
private Mpeg2Writer _mpeg2Writer;
|
||||
private TdtPsiGenerator _tdtPsiGenerator;
|
||||
private PatPsiGenerator _patPsiGenerator;
|
||||
private PmtPsiGenerator _pmtPsiGenerator;
|
||||
private bool[] knownVlans;
|
||||
private PacketCounter _packetCounter;
|
||||
|
||||
private void RunPacketProcessingThread()
|
||||
{
|
||||
byte[] packet = null;
|
||||
while (true)
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Count == 0)
|
||||
return;
|
||||
else
|
||||
packet = queue.Dequeue();
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
continue;
|
||||
|
||||
DismantledPacket dismantledPacket = DismantledPacket.Dismantle(packet);
|
||||
if (dismantledPacket == null)
|
||||
continue;
|
||||
if (dismantledPacket.Discard)
|
||||
continue;
|
||||
if (dismantledPacket.Payload == null)
|
||||
continue;
|
||||
if (dismantledPacket.Payload.Length == 0)
|
||||
continue;
|
||||
if (dismantledPacket.Payload.Length >= 4091)
|
||||
{
|
||||
//Too long
|
||||
continue;
|
||||
}
|
||||
Span<byte> buildPsi = MpePsiGenerator.BuildPsi(dismantledPacket);
|
||||
|
||||
if (_packetCounter == null)
|
||||
_packetCounter = PacketCounter.GetInstance();
|
||||
_packetCounter.Count(dismantledPacket.VlanId, dismantledPacket.LlcSnap);
|
||||
|
||||
if (_mpeg2Writer == null)
|
||||
{
|
||||
_mpeg2Writer = Mpeg2WriterFactory.CreateWriter();
|
||||
_tdtPsiGenerator = new TdtPsiGenerator(_mpeg2Writer);
|
||||
_tdtPsiGenerator.Run();
|
||||
_patPsiGenerator = new PatPsiGenerator(_mpeg2Writer);
|
||||
_patPsiGenerator.AddProgram(1, 0x0099);
|
||||
_patPsiGenerator.Run();
|
||||
_pmtPsiGenerator = new PmtPsiGenerator(0x0099, 1, 0x0098, _mpeg2Writer);
|
||||
_pmtPsiGenerator.AddStream(0x82, 0x0098);
|
||||
_pmtPsiGenerator.Run();
|
||||
}
|
||||
|
||||
if (!knownVlans[dismantledPacket.VlanId])
|
||||
{
|
||||
ushort pid = (ushort)(0x0100 + dismantledPacket.VlanId);
|
||||
PmtPsiGenerator.PmtGeneratorStream stream = _pmtPsiGenerator.AddStream(0x0d, pid);
|
||||
stream.AddDescriptor(new _0x66_DataBroadcastIdDescriptor(0x0005));
|
||||
Console.WriteLine("Add PID {0}", pid);
|
||||
knownVlans[dismantledPacket.VlanId] = true;
|
||||
}
|
||||
|
||||
_mpeg2Writer.EmitPsi(0x0100 + dismantledPacket.VlanId, buildPsi);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
46
pcap2mpe/Program.cs
Normal file
46
pcap2mpe/Program.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using SharpPcap;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("specify interface as first argument");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[0].Equals("lo"))
|
||||
{
|
||||
Console.WriteLine("can't use the loopback interface");
|
||||
return;
|
||||
}
|
||||
|
||||
var captureDeviceList = CaptureDeviceList.New();
|
||||
captureDeviceList.Refresh();
|
||||
|
||||
ILiveDevice targetDevice = null;
|
||||
foreach (ILiveDevice liveDevice in captureDeviceList)
|
||||
{
|
||||
if (liveDevice.Name.Equals(args[0]))
|
||||
{
|
||||
targetDevice = liveDevice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetDevice == null)
|
||||
{
|
||||
Console.WriteLine("No target device found");
|
||||
return;
|
||||
}
|
||||
|
||||
PacketHandlerQueue queue = new PacketHandlerQueue();
|
||||
|
||||
targetDevice.OnPacketArrival += queue.HandlePacket;
|
||||
targetDevice.Open(DeviceModes.Promiscuous, 9001);
|
||||
targetDevice.Capture();
|
||||
}
|
||||
}
|
||||
38
pcap2mpe/Psi/BasePsiGenerator.cs
Normal file
38
pcap2mpe/Psi/BasePsiGenerator.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public abstract class BasePsiGenerator
|
||||
{
|
||||
public BasePsiGenerator(int pid, string psiType, int intervalMillis, Mpeg2Writer writer)
|
||||
{
|
||||
_pid = pid;
|
||||
_psiType = psiType;
|
||||
_intervalMillis = intervalMillis;
|
||||
_writer = writer;
|
||||
}
|
||||
|
||||
private int _pid;
|
||||
private Mpeg2Writer _writer;
|
||||
private int _intervalMillis;
|
||||
private string _psiType;
|
||||
private Thread _thread;
|
||||
public void Run()
|
||||
{
|
||||
_thread = new Thread(RunEx);
|
||||
_thread.Name = String.Format("{0} PSI Generator", _psiType);
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
private void RunEx()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(_intervalMillis);
|
||||
byte[] buffer = GeneratePsi();
|
||||
_writer.EmitPsi(_pid, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract byte[] GeneratePsi();
|
||||
|
||||
protected int versionNumber;
|
||||
}
|
||||
24
pcap2mpe/Psi/Descriptors/0x66_DataBroadcastIdDescriptor.cs
Normal file
24
pcap2mpe/Psi/Descriptors/0x66_DataBroadcastIdDescriptor.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace pcap2mpe.Descriptors;
|
||||
|
||||
public class _0x66_DataBroadcastIdDescriptor : TsDescriptor {
|
||||
|
||||
public _0x66_DataBroadcastIdDescriptor(ushort id)
|
||||
{
|
||||
DataBroadcastId = id;
|
||||
}
|
||||
|
||||
public ushort DataBroadcastId { get; set; }
|
||||
|
||||
public override byte GetDescriptorId()
|
||||
{
|
||||
return 0x66;
|
||||
}
|
||||
|
||||
public override byte[] Serialize()
|
||||
{
|
||||
byte[] result = BitConverter.GetBytes(DataBroadcastId);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
7
pcap2mpe/Psi/Descriptors/TsDescriptor.cs
Normal file
7
pcap2mpe/Psi/Descriptors/TsDescriptor.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace pcap2mpe.Descriptors;
|
||||
|
||||
public abstract class TsDescriptor
|
||||
{
|
||||
public abstract byte GetDescriptorId();
|
||||
public abstract byte[] Serialize();
|
||||
}
|
||||
68
pcap2mpe/Psi/MpePsiGenerator.cs
Normal file
68
pcap2mpe/Psi/MpePsiGenerator.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class MpePsiGenerator
|
||||
{
|
||||
public static Span<byte> BuildPsi(DismantledPacket packet)
|
||||
{
|
||||
byte[] macAddress = packet.Destination.GetAddressBytes();
|
||||
|
||||
byte[] outBuffer = new byte[4086];
|
||||
MemoryStream outStream = new MemoryStream(outBuffer);
|
||||
outStream.WriteByte(0x3e);
|
||||
|
||||
ushort sectionPrivateReservedLength = 0x0000;
|
||||
sectionPrivateReservedLength |= 0x8000; //Section Syntax indicator ON
|
||||
sectionPrivateReservedLength |= 0x4000; //Private Indicator ON
|
||||
sectionPrivateReservedLength |= 0x3000; //Reserved shall be set to on.
|
||||
sectionPrivateReservedLength |= GetLength(packet); //G
|
||||
outStream.WriteUInt16(sectionPrivateReservedLength);
|
||||
outStream.WriteByte(macAddress[5]);
|
||||
outStream.WriteByte(macAddress[4]);
|
||||
|
||||
byte reservedPayloadAddressLlcCurrent = 0x00;
|
||||
reservedPayloadAddressLlcCurrent |= 0xc0; //reserved
|
||||
//reservedPayloadAddressLlcCurrent |= 0x30; //payload scrambling
|
||||
//reservedPayloadAddressLlcCurrent |= 0x0c; //address scrambling
|
||||
if (packet.LlcSnap)
|
||||
reservedPayloadAddressLlcCurrent |= 0x02; //LLC_SNAP
|
||||
reservedPayloadAddressLlcCurrent |= 0x01; //current_next
|
||||
outStream.WriteByte(reservedPayloadAddressLlcCurrent);
|
||||
outStream.WriteByte(0); //section_number
|
||||
outStream.WriteByte(0); //last_section_number
|
||||
outStream.WriteByte(macAddress[3]);
|
||||
outStream.WriteByte(macAddress[2]);
|
||||
outStream.WriteByte(macAddress[1]);
|
||||
outStream.WriteByte(macAddress[0]);
|
||||
outStream.WriteByteArray(packet.Payload);
|
||||
outStream.Flush();
|
||||
|
||||
uint crc32 = DvbCrc32.CreateCrc(outStream, 0, (int)outStream.Position);
|
||||
outStream.WriteUInt32(crc32);
|
||||
outStream.Flush();
|
||||
|
||||
bool valid = DvbCrc32.ValidateCrc(outStream, 0, (int)outStream.Position);
|
||||
if (!valid)
|
||||
throw new Exception("Invalid crc check");
|
||||
|
||||
return new Span<byte>(outBuffer, 0, (int)outStream.Position);
|
||||
}
|
||||
|
||||
private static ushort GetLength(DismantledPacket packet)
|
||||
{
|
||||
ushort result = 0;
|
||||
result++; //MAC_address_6
|
||||
result++; //MAC_address_5
|
||||
result++; //reserved, payload_scrambling_control, address_scrambling_control, LLC_SNAP_flag, current_next_indicator
|
||||
result++; //section_number;
|
||||
result++; //last_section_number;
|
||||
result++; //MAC_address_4
|
||||
result++; //MAC_address_3
|
||||
result++; //MAC_address_2
|
||||
result++; //MAC_address_1
|
||||
result += (ushort)packet.Payload.Length; //LLC_SNAP / IP_datagram_data_byte
|
||||
result += 4; //CRC_32
|
||||
return result;
|
||||
}
|
||||
}
|
||||
67
pcap2mpe/Psi/PatPsiGenerator.cs
Normal file
67
pcap2mpe/Psi/PatPsiGenerator.cs
Normal file
@ -0,0 +1,67 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class PatPsiGenerator : BasePsiGenerator
|
||||
{
|
||||
public PatPsiGenerator(Mpeg2Writer writer,ushort transportStreamId = 1)
|
||||
: base(0x0000, "PAT", 500, writer)
|
||||
{
|
||||
TransportStreamId = transportStreamId;
|
||||
content = new Dictionary<ushort, ushort>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Dictionary<ushort, ushort> content;
|
||||
|
||||
public void AddProgram(ushort programId, ushort pmtPid)
|
||||
{
|
||||
content.Add(programId, pmtPid);
|
||||
versionNumber++;
|
||||
}
|
||||
|
||||
public ushort TransportStreamId { get; }
|
||||
|
||||
protected override byte[] GeneratePsi()
|
||||
{
|
||||
ushort sectionLength = (ushort)((content.Count * 4) + 5 + 4);
|
||||
sectionLength &= 0x03ff;
|
||||
if (versionNumber > 32)
|
||||
versionNumber %= 32;
|
||||
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ms.WriteUInt8(0); //Table ID
|
||||
|
||||
byte byte2 = 0;
|
||||
byte2 |= 0x80; //section syntax indicator
|
||||
//byte2 &= 0x40; //'0'
|
||||
byte2 |= 0x30; //reserved
|
||||
byte2 += (byte)((sectionLength & 0x0300) >> 8);
|
||||
ms.WriteUInt8(byte2); //section length, part 1
|
||||
|
||||
byte byte3 = (byte)(sectionLength & 0x00ff);
|
||||
ms.WriteUInt8(byte3); //section length, part 2
|
||||
|
||||
ms.WriteUInt16(TransportStreamId); //transport stream id
|
||||
|
||||
|
||||
byte byte4 = 0;
|
||||
byte4 |= 0xc0; //reserved
|
||||
byte4 |= (byte)(versionNumber << 1); //version number
|
||||
byte4 |= 0x01; //current_next_indicator
|
||||
ms.WriteUInt8(byte4);
|
||||
|
||||
ms.WriteUInt8(0); //section number
|
||||
ms.WriteUInt8(0); //last section number
|
||||
foreach (var (program_number, program_map_pid) in content)
|
||||
{
|
||||
ms.WriteUInt16(program_number);
|
||||
ms.WriteUInt16((ushort)(program_map_pid & 0x1fff));
|
||||
}
|
||||
|
||||
uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position);
|
||||
ms.WriteUInt32(crc);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
142
pcap2mpe/Psi/PmtPsiGenerator.cs
Normal file
142
pcap2mpe/Psi/PmtPsiGenerator.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using pcap2mpe.Descriptors;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class PmtPsiGenerator : BasePsiGenerator
|
||||
{
|
||||
public ushort ProgramNumber { get; }
|
||||
public ushort PcrPid { get; }
|
||||
|
||||
private List<TsDescriptor> descriptors;
|
||||
private List<PmtGeneratorStream> streams;
|
||||
|
||||
public PmtPsiGenerator(int pid, ushort programNumber, ushort PcrPid, Mpeg2Writer writer)
|
||||
: base(pid, "PMT", 500, writer)
|
||||
{
|
||||
ProgramNumber = programNumber;
|
||||
this.PcrPid = PcrPid;
|
||||
descriptors = new List<TsDescriptor>();
|
||||
streams = new List<PmtGeneratorStream>();
|
||||
}
|
||||
|
||||
public class PmtGeneratorStream
|
||||
{
|
||||
public PmtGeneratorStream(byte type, ushort pid)
|
||||
{
|
||||
descriptors = new List<TsDescriptor>();
|
||||
this.StreamType = type;
|
||||
this.ElementaryPid = pid;
|
||||
}
|
||||
|
||||
private List<TsDescriptor> descriptors;
|
||||
public byte StreamType { get; }
|
||||
public ushort ElementaryPid { get; }
|
||||
|
||||
public byte[] SerializeDescriptors()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
foreach (TsDescriptor descriptor in descriptors)
|
||||
{
|
||||
ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag
|
||||
|
||||
byte[] descriptorBytes = descriptor.Serialize();
|
||||
ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length
|
||||
ms.Write(descriptorBytes, 0, descriptorBytes.Length);
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public void AddDescriptor(TsDescriptor privateDataSpecifierDescriptor)
|
||||
{
|
||||
descriptors.Add(privateDataSpecifierDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] SerializeStreams()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
foreach (PmtGeneratorStream stream in streams)
|
||||
{
|
||||
ms.WriteUInt8(stream.StreamType);
|
||||
ms.WriteUInt16((ushort)(stream.ElementaryPid | 0xe000));
|
||||
|
||||
byte[] descriptorLoop = stream.SerializeDescriptors();
|
||||
ms.WriteUInt16((ushort)(descriptorLoop.Length | 0xf000)); //ES_Info_length
|
||||
ms.Write(descriptorLoop, 0, descriptorLoop.Length);
|
||||
}
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private byte[] SerializeDescriptors()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
foreach (TsDescriptor descriptor in descriptors)
|
||||
{
|
||||
ms.WriteUInt8(descriptor.GetDescriptorId()); //descriptor_tag
|
||||
|
||||
byte[] descriptorBytes = descriptor.Serialize();
|
||||
ms.WriteUInt8((byte)descriptorBytes.Length); //descriptor_length
|
||||
ms.Write(descriptorBytes, 0, descriptorBytes.Length);
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override byte[] GeneratePsi()
|
||||
{
|
||||
byte[] descriptorBuffer = SerializeDescriptors();
|
||||
int programInfoLength = descriptorBuffer.Length;
|
||||
byte[] streamBuffer = SerializeStreams();
|
||||
int sectionLength = 9 + descriptorBuffer.Length + streamBuffer.Length + 4;
|
||||
|
||||
//TODO: calculate section length here
|
||||
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ms.WriteUInt8(0x02); //table_id
|
||||
|
||||
byte byte2 = 0;
|
||||
byte2 |= 0x80; //section_syntax_indicator
|
||||
//'0'
|
||||
byte2 |= 0x30; //reserved
|
||||
byte2 += (byte)((sectionLength & 0x0300) >> 8);
|
||||
ms.WriteUInt8(byte2); //section length, part 1
|
||||
|
||||
byte byte3 = (byte)(sectionLength & 0x00ff);
|
||||
ms.WriteUInt8(byte3); //section length, part 2
|
||||
|
||||
ms.WriteUInt16(ProgramNumber);
|
||||
|
||||
byte byte4 = 0;
|
||||
byte4 |= 0xc0; //reserved
|
||||
byte4 |= (byte)((versionNumber & 0x1f) << 1); //version number
|
||||
byte4 |= 0x01; //current next indicator
|
||||
ms.WriteUInt8(byte4);
|
||||
|
||||
ms.WriteUInt8(0); //section number
|
||||
ms.WriteUInt8(0); //last section number
|
||||
|
||||
ms.WriteUInt16((ushort)(PcrPid | 0xe000)); //reserved & PCR_PID
|
||||
|
||||
ms.WriteUInt16((ushort)(programInfoLength | 0xf000)); //reserved & program info length
|
||||
|
||||
ms.Write(descriptorBuffer, 0, descriptorBuffer.Length); //descriptors()
|
||||
|
||||
ms.Write(streamBuffer, 0, streamBuffer.Length);
|
||||
|
||||
uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position);
|
||||
ms.WriteUInt32(crc);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public PmtGeneratorStream AddStream(byte streamType, ushort pid)
|
||||
{
|
||||
PmtGeneratorStream stream = new PmtGeneratorStream(streamType, pid);
|
||||
streams.Add(stream);
|
||||
versionNumber++;
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
62
pcap2mpe/Psi/TdtPsiGenerator.cs
Normal file
62
pcap2mpe/Psi/TdtPsiGenerator.cs
Normal file
@ -0,0 +1,62 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class TdtPsiGenerator : BasePsiGenerator
|
||||
{
|
||||
public TdtPsiGenerator(Mpeg2Writer writer) : base(0x0014, "TDT", 1000, writer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
protected override byte[] GeneratePsi()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
ms.WriteByte(0x70);
|
||||
|
||||
byte byte1 = 0;
|
||||
//section syntax indicator = 0
|
||||
byte1 |= 0x40; //reserved future_use
|
||||
byte1 |= 0x30;
|
||||
ms.WriteByte(byte1);
|
||||
|
||||
ms.WriteByte(5); //Section_length (always 5)
|
||||
ms.Write(EncodeMjd(DateTime.Now), 0, 5);
|
||||
return ms.ToArray();
|
||||
|
||||
}
|
||||
|
||||
private static long MilliSecPerDay = (24 * 3600) * 1000;
|
||||
private static long JulianEpochOffset = -40587 * MilliSecPerDay;
|
||||
|
||||
internal static byte[] EncodeMjd(DateTime dt)
|
||||
{
|
||||
//shamelessly stolen from TSDuck's tsMJD.cpp
|
||||
long time_ms = dt.ToUnixTime() * 1000;
|
||||
|
||||
if (time_ms < JulianEpochOffset)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
long d = (time_ms - JulianEpochOffset) / 1000; //seconds since MJD epoch
|
||||
byte[] days = BitConverter.GetBytes((ushort)(d / (24 * 3600)));
|
||||
if (BitConverter.IsLittleEndian)
|
||||
(days[1], days[0]) = (days[0], days[1]);
|
||||
byte[] result = new byte[5];
|
||||
result[0] = days[0];
|
||||
result[1] = days[1];
|
||||
result[2] = EncodeBCD((int)((d / 3600) % 24));
|
||||
result[3] = EncodeBCD((int)((d / 60) % 60));
|
||||
result[4] = EncodeBCD((int)(d % 60));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte EncodeBCD(int value)
|
||||
{
|
||||
byte result = 0;
|
||||
result += (byte)(value % 10);
|
||||
value /= 10;
|
||||
result += (byte)((value % 10) << 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
95
pcap2mpe/StreamExtensions.cs
Normal file
95
pcap2mpe/StreamExtensions.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public static class StreamExtensions
|
||||
{
|
||||
public static PhysicalAddress ReadMacAddress(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[6];
|
||||
if (stream.Read(buffer, 0, 6) != 6)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
return new PhysicalAddress(buffer);
|
||||
}
|
||||
|
||||
public static ushort ReadUInt16BigEndian(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[2];
|
||||
if (stream.Read(buffer, 0, 2) != 2)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
(buffer[1], buffer[0]) = (buffer[0], buffer[1]);
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
public static byte[] ReadByteArray(this Stream stream, int length)
|
||||
{
|
||||
byte[] buffer = new byte[length];
|
||||
int actuallyRead = stream.Read(buffer, 0, length);
|
||||
if (actuallyRead != length)
|
||||
{
|
||||
Console.WriteLine("Packet was cut short during transmission. Expected {0} bytes, got {1}", length,
|
||||
actuallyRead);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static void WriteUInt16(this Stream stream, ushort value)
|
||||
{
|
||||
byte[] buffer = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(buffer);
|
||||
}
|
||||
stream.Write(buffer, 0, 2);
|
||||
}
|
||||
|
||||
public static void WriteByteArray(this Stream stream, byte[] value)
|
||||
{
|
||||
stream.Write(value, 0, value.Length);
|
||||
}
|
||||
|
||||
public static byte ReadUInt8(this Stream stream)
|
||||
{
|
||||
byte[] buffer = new byte[1];
|
||||
if (stream.Read(buffer, 0, 1) != 1)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
public static void WriteUInt32(this Stream stream, uint value)
|
||||
{
|
||||
byte[] buffer = BitConverter.GetBytes(value);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(buffer);
|
||||
}
|
||||
stream.Write(buffer, 0, 4);
|
||||
|
||||
}
|
||||
|
||||
public static byte[] ReadRemainderAsBytes(this Stream stream)
|
||||
{
|
||||
long remainder = stream.Length - stream.Position;
|
||||
if (remainder >= int.MaxValue)
|
||||
throw new NotImplementedException("oversized stream");
|
||||
return ReadByteArray(stream, (int)remainder);
|
||||
}
|
||||
|
||||
public static void WriteUInt8(this Stream stream, byte value)
|
||||
{
|
||||
stream.WriteByte(value);
|
||||
}
|
||||
|
||||
public static long GetAvailableBytes(this Stream stream)
|
||||
{
|
||||
return stream.Length - stream.Position;
|
||||
}
|
||||
}
|
||||
25
pcap2mpe/Writers/Mpeg2FileWriter.cs
Normal file
25
pcap2mpe/Writers/Mpeg2FileWriter.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class Mpeg2FileWriter : Mpeg2Writer
|
||||
{
|
||||
public Mpeg2FileWriter(FileInfo file)
|
||||
{
|
||||
_fileInfo = file;
|
||||
_fileStream = file.OpenWrite();
|
||||
}
|
||||
|
||||
private FileInfo _fileInfo;
|
||||
private FileStream _fileStream;
|
||||
|
||||
protected override void WriteMpeg2PacketEx(byte[] packet)
|
||||
{
|
||||
_fileStream.Write(packet, 0, packet.Length);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_fileStream.Flush();
|
||||
_fileStream.Close();
|
||||
_fileStream.Dispose();
|
||||
}
|
||||
}
|
||||
29
pcap2mpe/Writers/Mpeg2UdpSender.cs
Normal file
29
pcap2mpe/Writers/Mpeg2UdpSender.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace pcap2mpe;
|
||||
|
||||
public class Mpeg2UdpSender : Mpeg2Writer
|
||||
{
|
||||
private readonly IPEndPoint _ipEndPoint;
|
||||
private readonly UdpClient _udpClient;
|
||||
|
||||
public Mpeg2UdpSender(IPEndPoint ipEndPoint)
|
||||
{
|
||||
Console.WriteLine("send to {0}", ipEndPoint);
|
||||
_ipEndPoint = ipEndPoint;
|
||||
_udpClient = new UdpClient();
|
||||
//_udpClient.Connect(_ipEndPoint);
|
||||
}
|
||||
|
||||
protected override void WriteMpeg2PacketEx(byte[] packet)
|
||||
{
|
||||
_udpClient.Send(packet, packet.Length, _ipEndPoint);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_udpClient.Close();
|
||||
_udpClient.Dispose();
|
||||
}
|
||||
}
|
||||
112
pcap2mpe/Writers/Mpeg2Writer.cs
Normal file
112
pcap2mpe/Writers/Mpeg2Writer.cs
Normal file
@ -0,0 +1,112 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
public abstract class Mpeg2Writer : IDisposable
|
||||
{
|
||||
public void EmitPsi(int pid, Span<byte> psi)
|
||||
{
|
||||
EmitPsi(pid, psi.ToArray());
|
||||
}
|
||||
|
||||
public void EmitPsi(int pid, byte[] psi)
|
||||
{
|
||||
int psiOffset = 0;
|
||||
while (psiOffset < psi.Length)
|
||||
{
|
||||
byte[] newPacket = new byte[188];
|
||||
Array.Fill(newPacket, (byte)0xFF);
|
||||
int continuity = CalculateContinuityCounter(pid);
|
||||
byte[] buildHeader = BuildHeader(psiOffset == 0, (uint)pid, continuity);
|
||||
Array.Copy(buildHeader, 0, newPacket, 0, buildHeader.Length);
|
||||
|
||||
byte packetSizeLeft = 188;
|
||||
packetSizeLeft -= (byte)buildHeader.Length;
|
||||
if (psiOffset == 0)
|
||||
{
|
||||
newPacket[buildHeader.Length] = 0;
|
||||
packetSizeLeft--;
|
||||
}
|
||||
|
||||
int copySize = Math.Min(packetSizeLeft, psi.Length - psiOffset);
|
||||
Array.Copy(psi, psiOffset, newPacket, 188 - packetSizeLeft, copySize);
|
||||
psiOffset += copySize;
|
||||
WriteMpeg2Packet(newPacket);
|
||||
}
|
||||
}
|
||||
|
||||
private object writeLock = new object();
|
||||
private void WriteMpeg2Packet(byte[] packet)
|
||||
{
|
||||
if (packet.Length != 188)
|
||||
throw new Exception("packet length mismatch");
|
||||
|
||||
lock (writeLock)
|
||||
{
|
||||
WriteMpeg2PacketEx(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] continuities;
|
||||
|
||||
private int CalculateContinuityCounter(int pid)
|
||||
{
|
||||
if (continuities == null)
|
||||
continuities = new int[0x2000];
|
||||
return continuities[pid]++;
|
||||
}
|
||||
protected abstract void WriteMpeg2PacketEx(byte[] packet);
|
||||
|
||||
public static byte[] BuildHeader(bool pusi, uint pid, int continuityCounter)
|
||||
{
|
||||
return BuildHeader(false, pusi, false, pid, 0, continuityCounter, null, true);
|
||||
}
|
||||
|
||||
public static byte[] BuildHeader(bool tei, bool pusi, bool transportPriority, uint pid, uint tsc, int continuityCounter, byte[] adaptionField = null, bool withPayload = true)
|
||||
{
|
||||
byte[] buffer = new byte[4 + (adaptionField != null ? adaptionField.Length + 1 : 0)];
|
||||
buffer[0] = (byte)'G';
|
||||
|
||||
if (tei)
|
||||
buffer[1] |= 0x80;
|
||||
|
||||
if (pusi)
|
||||
buffer[1] |= 0x40;
|
||||
|
||||
if (transportPriority)
|
||||
buffer[1] |= 0x20;
|
||||
|
||||
if (pid > 0x1fff)
|
||||
throw new ArgumentOutOfRangeException(nameof(pid));
|
||||
|
||||
if ((pid & 0x1000) != 0)
|
||||
buffer[1] |= 0x10;
|
||||
|
||||
uint pid2 = (pid & 0x0f00) >> 8;
|
||||
buffer[1] += (byte)pid2;
|
||||
|
||||
buffer[2] = (byte)(pid & 0x00ff);
|
||||
|
||||
if (tsc > 3)
|
||||
throw new ArgumentOutOfRangeException(nameof(tsc));
|
||||
|
||||
tsc <<= 6;
|
||||
buffer[3] += (byte)tsc;
|
||||
|
||||
if (adaptionField != null)
|
||||
{
|
||||
buffer[3] |= 0x20;
|
||||
buffer[4] = (byte)adaptionField.Length;
|
||||
Array.Copy(adaptionField, 0, buffer, 5, adaptionField.Length);
|
||||
}
|
||||
|
||||
if (withPayload)
|
||||
buffer[3] |= 0x10;
|
||||
|
||||
continuityCounter &= 0x0000000f;
|
||||
buffer[3] += (byte)continuityCounter;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public abstract void Dispose();
|
||||
}
|
||||
|
||||
|
||||
15
pcap2mpe/Writers/NullMpeg2Writer.cs
Normal file
15
pcap2mpe/Writers/NullMpeg2Writer.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace pcap2mpe;
|
||||
|
||||
|
||||
public class NullMpeg2Writer : Mpeg2Writer
|
||||
{
|
||||
protected override void WriteMpeg2PacketEx(byte[] packet)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
14
pcap2mpe/pcap2mpe.csproj
Normal file
14
pcap2mpe/pcap2mpe.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpPcap" Version="6.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
x
Reference in New Issue
Block a user