Jump to content

SAT>IP


Schimi

Recommended Posts

Guest Diefenthal

sadly is my sparetime much more limited as i wish
 
so will i now share some code for anyone that will Play with it
 

_udpclient = new UdpClient(_device.RtspSession.RtpPort);
_remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
/* Say the Sat>IP server we want Receives the ServiceDescriptionTable */
    _device.RtspSession.Play("&addpids=17");
    List<ServiceDescription> sdt ;
    GetSDT(_udpclient, _remoteEndPoint,out sdt  );
/* Say the Sat>IP server we want not more Receives the ServiceDescriptionTable */
    _device.RtspSession.Play(string.Format("&delpids=17"));
public class ServiceDescription
    {
        public ushort ServiceID;
        public byte Reserved;
        public bool EitScheduleFlag;
        public bool EitPresentFollowingFlag;
        public RunningStatus RunningStatus;
        public bool FreeCaMode;
        public ushort DescriptorsLoopLength;
        public string ProviderName;
        public string ServiceName;
    }

 

        private bool GetSDT(UdpClient client, IPEndPoint endpoint,out List<ServiceDescription> services)
        {

            services = new List<ServiceDescription>();
            bool retval = false;
            while (!retval)
            {                
                var receivedbytes = client.Receive(ref endpoint);
                RtpHeader h = new RtpHeader(receivedbytes);
                
                if ((receivedbytes.Length > 12) && ((receivedbytes.Length - 12) % 188) == 0)
                {
                    double num9 = (((double)(receivedbytes.Length - 12)) / 188.0) - 1.0;
                    for (double j = 0.0; j <= num9; j++)
                    {
                        byte[] destinationarray = (byte[])Array.CreateInstance(typeof(byte), 188);
                        Array.Copy(receivedbytes, (int)Math.Round((double)(12.0 + (j * 188))), destinationarray, 0, 188);
                        SDTParser parser = new SDTParser();
                        parser.OnTsPacket(destinationarray);
                        int[] vals= parser.Services;
                        foreach(var val in vals)
                        {
                            var s =parser.GetService(val);
                            services.Add(s);
                        }
                        retval = parser.IsReady;                        
                    }
                }                
            }
            
            return retval;
        }        

 
 

        public class SDTParser
        {
           
            private TsSectionDecoder dec1;
            private TsSectionDecoder dec2;
            public bool IsReady;
            private Dictionary<int, ServiceDescription> _services = new Dictionary<int, ServiceDescription>();
            private ServiceDescription servicedescription = null; 
            public SDTParser()
            {
                IsReady = false;
                
                this.dec1 = new TsSectionDecoder(0x11, 0x42);
                this.dec1.OnSectionDecoded += new TsSectionDecoder.MethodOnSectionDecoded(this.OnNewSection);
                this.dec2 = new TsSectionDecoder(0x11, 0x46);
                this.dec2.OnSectionDecoded += new TsSectionDecoder.MethodOnSectionDecoded(this.OnNewSection);
            }

            public int[] Services
            {
                get
                {
                    int[] retServices = new int[ServiceCount];
                    int i = 0;
                    foreach (int key in _services.Keys)
                    {
                        retServices[i] = key;
                        i++;
                    }
                    return retServices;
                }
            }
            public int ServiceCount
            {
                get
                {
                    return _services.Keys.Count;
                }
            }
            public ServiceDescription GetService(int service_id)
            {
                return _services[service_id];
            }

            public void OnNewSection(TsSection section)
            {
                
                
                int num = section.table_id_extension;
                int OriginalNetworkID = (section.Data[8] << 8) + section.Data[9];
                int offset = 11;
                while (offset < section.section_length - 4)
                {
                    servicedescription = new ServiceDescription();
                    servicedescription.ServiceID = (ushort)((section.Data[offset] << 8) + section.Data[offset + 1]);
                    servicedescription.EitScheduleFlag = ((section.Data[offset + 2] & 0x02) != 0);
                    servicedescription.EitPresentFollowingFlag = ((section.Data[offset + 2] & 0x01) != 0);
                    servicedescription.RunningStatus = (RunningStatus)((section.Data[offset + 3] >> 5) & 0x07);
                    servicedescription.FreeCaMode = (((section.Data[offset + 3] >> 4) & 0x01) != 0);
                    servicedescription.DescriptorsLoopLength = (ushort)(((section.Data[offset + 3] << 8) | section.Data[offset + 4]) & 0xfff);
                    offset += 5;
                    int descOffset = offset;
                    offset += servicedescription.DescriptorsLoopLength;
                    while (descOffset < offset)
                    {
                        var descriptor_tag = section.Data[descOffset];
                        var descriptor_length = section.Data[descOffset];
                        switch (descriptor_tag)
                        {
                            case 0x42:
                                break;
                            case 0x48:
                                ReadServiceDescriptor(section.Data, descOffset+2);
                                break;
                            case 0x49:
                                break;
                            case 0x4A:
                                break;
                            case 0x4B:
                                break;
                            case 0x4C:
                                break;
                            case 0x50:
                                break;
                            case 0x51:
                                break;
                            case 0x53:
                                break;
                            case 0x57:
                                break;
                            case 0x5D:
                                break;
                            case 0x5F:
                                break;
                            case 0x64:
                                break;
                            case 0x6E:
                                break;
                            case 0x71:
                                break;
                            case 0x72:
                                break;
                            case 0x73:
                                break;
                            case 0x7D:
                                break;
                            case 0x7E:
                                break;
                            case 0x7F:
                                break;
                            default:
                                break;
                        }
                        descOffset += descriptor_length + 2;
                    }
                    if (!_services.ContainsKey(servicedescription.ServiceID))
                    {
                        _services.Add(servicedescription.ServiceID,servicedescription);
                    }
                }                
                IsReady = true;
            }

            public void OnTsPacket(byte[] tsPacket)
            {
                this.dec1.OnTsPacket(tsPacket);
                this.dec2.OnTsPacket(tsPacket);
            }
            public void ReadServiceDescriptor(byte[] data, int offset)
            {  
                byte ProviderNameLength = data[offset + 1];               
                servicedescription.ProviderName = ReadString(data, offset + 2, (int)ProviderNameLength);
                byte ServiceNameLength = data[offset + 2 + ProviderNameLength];
                servicedescription.ServiceName = ReadString(data, offset + 3 + ProviderNameLength, ServiceNameLength);
            }
            protected string ReadString(byte[] data, int offset, int length)
            {
                string encoding = "utf-8"; // Standard latin alphabet
                List<byte> bytes = new List<byte>();
                for (int i = 0; i < length; i++)
                {
                    byte character = data[ offset + i];
                    bool notACharacter = false;
                    if (i == 0)
                    {
                        // Look for a character table definition
                        if (character < 0x20)
                        {
                            switch (character)
                            {
                                case 0x00:
                                    break;
                                case 0x01:
                                    encoding = "iso-8859-5";
                                    break;
                                case 0x02:
                                    encoding = "iso-8859-6";
                                    break;
                                case 0x03:
                                    encoding = "iso-8859-7";
                                    break;
                                case 0x04:
                                    encoding = "iso-8859-8";
                                    break;
                                case 0x05:
                                    encoding = "iso-8859-9";
                                    break;
                                default:
                                    break;
                            }
                            notACharacter = true;
                        }
                    }
                    if (character < 0x20 || (character >= 0x80 && character <= 0x9F))
                    {  
                        notACharacter = true;
                    }
                    if (!notACharacter)
                    {
                        bytes.Add(character);
                    }
                }
                Encoding enc = Encoding.GetEncoding(encoding);
                ASCIIEncoding destEnc = new ASCIIEncoding();
                byte[] destBytes = Encoding.Convert(enc, destEnc, bytes.ToArray());
                return destEnc.GetString(destBytes);
            }
        } 
        public class TsSectionDecoder
        {
            public static uint incompleteSections;
            private ushort _pid;
            private TsSection _section;
            private int _tableId;

            public event MethodOnSectionDecoded OnSectionDecoded;

            public TsSectionDecoder()
            {
                _pid = 8191;
                _tableId = -1;
                _section = new TsSection();
            }  

            public TsSectionDecoder(ushort pid, int table_id)
            {
                _pid = pid;
                _tableId = table_id;
                _section = new TsSection();
            }

            private int AddToSection(byte[] tsPacket, int index, int sectionLen)
            {
                int num = -1;
                int length = -1;
                if ((index + sectionLen) < 0xb9)
                {
                    length = sectionLen + 3;
                    num = (index + sectionLen) + 3;
                }
                else
                {
                    num = 0xbc;
                    length = 0xbc - index;
                }
                Array.Copy(tsPacket, index, _section.Data, _section.BufferPos, length);
                _section.BufferPos += length;
                _section.DecodeHeader();
                return num;
            }

            public virtual void OnNewSection(TsSection section)
            {
            }

            public virtual void OnTsPacket(byte[] tsPacket)
            {
                TsHeader header = TsHeader.Decode(tsPacket);
                if (((_pid < 0x1fff) && (header.Pid == _pid)) && header.HasPayload)
                {
                    int payLoadStart = header.PayLoadStart;
                    int num2 = 0;
                    if (header.PayloadUnitStartIndicator)
                    {
                        num2 = (payLoadStart + tsPacket[payLoadStart]) + 1;
                        if (_section.BufferPos == 0)
                        {
                            payLoadStart += tsPacket[payLoadStart] + 1;
                        }
                        else
                        {
                            payLoadStart++;
                        }
                    }
                    while (payLoadStart < 0xbc)
                    {
                        if (_section.BufferPos == 0)
                        {
                            if (!header.PayloadUnitStartIndicator)
                            {
                                return;
                            }
                            if (tsPacket[payLoadStart] == 0xff)
                            {
                                return;
                            }
                            int sectionLen = this.SnapshotSectionLength(tsPacket, payLoadStart);
                            payLoadStart = this.StartNewTsSection(tsPacket, payLoadStart, sectionLen);
                        }
                        else
                        {
                            if (_section.section_length == -1)
                            {
                                _section.CalcSectionLength(tsPacket, payLoadStart);
                            }
                            if (_section.section_length == 0)
                            {
                                _section.Reset();
                                return;
                            }
                            int num4 = _section.section_length - _section.BufferPos;
                            if ((num2 != 0) && ((payLoadStart + num4) > num2))
                            {
                                num4 = num2 - payLoadStart;
                                payLoadStart = this.AddToSection(tsPacket, payLoadStart, num4);
                                _section.section_length = _section.BufferPos - 1;
                                payLoadStart = num2;
                                incompleteSections++;
                            }
                            else
                            {
                                payLoadStart = this.AddToSection(tsPacket, payLoadStart, num4);
                            }
                        }
                        if (_section.SectionComplete() && (_section.section_length > 0))
                        {
                            this.OnNewSection(_section);
                            if (this.OnSectionDecoded != null)
                            {
                                this.OnSectionDecoded(_section);
                            }
                            _section.Reset();
                        }
                        num2 = 0;
                    }
                }
            }

            public void Reset()
            {
                _section.Reset();
            }

            private int SnapshotSectionLength(byte[] tsPacket, int start)
            {
                if (start > 0xb8)
                {
                    return -1;
                }
                return (((tsPacket[start + 1] & 15) << 8) + tsPacket[start + 2]);
            }

            private int StartNewTsSection(byte[] tsPacket, int index, int sectionLen)
            {
                int num = -1;
                int length = -1;
                if (sectionLen > -1)
                {
                    if ((index + sectionLen) < 0xb9)
                    {
                        length = sectionLen + 3;
                        num = (index + sectionLen) + 3;
                    }
                    else
                    {
                        num = 0xbc;
                        length = 0xbc - index;
                    }
                }
                else
                {
                    num = 0xbc;
                    length = 0xbc - index;
                }
                _section.Reset();
                Array.Copy(tsPacket, index, _section.Data, 0, length);
                _section.BufferPos = length;
                _section.DecodeHeader();
                return num;
            }

            public ushort Pid
            {
                get
                {
                    return _pid;
                }
                set
                {
                    _pid = value;
                }
            }

            public int TableId
            {
                get
                {
                    return _tableId;
                }
                set
                {
                    _tableId = value;
                }
            }

            public delegate void MethodOnSectionDecoded(TsSection section);
       }
public class TsSection
    {
        public int BufferPos;
        public byte[] Data = new byte[MAX_SECTION_LENGTH * 5];
        public int last_section_number;
        public static int MAX_SECTION_LENGTH = 0x10cc;
        public int section_length;
        public int section_number;
        public int section_syntax_indicator;
        public int table_id;
        public int table_id_extension;
        public int version_number;

        public TsSection()
        {
            this.Reset();
        }

        public int CalcSectionLength(byte[] tsPacket, int start)
        {
            if (this.BufferPos < 3)
            {
                byte num = 0;
                byte num2 = 0;
                if (this.BufferPos == 1)
                {
                    num = tsPacket[start];
                    num2 = tsPacket[start + 1];
                }
                else if (this.BufferPos == 2)
                {
                    num = this.Data[1];
                    num2 = tsPacket[start];
                }
                this.section_length = ((num & 15) << 8) + num2;
            }
            else
            {
                this.section_length = ((this.Data[1] & 15) << 8) + this.Data[2];
            }
            return this.section_length;
        }

        public bool DecodeHeader()
        {
            if (this.BufferPos < 8)
            {
                return false;
            }
            this.table_id = this.Data[0];
            this.section_syntax_indicator = (this.Data[1] >> 7) & 1;
            if (this.section_length == -1)
            {
                this.section_length = ((this.Data[1] & 15) << 8) + this.Data[2];
            }
            this.table_id_extension = (this.Data[3] << 8) + this.Data[4];
            this.version_number = (this.Data[5] >> 1) & 0x1f;
            this.section_number = this.Data[6];
            this.section_syntax_indicator = (this.Data[1] >> 7) & 1;
            return true;
        }

        public void Reset()
        {
            this.table_id = -1;
            this.table_id_extension = -1;
            this.section_length = -1;
            this.section_number = -1;
            this.version_number = -1;
            this.section_syntax_indicator = -1;
            this.BufferPos = 0;
            for (int i = 0; i < this.Data.Length; i++)
            {
                this.Data[i] = 0xff;
            }
        }

        public bool SectionComplete()
        {
            if ((!this.DecodeHeader() && (this.BufferPos > this.section_length)) && (this.section_length > 0))
            {
                return true;
            }
            if (!this.DecodeHeader())
            {
                return false;
            }
            return (this.BufferPos >= this.section_length);
        }
    }
 public class TsHeader
    {
        public byte SyncByte;
        public bool TransportErrorIndicator;
        public bool PayloadUnitStartIndicator;
        public bool TransportPriority;
        public int Pid;
        public byte TransportScramblingControl;
        public byte AdaptionFieldControl;
        public byte ContinuityCounter;
        public byte AdaptionFieldLength;
        public byte PayLoadStart;
        public bool HasAdaptionField;
        public bool HasPayload;

        public TsHeader()
        {
            TransportErrorIndicator = true;
        }
        
        public static TsHeader Decode(byte[] buffer)
        {
            var header = new TsHeader();
            header.SyncByte = buffer[0];
            if (header.SyncByte != 0x47)
            {
                header.TransportErrorIndicator = true;                
            }
            header.TransportErrorIndicator = ((buffer[1] & 0x80) == 0x80);
            if (!header.TransportErrorIndicator)
            {
                header.PayloadUnitStartIndicator = ((buffer[1] & 0x40) == 0x40);
                header.TransportPriority = ((buffer[1] & 0x20) == 0x20);
                header.Pid = (((buffer[1] & 0x1F) << 8) + buffer[2]);
                header.TransportScramblingControl = (byte)(buffer[3] & 0xC0);
                header.AdaptionFieldControl = (byte)((buffer[3] >> 4) & 0x3);
                header.HasAdaptionField = (buffer[3] & 0x20) == 0x20;
                header.HasPayload = (buffer[3] & 0x10) == 0x10;
                header.ContinuityCounter = (byte)(buffer[3] & 0x0F);
                header.AdaptionFieldLength = 0;
                header.PayLoadStart = 4;
                if (header.HasAdaptionField)
                {
                    header.AdaptionFieldLength = buffer[4];
                    header.PayLoadStart = (byte)(5 + header.AdaptionFieldLength);
                }
                return header;
            }
            return (null);
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Transportstream Header .\n");
            sb.AppendFormat("Sync Byte: {0} .\n", SyncByte.ToString());
            sb.AppendFormat("Transport Error Indicator: {0} .\n", TransportErrorIndicator);
            sb.AppendFormat("Payload Unit Start Indicator: {0} .\n",PayloadUnitStartIndicator);
            sb.AppendFormat("Transport Priority: {0} .\n", TransportPriority);
            sb.AppendFormat("PID: {0} .\n", Pid);
            sb.AppendFormat("Transport Scrambling Control: {0} .\n", TransportScramblingControl);
            sb.AppendFormat("Adaption Field Control: {0} .\n", AdaptionFieldControl);
            sb.AppendFormat("Continuity Counter: {0} .\n" , ContinuityCounter);
            sb.AppendFormat(".\n");
            return sb.ToString();
        }
    }
Edited by Diefenthal
  • Like 1
Link to comment
Share on other sites

Guest Diefenthal

short test results says yes the ServiceDescriptionTable parsing  is working

with that had we

 

this values avaible

 

ServiceType 

ServiceName

ServiceProviderName

ServiceId

EitScheduleFlag

EitPresentFollowingFlag

ScrambeldFlag

RunningStatus

 

but now is the nextsteps are  if found more time

Parsing of PMT to get PCR, Audio, Video, TTX and Subtitle Pids for adding to Channel Object

Parsing of NIT to get exact Tuning Informations for TransportStreamId for adding to Channel Object

  • Like 1
Link to comment
Share on other sites

Parsing of PMT to get PCR, Audio, Video, TTX and Subtitle Pids for adding to Channel Object

What is this for? Once we open the live stream can't we just probe with ffmpeg?

Link to comment
Share on other sites

Guest Diefenthal

sure but we must the satip Server say that we wont this data streams  

and so must tell this pids over rtsp Play request  to the server

  • Like 1
Link to comment
Share on other sites

Master of Tragedy

So it seems the SAT>IP-Server support is no more than finding and listing the server for now but not being able to actually use it?

I was pretty happy to see that it found my Kathrein SAT>IP-Server when I clicked on Live-TV but it's not doing anything. I could configure it but that's it.

And now I can't even delete it anymore. Once deleted and back to the Live-TV configuration it shows up again. Is there any way to get rid of it?

 

For now I've added the DVBLink-Plugin which works although extremely slow (needs about 30 seconds to actually show a channel).

Is there any way to sort the EPG by channel number or anything other than completely unsorted?

Link to comment
Share on other sites

Guest Diefenthal

Yes Sat>Ip Support is more than discover the devices and read an m3u and Play an stream

 

that is the reason why in the SatIp Spec is describe what an Client should can do

 

Discovery (SSDP + Parsing Device Description)

Start Modify Stop Streams (RTSP RTCP RTP)

Service / Channel Search (Read Transport Streams Data from RTP Listener)

Collecting Guide Data

Manage Services Channels

 

EMBY can at time only Discover the Devices  and send RTSP Request to the Server  

all other things (Service Channel Search, Collecting Guide Data and Manage Services Channels)   are not given in the current state

 

and for Users that ask why i share and work here on an Winform Project (Binary + Source )

it is so for me easier to test written code  and if it stable enough, give i @@Luke the info for porting it to Emby

 

PS if anyone would help for speedup the Progress

the source is avaible on github

and would anyone make an Code Review is that welcome too

Edited by Diefenthal
Link to comment
Share on other sites

Guest Diefenthal

it Looks like that it give Problems by device Discovery

i ask me why it not was previous reported

i work for you  and this is not an oneway, i need Test Results without this  is the Sat>Ip Support for emby dead

so once more ,i share test apps to get an true it works or false it crash
please test any version  what i share here if one build crash in your config let it me Know
this helps emby in any case

 

here 2 Tools two Show where the discovery (ssdp) has Problems with device xyz

the first GetSSDPUnicast Response send 3 M-Search Messages to "239.255.255.250:1900" with searchterm "urn:ses-com:device:SatIPServer:1"

and should recieve HTTP/1.1 200 OK Responses

 

 

  1. check is Test the Regex Match for HTTP/1.1 200 OK
  2. check is Test the Regex Match for UUID 
  3. check is Test for Searchtearm  

if all 3 passed with true  than can you test the second Tool

 

there put you the Location: value into the TextBox

and Show it the Description Fields are filled

 

 

 

if there Textboxes filled should it work

 

thanx

 

 

 

 

Link to comment
Share on other sites

Master of Tragedy

I appreciate your work but I'm on MacOS so I can't test your tool.

I will see if I can set up a windows VM for that.

Link to comment
Share on other sites

Guest Diefenthal
Parsing of PMT to get PCR, Audio, Video, TTX and Subtitle Pids for adding to Channel Object

What is this for? Once we open the live stream can't we just probe with ffmpeg?

 

you can it try

SDT give you the Basic informations

Link to comment
Share on other sites

rechner123

Hi 

 

There is a new DVB-Viewer Recording Service available, with maybe some important changes for satip?! and security fixes.

 

"Change/Fix: RTSP Server: Fixes and enhanced Sat>IP 1.2.2 compatibility concerning data sent through the RTCP channel and as response to DESCRIBE commands. Proprietary RTCP data for DVBViewer & Co clients is not sent to other Sat>IP clients anymore. More about it here."

 

I hope this could be useful.

Edited by rechner123
Link to comment
Share on other sites

Guest Diefenthal

Hi 

 

There is a new DVB-Viewer Recording Service available, with maybe some important changes for satip?! and security fixes.

 

"Change/Fix: RTSP Server: Fixes and enhanced Sat>IP 1.2.2 compatibility concerning data sent through the RTCP channel and as response to DESCRIBE commands. Proprietary RTCP data for DVBViewer & Co clients is not sent to other Sat>IP clients anymore. More about it here."

 

I hope this could be useful.

helpfull yes but DVBViewer send s not specified Parameters in there RTCP APP  and Describe Responses

think that they mean this with this info

Link to comment
Share on other sites

  • 2 weeks later...
Guest Diefenthal

think this Topic is dead

 

there was an info in emby Repro "remove dead code"  ande the source code lines behind this Info was Sat>Ip related

 

 

@@Luke you can  delete my account all attachments are deleted

Edited by Diefenthal
Link to comment
Share on other sites

  • 2 months later...
Commerzpunk

Hello,

i am from germany and very interested to get SAT>IP working in Emby.

 

Right now i got Emby and TVHeadend running on a Synology DS115.

I hope to only have Emby in the future to get recording of my series perfectly running.

 

My device is a Digibit R1 which is a 4-Tuner in and LAN out SAT>Ip Server.

Works perfectly with TVHeadend.

 

I can offer to test, provide logs, and so on.

 

@Diegenthal: Youre still here and reading?

Link to comment
Share on other sites

  • 3 weeks later...
Guest Diefenthal

Hello,

i am from germany and very interested to get SAT>IP working in Emby.

 

Right now i got Emby and TVHeadend running on a Synology DS115.

I hope to only have Emby in the future to get recording of my series perfectly running.

 

My device is a Digibit R1 which is a 4-Tuner in and LAN out SAT>Ip Server.

Works perfectly with TVHeadend.

 

I can offer to test, provide logs, and so on.

 

@Diegenthal: Youre still here and reading?

yes i m there and read somethings

but nothing more

 

@@Luke had add some classes Import back but in MediaBrowser.Server.Startup.Common i ask me why there

Edited by Diefenthal
  • Like 1
Link to comment
Share on other sites

Commerzpunk

Hi Kai ;-)

nice to hear that.

I could help testing or providing logs.

But seems there is no action in sat>ip and emby right now.

 

Thats sad. But you can send me messages, of course in german too.

 

Cheers

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...