예제 #1
0
        public static SampleSizeTable readSampleSizeCompact(Mp4Reader reader)
        {
            Debug.Assert(reader.currentBox == eBoxType.stz2);

            Span <int> header = stackalloc int[3];

            reader.read(header.asBytes());
            int fieldSize = (header[1] & 0xFF);
            int count     = header[2].endian();

            switch (fieldSize)
            {
            case 4:
                // bits / sample, i.e. each value is in [ 0 .. 15 ] interval. I wonder which codec they have designed it for..
                throw new NotImplementedException();

            case 8:
                return(new SampleSizeVariable8(reader, count));

            case 16:
                return(new SampleSizeVariable16(reader, count));

            default:
                throw new ArgumentException();
            }
        }
예제 #2
0
        static int parseSampleTableHeader(Mp4Reader reader)
        {
            Span <byte> span = stackalloc byte[8];

            reader.read(span);
            return(BitConverter.ToInt32(span.Slice(4)).endian());
        }
예제 #3
0
        internal MediaHandler(Mp4Reader reader)
        {
            Debug.Assert(reader.currentBox == eBoxType.hdlr);

            // 8.4.3.2
            int         cb   = checked ((int)reader.remainingBytes);
            Span <byte> data = stackalloc byte[cb];

            reader.read(data);

            uint handlerType = BitConverter.ToUInt32(data.Slice(8));

            mediaHandler = (eMediaHandler)handlerType;

            ReadOnlySpan <byte> utf8 = data.Slice(8 + 4 + 4 * 3);

            // Trim the null
            for (int i = 0; i < utf8.Length; i++)
            {
                if (0 == utf8[i])
                {
                    utf8 = utf8.Slice(0, i);
                    break;
                }
            }

            name = Encoding.UTF8.GetString(utf8);
        }
예제 #4
0
파일: Mp4Utils.cs 프로젝트: zeta1999/Vrmac
        internal static uint readUInt(this Mp4Reader reader)
        {
            Span <byte> bytes = stackalloc byte[4];

            reader.read(bytes);
            return(bytes.cast <uint>()[0]);
        }
예제 #5
0
        public static SampleSizeTable createVariable(Mp4Reader reader, int count)
        {
            // That's not a small amount of data, maybe a megabyte or 2. Bypassing the GC with malloc/free.
            IntPtr nativePointer = Marshal.AllocHGlobal(count * 4);

            try
            {
                Span <int> entries = Unsafe.writeSpan <int>(nativePointer, count);

                reader.read(entries.asBytes());
                ComputeUtils.flipEndiannessAndComputeMax(entries, out uint maxValue);

                if (maxValue > 0xFFFFFF)
                {
                    return(new SampleSizeVariable32(entries.ToArray(), (int)maxValue));
                }

                if (maxValue > 0xFFFF)
                {
                    return(new SampleSizeVariable24(entries, (int)maxValue));
                }

                return(new SampleSizeVariable16(entries, (int)maxValue));
            }
            finally
            {
                Marshal.FreeHGlobal(nativePointer);
            }
        }
예제 #6
0
 public SampleSizeVariable8(Mp4Reader mp4, int count) : base(count)
 {
     Debug.Assert(mp4.currentBox == eBoxType.stz2);
     entries = new byte[count];
     mp4.read(entries.AsSpan());
     maxSampleSize = ComputeUtils.computeMax(entries.AsSpan());
 }
예제 #7
0
 public SampleSizeVariable16(Mp4Reader mp4, int count) :
     base(count)
 {
     Debug.Assert(mp4.currentBox == eBoxType.stz2);
     entries = new ushort[count];
     mp4.read(entries.AsSpan().asBytes());
     maxSampleSize = ComputeUtils.swapEndiannessAndComputeMax(entries.AsSpan());
 }
예제 #8
0
파일: Mp4Utils.cs 프로젝트: zeta1999/Vrmac
        internal static T readStructure <T>(this Mp4Reader reader) where T : unmanaged
        {
            T           result = default;
            Span <T>    span1  = MemoryMarshal.CreateSpan(ref result, 1);
            Span <byte> span2  = MemoryMarshal.Cast <T, byte>(span1);

            reader.read(span2);
            return(result);
        }
예제 #9
0
        static float readHeader(Mp4Reader reader)
        {
            Debug.Assert(reader.currentBox == eBoxType.smhd);
            Span <byte> bytes = stackalloc byte[8];

            reader.read(bytes);
            short balance = BitConverter.ToInt16(bytes.Slice(4));

            balance = BinaryPrimitives.ReverseEndianness(balance);
            return(balance * (1.0f / 0x100));
        }
예제 #10
0
        internal static CompositionTimeDeltas read(Mp4Reader reader)
        {
            Debug.Assert(reader.currentBox == eBoxType.ctts);

            sHeader header = reader.readStructure <sHeader>();
            int     count  = header.entry_count.endian();

            if (count <= 0)
            {
                return(null);
            }

            bool signedIntegers;

            switch (header.version)
            {
            case 0:
                signedIntegers = false;
                break;

            case 1:
                signedIntegers = true;
                break;

            default:
                // Warning because videos actually play OK even when ignoring the data from that box.
                Logger.logWarning("ctts box has unexpected version {0}", header.version);
                return(null);
            }

            // That thing takes couple MB of RAM. Bypassing the GC with malloc/free.
            IntPtr nativePointer = Marshal.AllocHGlobal(count * 8);

            try
            {
                Span <CttsEntryUnsigned> span = Unsafe.writeSpan <CttsEntryUnsigned>(nativePointer, count);
                reader.read(span.asBytes());

                if (signedIntegers)
                {
                    var signed = MemoryMarshal.Cast <CttsEntryUnsigned, CttsEntrySigned>(span);
                    return(parseSigned(signed));
                }
                return(parseUnsigned(span));
            }
            finally
            {
                Marshal.FreeHGlobal(nativePointer);
            }
        }
예제 #11
0
        static                                SampleEntry[] parseSampleTable(Mp4Reader reader)
        {
            Debug.Assert(reader.currentBox == eBoxType.stsd);

            Span <byte> span = stackalloc byte[8];

            reader.read(span);

            int entryCount = BitConverter.ToInt32(span.Slice(4)).endian();

            SampleEntry[] result = new SampleEntry[entryCount];
            for (int i = 0; i < entryCount; i++)
            {
                reader.read(span);
                int  length = BitConverter.ToInt32(span).endian();
                uint code   = BitConverter.ToUInt32(span.Slice(4));
                switch (code)
                {
                case (uint)eFileType.avc1:
                    result[i] = new AVC1SampleEntry(reader, length - 8);
                    break;

                case (uint)eAudioBoxType.mp4a:
                    result[i] = new MP4AudioSampleEntry(reader, length - 8);
                    break;

                case (uint)eAudioBoxType.ac3:
                    result[i] = new Ac3AudioSampleEntry(reader, length - 8);
                    break;

                default:
                    throw new NotImplementedException("The sample format is not currently supported");
                }
            }
            return(result);
        }
예제 #12
0
        public static T[] readArray <T>(Mp4Reader reader, int count) where T : unmanaged
        {
            T[] result = new T[count];
            var span   = result.AsSpan().asBytes();

            reader.read(span);

            Span <uint> values = span.cast <uint>();

            for (int i = 0; i < values.Length; i++)
            {
                values[i] = values[i].endian();
            }

            return(result);
        }
예제 #13
0
        public static ChunkOffsetTable readOffsets64(Mp4Reader reader)
        {
            Debug.Assert(reader.currentBox == eBoxType.co64);

            sHeader header = reader.readStructure <sHeader>();
            int     count  = header.entry_count.endian();

            long[] entries = new long[count];
            reader.read(entries.AsSpan().asBytes());
            for (int i = 0; i < entries.Length; i++)
            {
                entries[i] = entries[i].endian();
            }

            return(new ChunkOffsetTable64(entries));
        }
예제 #14
0
        public Mp4File(Mp4Reader reader)
        {
            this.reader = reader;

            reader.moveToBox(eBoxType.ftyp);

            int         cb   = checked ((int)reader.remainingBytes);
            Span <byte> data = stackalloc byte[cb];

            reader.read(data);

            List <eFileType> types = new List <eFileType>();
            var values             = data.cast <uint>();

            types.Add((eFileType)values[0]);
            minorVersion = (int)BinaryPrimitives.ReverseEndianness(values[1]);
            for (int i = 2; i < values.Length; i++)
            {
                uint v = values[i];
                if (v == values[0])
                {
                    continue;
                }
                if (Enum.IsDefined(typeof(eFileType), v))
                {
                    types.Add((eFileType)v);
                }
            }
            fileType = types.ToArray();

            metadata = new Metadata(reader);

            findDefaultTracks();

            // Fast forward to the movie data; that box has the actual content we gonna play.
            // if( reader.currentBox != eBoxType.mdat )
            //	reader.moveToBox( eBoxType.mdat );

            m_videoTrack = new VideoTrack(this);
            m_audio      = new Audio(this);
        }
예제 #15
0
        public MP4AudioSampleEntry(Mp4Reader mp4, int bytesLeft) :
            base(mp4, ref bytesLeft)
        {
            Span <byte> bytes = stackalloc byte[bytesLeft];

            mp4.read(bytes);

            int  atomSize = BitConverter.ToInt32(bytes).endian();
            uint atomTag  = BitConverter.ToUInt32(bytes.Slice(4));

            switch (atomTag)
            {
            case (uint)eAudioBoxType.esds:
                break;

            default:
                throw new NotImplementedException();
            }

            // Skip header, version and flags
            bytes = bytes.Slice(12);

            Reader streamReader = new Reader(bytes);

            if (streamReader.EOF)
            {
                throw new ArgumentException($"The `esds` atom is empty");
            }

            // Read tag and size
            eDescriptorTag tag = streamReader.readTag();

            if (eDescriptorTag.ElementaryStream != tag)
            {
                throw new ArgumentException($"The `esds` atom is expected to contain an elementary stream descriptor, got { tag } instead");
            }
            int size = streamReader.readSize();

            // Create a reader for the content
            Reader esdReader = streamReader.readSubStream(size);

            // Read ElementaryStreamDescriptor
            ElementaryStreamDescriptor esd = esdReader.readStructure <ElementaryStreamDescriptor>();

            id       = esd.id;
            flags    = esd.flags;
            priority = esd.priority;

            if (esd.flags.HasFlag(eDescriptorFlags.DependentStream))
            {
                dependsOn = esdReader.readStructure <ushort>().endian();
            }

            if (esd.flags.HasFlag(eDescriptorFlags.URL))
            {
                byte        urlLength = esdReader.readByte();
                Span <byte> urlBuffer = stackalloc byte[urlLength];
                esdReader.readBytes(urlBuffer);
                unsafe
                {
                    fixed(byte *ptr = urlBuffer)
                    url = StringMarshal.copy(ptr, urlLength);
                }
            }

            if (esd.flags.HasFlag(eDescriptorFlags.OCRstream))
            {
                esId = esdReader.readStructure <ushort>().endian();
            }

            while (!esdReader.EOF)
            {
                tag  = esdReader.readTag();
                size = esdReader.readSize();
                Reader ss = esdReader.readSubStream(size);
                switch (tag)
                {
                case eDescriptorTag.DecoderConfiguration:
                    decoderConfig = new DecoderConfiguration(ref ss);
                    break;

                case eDescriptorTag.SyncLayerConfiguration:
                    syncLayerConfig = new SyncLayerConfiguration(ref ss);
                    break;

                case eDescriptorTag.ProfileLevelIndicationIndex:
                    profileLevelIndicationindex = ss.readByte();
                    break;

                case eDescriptorTag.IPIdentificationPointer:
                case eDescriptorTag.IPMPPointer:
                case eDescriptorTag.Language:
                case eDescriptorTag.QoS:
                case eDescriptorTag.Registration:
                    // Because of the way we implemented readSubStream, this break will skip them gracefully.
                    // TODO: support at least language, likely to be seen in the wild
                    break;

                default:
                    throw new NotSupportedException();
                }
            }

            // TODO: the esdReader has EOF, but the outer one, streamReader, might have not. It might contain moar stuff.
            // You can test for `if( !streamReader.EOF )` here, and parse even moar garbage carefully designed by these ISO/IEC committees and documented in many thousands of pages of these PDFs they sell.
        }
예제 #16
0
        public AVC1SampleEntry(Mp4Reader reader, int bytesLeft) :
            base(reader, ref bytesLeft)
        {
            var avcc = reader.readStructure <Structures.AVCDecoderConfigurationRecord>();

            if (avcc.boxType != eAVC1BoxType.avcC)
            {
                throw new NotImplementedException();
            }
            bytesLeft -= decoderConfigSizeof;

            profile = avcc.profileCode;
            profileCompatibility = avcc.profileCompatibility;
            levelCode            = avcc.levelCode;
            naluLengthSize       = checked ((byte)(avcc.lengthSizeMinusOne + 1));

            Span <byte> remainingStuff = stackalloc byte[bytesLeft];

            reader.read(remainingStuff);

            int readOffset = 0;

            sps = ContainerUtils.copyBlobs(avcc.numOfSequenceParameterSets, remainingStuff, ref readOffset);

            if (null == sps)
            {
                throw new ArgumentException("The file doesn't have an SPS");
            }
            // SpsData spsData = new SpsData( sps[ 0 ] );
            // File.WriteAllBytes( @"C:\Temp\2remove\h264\sps.bin", sps[ 0 ] );

            int ppsCount = remainingStuff[readOffset++];

            pps = ContainerUtils.copyBlobs(ppsCount, remainingStuff, ref readOffset);

            if (null == sps || null == pps)
            {
                throw new NotImplementedException("Vrmac Video only supports mp4 files with out-of-band SPS and PPS blobs, in the `avcC` atom of the file.");
            }
            if (sps.Length > 1 || pps.Length > 1)
            {
                throw new NotImplementedException("Vrmac Video only supports mp4 files with a single out-of-band SPS and PPS for the complete video.");                     // The video payload may include other PPS-es, these are fine.
            }
            if (readOffset >= remainingStuff.Length)
            {
                return;
            }

            remainingStuff = remainingStuff.Slice(readOffset);

            if (readOffset + decoderConfigSizeof < avcc.length)
            {
                // The spec I have says the files with profile IDs 100, 110, 122, 144 have this.
                // The mp4 file I use to test this code has 100, but misses this data.
                chromaFormat   = (eChromaFormat)(remainingStuff[0] & 3);
                bitDepthLuma   = (byte)((remainingStuff[1] & 7) + 8);
                bitDepthChroma = (byte)((remainingStuff[2] & 7) + 8);
                int numPpsEx = remainingStuff[3];
                readOffset = 4;                 // Resetting because sliced the span
                ppsExt     = ContainerUtils.copyBlobs(numPpsEx, remainingStuff, ref readOffset);

                remainingStuff = remainingStuff.Slice(readOffset);
            }
            else
            {
                // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Feature_support_in_particular_profiles
                chromaFormat   = eChromaFormat.c420;
                bitDepthLuma   = 8;
                bitDepthChroma = 8;
            }

            while (!remainingStuff.IsEmpty)
            {
                int          size = BitConverter.ToInt32(remainingStuff).endian();
                eAVC1BoxType code = (eAVC1BoxType)BitConverter.ToUInt32(remainingStuff.Slice(4));
                switch (code)
                {
                case eAVC1BoxType.btrt:
                    bitRate           = new MPEG4BitRateBox(remainingStuff);
                    m_maxBytesInFrame = bitRate.decodingBufferSize;
                    break;
                }
                remainingStuff = remainingStuff.Slice(size);
            }
        }