This project has moved. For the latest updates, please go here.

Feature request: M2TS support

Nov 4, 2014 at 8:34 PM
As far as I understand, M2TS differs only in 4 bytes per packet.
Could you add M2TS support?
Coordinator
Nov 4, 2014 at 9:55 PM
Try changing the "PacketSize = 188" in SM.TsParser.TsPacket to 192. It might work. If that is all that's needed, then we could see about making that value configurable without recompilation.

If the video is VC-1, then one would also need to change the configuration stuff to pass it through.
Nov 5, 2014 at 5:07 AM
That was the first thing I've tried, but it doesn't work :(
When opening .m2ts WITHOUT any changes to source code the video plays with some artefacts.

PS. Video codec - H.264.
Coordinator
Nov 5, 2014 at 3:33 PM
I tested with a sample .m2ts file and can confirm that it isn't getting demuxed properly. I'm not yet sure what is going wrong; I anticipate some fun times comparing ffprobe output with phonem's TsDump output...
Coordinator
Nov 6, 2014 at 1:28 AM
I don't know about a proper solution in the near term, but for now, you can try using the following TsDecoder.cs (note that TsDecoder.Skip will need to be set to 0 to play .TS streams):
// -----------------------------------------------------------------------
//  <copyright file="TsDecoder.cs" company="Henric Jungheim">
//  Copyright (c) 2012-2014.
//  <author>Henric Jungheim</author>
//  </copyright>
// -----------------------------------------------------------------------
// Copyright (c) 2012-2014 Henric Jungheim <software@henric.org>
// 
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace SM.TsParser
{
    public interface ITsDecoder : IDisposable
    {
        bool EnableProcessing { get; set; }
        void ParseEnd();
        void Parse(byte[] buffer, int offset, int length);
        void Initialize(Func<TsStreamType, uint, TsPacketizedElementaryStream> pesStreamFactory, Action<IProgramStreams> programStreamsHandler);
        void FlushBuffers();
    }

    public sealed class TsDecoder : ITsDecoder
    {
        public static int Skip = 4;
        readonly byte[] _destinationArray;
        readonly Dictionary<uint, Action<TsPacket>> _packetHandlers = new Dictionary<uint, Action<TsPacket>>();
        readonly int _packetSize;
        readonly TsPacket _tsPacket = new TsPacket();
        int _destinationLength;
        volatile bool _enableProcessing = true;
        Func<TsStreamType, uint, TsPacketizedElementaryStream> _pesStreamFactory;
        TsProgramAssociationTable _programAssociationTable;
        int _tsIndex;

        public TsDecoder()
        {
            _packetSize = TsPacket.PacketSize;

            _destinationArray = new byte[_packetSize * 174];

            //var packetId = 0;
            //PacketMonitor += p => Debug.WriteLine("Packet {0}: {1}", ++packetId, p);
        }

        public Action<TsPacket> PacketMonitor { get; set; }

        #region ITsDecoder Members

        public bool EnableProcessing
        {
            get { return _enableProcessing; }
            set { _enableProcessing = value; }
        }

        public void Dispose()
        {
            Clear();
        }

        public void Initialize(Func<TsStreamType, uint, TsPacketizedElementaryStream> pesStreamFactory, Action<IProgramStreams> programStreamsHandler = null)
        {
            if (pesStreamFactory == null)
                throw new ArgumentNullException("pesStreamFactory");

            _pesStreamFactory = pesStreamFactory;

            Clear();

            // Bootstrap with the program association handler
            _programAssociationTable = new TsProgramAssociationTable(this, program => true, programStreamsHandler);

            _packetHandlers[0x0000] = _programAssociationTable.Add;

            _tsIndex = 0;
        }

        public void FlushBuffers()
        {
            if (null != _programAssociationTable)
                _programAssociationTable.FlushBuffers();

            _destinationLength = 0;
        }

        public void ParseEnd()
        {
            Parse(null, 0, 0);

            foreach (var handler in _packetHandlers.Values)
                handler(null);
        }

        public void Parse(byte[] buffer, int offset, int length)
        {
            if (!EnableProcessing)
                return;

            // First, finish off anything currently buffered.  Takes
            // new bytes to help finish off any pending data.
            if (_destinationLength > 0)
            {
                var consumed = ParseBuffer(buffer, offset, length);

                Debug.Assert(consumed <= length, "Consumed too much");

                offset += consumed;
                length -= consumed;

                if (0 == length)
                    return;

                Debug.Assert(0 == _destinationLength, "The buffer should be consumed");
            }

            // Run through as much as we can of the provided buffer

            var i = offset;

            while (EnableProcessing && i <= offset + length - (Skip + _packetSize))
            {
                if (TsPacket.SyncByte != buffer[Skip + i] || !ParsePacket(buffer, Skip + i))
                {
                    ++i;
                    continue;
                }

                i += _packetSize + Skip;
            }

            for (; i < offset + length - Skip && TsPacket.SyncByte != buffer[Skip + i]; ++i)
                ;

            _destinationLength = length - (i - offset);

            // Store any remainder
            if (_destinationLength > 0)
            {
                Array.Copy(buffer, i, _destinationArray, 0, _destinationLength);
                Debug.Assert(Skip < _destinationLength || TsPacket.SyncByte == _destinationArray[Skip], "destination array must start with sync byte");
            }
        }

        #endregion

        internal void RegisterHandler(uint pid, Action<TsPacket> handler)
        {
            _packetHandlers[pid] = handler;
        }

        internal void UnregisterHandler(uint pid)
        {
            _packetHandlers.Remove(pid);
        }

        internal TsPacketizedElementaryStream CreateStream(TsStreamType streamType, uint pid)
        {
            return _pesStreamFactory(streamType, pid);
        }

        void Clear()
        {
            if (null != _programAssociationTable)
            {
                _programAssociationTable.Clear();
                _programAssociationTable = null;
            }

            _packetHandlers.Clear();
            _destinationLength = 0;
        }

        int ParseBuffer(byte[] buffer, int offset, int length)
        {
            var length0 = length;
            var i = 0;

            while (i < _destinationLength)
            {
                for (; i < _destinationLength - Skip; ++i)
                {
                    if (TsPacket.SyncByte == _destinationArray[Skip + i])
                        break;
                }

                if (i >= _destinationLength)
                    break;

                var remainder = (_destinationLength - i) % (Skip + _packetSize);

                Debug.Assert(remainder > 0, "Remainder must be positive");

                if (0 == length)
                    break;

                var copyLength = Math.Min(Skip + _packetSize - remainder, length);

                if (copyLength > 0)
                {
                    Array.Copy(buffer, offset, _destinationArray, _destinationLength, copyLength);

                    offset += copyLength;
                    length -= copyLength;

                    _destinationLength += copyLength;
                }

                Debug.Assert(_destinationLength - i >= Skip + _packetSize, "There must be a full packet available");

                if (!ParsePacket(_destinationArray, Skip + i))
                {
                    ++i;
                    continue;
                }

                i += Skip + _packetSize;

                Debug.Assert(i <= _destinationLength, "The offset must not run past the end of the buffer");
            }

            if (i >= _destinationLength)
                _destinationLength = 0;
            else if (i > 0)
            {
                _destinationLength -= i;
                Array.Copy(_destinationArray, i, _destinationArray, 0, _destinationLength);
            }

            return length0 - length;
        }

        bool ParsePacket(byte[] buffer, int offset)
        {
            if (!_tsPacket.Parse(_tsIndex, buffer, offset))
                return false;

            _tsIndex += _packetSize;

            if (_tsPacket.IsSkip)
                return true;

            Action<TsPacket> handler;
            if (_packetHandlers.TryGetValue(_tsPacket.Pid, out handler))
                handler(_tsPacket);

            var pm = PacketMonitor;

            if (null != pm)
                pm(_tsPacket);

            return true;
        }
    }
}