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