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(); } }
static int parseSampleTableHeader(Mp4Reader reader) { Span <byte> span = stackalloc byte[8]; reader.read(span); return(BitConverter.ToInt32(span.Slice(4)).endian()); }
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); }
internal static uint readUInt(this Mp4Reader reader) { Span <byte> bytes = stackalloc byte[4]; reader.read(bytes); return(bytes.cast <uint>()[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); } }
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()); }
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()); }
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); }
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)); }
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); } }
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); }
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); }
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)); }
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); }
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. }
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); } }