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); } }
static void readInfoHeader(Mp4Reader reader, out DateTime creationTime, out DateTime modificationTime, out TimeSpan duration, out CultureInfo culture, out uint timeScale) { uint ver = reader.readUInt(); switch (ver & 0xFF) { case 0: var v0 = reader.readStructure <MediaInfoV0>(); creationTime = v0.creationTime; modificationTime = v0.modificationTime; duration = v0.duration; culture = v0.culture; timeScale = v0.timeScale; return; case 1: var v1 = reader.readStructure <MediaInfoV1>(); creationTime = v1.creationTime; modificationTime = v1.modificationTime; duration = v1.duration; culture = v1.culture; timeScale = v1.timeScale; return; } throw new ApplicationException("Unsupported format version"); }
internal TrackHeader(Mp4Reader reader, uint timescale) { Debug.Assert(reader.currentBox == eBoxType.tkhd); uint versionAndFlags = reader.readUInt(); flags = (eTrackFlags)BinaryPrimitives.ReverseEndianness(versionAndFlags & 0xFFFFFF00u); switch (versionAndFlags & 0xFF) { case 0: var ver0 = reader.readStructure <TrackHeaderVersion0>(); ver0.parseHeader(timescale, out creationTime, out modificationTime, out id, out duration); ver0.parseCommon(out layer, out alternateGroup, out volume, out size); break; case 1: var ver1 = reader.readStructure <TrackHeaderVersion1>(); ver1.parseHeader(timescale, out creationTime, out modificationTime, out id, out duration); ver1.parseCommon(out layer, out alternateGroup, out volume, out size); break; default: throw new ArgumentException("Unsupported track version"); } }
static T[] readArray <T>(Mp4Reader reader) where T : unmanaged { sHeader header = reader.readStructure <sHeader>(); int count = header.entry_count.endian(); return(readArray <T>(reader, count)); }
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); }
public Ac3AudioSampleEntry(Mp4Reader mp4, int bytesLeft) : base(mp4, ref bytesLeft) { // configBlob = new byte[ bytesLeft ]; // mp4.read( configBlob.AsSpan() ); mp4.skipCurrentBox(); }
internal static uint readUInt(this Mp4Reader reader) { Span <byte> bytes = stackalloc byte[4]; reader.read(bytes); return(bytes.cast <uint>()[0]); }
public Metadata(Mp4Reader reader) { reader.moveToBox(eBoxType.moov); Debug.Assert(1 == reader.level); movieHeader = default; List <TrackMetadata> list = new List <TrackMetadata>(); foreach (eBoxType boxType in reader.readChildren()) { switch (boxType) { case eBoxType.mvhd: movieHeader = new MovieHeader(reader); break; case eBoxType.trak: list.Add(parseTrack(reader, movieHeader.timescale)); break; default: // e.g. udta, for optional user data. reader.skipCurrentBox(); break; } } tracks = list.ToArray(); }
public static IEnumerable <eBoxType> readChildren(this Mp4Reader reader) { int lvl = reader.level; Debug.Assert(lvl > 0); Debug.Assert(reader.currentBox.isContainer()); lvl--; while (true) { eBoxType boxType = reader.readBox(); // Console.WriteLine( "{0}: {1}", reader.currentBoxNames, boxType ); if (boxType == eBoxType.ChildContainerEnd) { if (reader.level == lvl) { yield break; } } else { yield return(boxType); } } }
static TrackMetadata parseTrack(Mp4Reader reader, uint timescale) { TrackHeader header = default; MediaInfo info = default; EditListBox editList = null; foreach (eBoxType boxType in reader.readChildren()) { switch (boxType) { case eBoxType.tkhd: header = new TrackHeader(reader, timescale); break; case eBoxType.mdia: info = new MediaInfo(reader); break; case eBoxType.edts: editList = EditListBox.load(reader); break; default: reader.skipCurrentBox(); break; } } iEditList el = Mpeg4EditList.create(editList, timescale, info.timeScale); return(new TrackMetadata(header, info, el)); }
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); }
internal SampleTable(Mp4Reader reader) { Debug.Assert(reader.currentBox == eBoxType.stbl); entries = null; timeToSample = null; sampleToChunk = null; sampleSize = null; chunkOffset = null; compositionToSample = null; syncSampleTable = null; foreach (eBoxType boxType in reader.readChildren()) { switch (boxType) { case eBoxType.stsd: entries = parseSampleTable(reader); break; case eBoxType.stts: timeToSample = TimingTables.readTimeToSample(reader); break; case eBoxType.ctts: compositionToSample = CompositionTimeDeltas.read(reader); break; case eBoxType.stsc: sampleToChunk = TimingTables.readSampleToChunk(reader); break; case eBoxType.stsz: sampleSize = TimingTables.readSampleSize(reader); break; case eBoxType.stz2: sampleSize = TimingTables.readSampleSizeCompact(reader); break; case eBoxType.stco: chunkOffset = TimingTables.readOffsets32(reader); break; case eBoxType.co64: chunkOffset = TimingTables.readOffsets64(reader); break; case eBoxType.stss: syncSampleTable = TimingTables.readSyncSample(reader); break; default: reader.skipCurrentBox(); break; } } }
public static ChunkOffsetTable readOffsets32(Mp4Reader reader) { Debug.Assert(reader.currentBox == eBoxType.stco); sHeader header = reader.readStructure <sHeader>(); int count = header.entry_count.endian(); uint[] entries = readArray <uint>(reader, count); return(new ChunkOffsetTable32(entries)); }
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)); }
public static SampleSizeTable readSampleSize(Mp4Reader reader) { Debug.Assert(reader.currentBox == eBoxType.stsz); sSampleSizeBox box = reader.readStructure <sSampleSizeBox>(); if (box.sample_size != 0) { return(new SampleSizeFixed(box.sample_size.endian(), box.sample_count.endian())); } return(SampleSizeTable.createVariable(reader, box.sample_count.endian())); }
public AudioSampleEntry(Mp4Reader reader, ref int bytesLeft) { var ss = reader.readStructure <Structures.AudioSampleEntry>(); bytesLeft -= Marshal.SizeOf <Structures.AudioSampleEntry>(); checked { channelCount = (byte)ss.channelcount.endian(); bitsPerSample = ss.samplesize.endian(); sampleRateInt = ss.sampleRate.endian(); } }
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); } }
internal VideoSampleEntry(Mp4Reader reader, ref int bytesLeft) { var ss = reader.readStructure <Structures.VisualSampleEntry>(); bytesLeft -= Marshal.SizeOf <Structures.VisualSampleEntry>(); sizePixels = ss.size; pixelsPerInch = ss.resolution; framesPerSample = ss.frameCount; unsafe { byte *comp = ss.compressorname; compressorName = StringMarshal.copy(comp, 32); } }
public static void moveToBox(this Mp4Reader reader, eBoxType boxType) { while (true) { var bt = reader.readBox(); if (bt == boxType) { return; } if (bt == eBoxType.Empty) { throw new EndOfStreamException(); } } }
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)); }
internal VideoInformation(Mp4Reader reader) { foreach (eBoxType boxType in reader.readChildren()) { switch (boxType) { default: // dinf is useless but mandatory in the spec. // vmhd is useless, as well. reader.skipCurrentBox(); break; case eBoxType.stbl: sampleTable = new SampleTable(reader); break; } } }
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); }
internal AudioInformation(Mp4Reader reader) { foreach (eBoxType boxType in reader.readChildren()) { switch (boxType) { case eBoxType.smhd: balance = readHeader(reader); break; default: // dinf is useless but mandatory in the spec reader.skipCurrentBox(); break; case eBoxType.stbl: sampleTable = new SampleTable(reader); break; } } }
internal MovieHeader(Mp4Reader reader) { Debug.Assert(reader.currentBox == eBoxType.mvhd); uint versionAndFlags = reader.readUInt(); switch (versionAndFlags & 0xFF) { case 0: var ver0 = reader.readStructure <MovieHeaderVersion0>(); ver0.parseHeader(out creationTime, out modificationTime, out duration, out timescale); ver0.parseCommon(out rate, out volume, out nextTrackId); break; case 1: var ver1 = reader.readStructure <MovieHeaderVersion1>(); ver1.parseHeader(out creationTime, out modificationTime, out duration, out timescale); ver1.parseCommon(out rate, out volume, out nextTrackId); break; default: throw new ArgumentException("Unsupported track version"); } }
internal MediaInfo(Mp4Reader reader) { creationTime = modificationTime = default; duration = default; culture = null; mediaHandler = default; mediaInformation = null; timeScale = 0; foreach (eBoxType boxType in reader.readChildren()) { switch (boxType) { case eBoxType.mdhd: readInfoHeader(reader, out creationTime, out modificationTime, out duration, out culture, out timeScale); break; case eBoxType.hdlr: mediaHandler = new MediaHandler(reader); break; case eBoxType.minf: switch (mediaHandler.mediaHandler) { case eMediaHandler.vide: case eMediaHandler.auxv: mediaInformation = new VideoInformation(reader); break; case eMediaHandler.soun: mediaInformation = new AudioInformation(reader); break; } break; } } }