/// <summary> /// Loads M4ESDAtom atom from the input bitstream. /// </summary> /// <param name="bitstream">The input bitstream.</param> /// <returns>The number of bytes loaded.</returns> public long create_esd_atom(Mp4DataStream bitstream) { create_full_atom(bitstream); esd_descriptor = Mp4Descriptor.CreateDescriptor(bitstream); _bytesRead += esd_descriptor.BytesRead; return _bytesRead; }
/// <summary> /// This handles the moov atom being at the beginning or end of the file, so the mdat may also be before or after the moov atom. /// </summary> public void DecodeHeader() { try { // the first atom will/should be the type Mp4Atom type = Mp4Atom.CreateAtom(_inputStream); // expect ftyp #if LOGGING log.Debug(string.Format("Type {0}", type)); #endif // keep a running count of the number of atoms found at the "top" levels int topAtoms = 0; // we want a moov and an mdat, anything else throw the invalid file type error while (topAtoms < 2) { Mp4Atom atom = Mp4Atom.CreateAtom(_inputStream); switch (atom.Type) { case 1836019574: //moov topAtoms++; Mp4Atom moov = atom; // expect moov #if LOGGING log.Debug(string.Format("Type {0}", moov)); #endif //log.debug("moov children: {}", moov.getChildren()); _moovOffset = _inputStream.Offset - moov.Size; Mp4Atom mvhd = moov.Lookup(Mp4Atom.TypeToInt("mvhd"), 0); if (mvhd != null) { #if LOGGING log.Debug("Movie header atom found"); #endif //get the initial timescale _timeScale = mvhd.TimeScale; _duration = mvhd.Duration; #if LOGGING log.Debug(string.Format("Time scale {0} Duration {1}", _timeScale, _duration)); #endif } /* nothing needed here yet * MP4Atom meta = moov.lookup(MP4Atom.typeToInt("meta"), 0); * if (meta != null) { * log.debug("Meta atom found"); * log.debug("{}", ToStringBuilder.reflectionToString(meta)); * } */ Mp4Atom trak = moov.Lookup(Mp4Atom.TypeToInt("trak"), 0); if (trak != null) { #if LOGGING log.Debug("Track atom found"); #endif //log.debug("trak children: {}", trak.getChildren()); // trak: tkhd, edts, mdia Mp4Atom edts = trak.Lookup(Mp4Atom.TypeToInt("edts"), 0); if (edts != null) { #if LOGGING log.Debug("Edit atom found"); #endif //log.debug("edts children: {}", edts.getChildren()); } Mp4Atom mdia = trak.Lookup(Mp4Atom.TypeToInt("mdia"), 0); if (mdia != null) { #if LOGGING log.Debug("Media atom found"); #endif // mdia: mdhd, hdlr, minf int scale = 0; //get the media header atom Mp4Atom mdhd = mdia.Lookup(Mp4Atom.TypeToInt("mdhd"), 0); if (mdhd != null) { #if LOGGING log.Debug("Media data header atom found"); #endif //this will be for either video or audio depending media info scale = mdhd.TimeScale; #if LOGGING log.Debug(string.Format("Time scale {0}", scale)); #endif } Mp4Atom hdlr = mdia.Lookup(Mp4Atom.TypeToInt("hdlr"), 0); if (hdlr != null) { #if LOGGING log.Debug("Handler ref atom found"); #endif // soun or vide #if LOGGING log.Debug(string.Format("Handler type: {0}", Mp4Atom.IntToType(hdlr.HandlerType))); #endif String hdlrType = Mp4Atom.IntToType(hdlr.HandlerType); if ("soun".Equals(hdlrType)) { if (scale > 0) { _audioTimeScale = scale * 1.0; #if LOGGING log.Debug(string.Format("Audio time scale: {0}", _audioTimeScale)); #endif } } } Mp4Atom minf = mdia.Lookup(Mp4Atom.TypeToInt("minf"), 0); if (minf != null) { #if LOGGING log.Debug("Media info atom found"); #endif // minf: (audio) smhd, dinf, stbl / (video) vmhd, // dinf, stbl Mp4Atom smhd = minf.Lookup(Mp4Atom.TypeToInt("smhd"), 0); if (smhd != null) { #if LOGGING log.Debug("Sound header atom found"); #endif Mp4Atom dinf = minf.Lookup(Mp4Atom.TypeToInt("dinf"), 0); if (dinf != null) { #if LOGGING log.Debug("Data info atom found"); #endif // dinf: dref //log.Debug("Sound dinf children: {}", dinf.getChildren()); Mp4Atom dref = dinf.Lookup(Mp4Atom.TypeToInt("dref"), 0); if (dref != null) { #if LOGGING log.Debug("Data reference atom found"); #endif } } Mp4Atom stbl = minf.Lookup(Mp4Atom.TypeToInt("stbl"), 0); if (stbl != null) { #if LOGGING log.Debug("Sample table atom found"); #endif // stbl: stsd, stts, stss, stsc, stsz, stco, // stsh //log.debug("Sound stbl children: {}", stbl.getChildren()); // stsd - sample description // stts - time to sample // stsc - sample to chunk // stsz - sample size // stco - chunk offset //stsd - has codec child Mp4Atom stsd = stbl.Lookup(Mp4Atom.TypeToInt("stsd"), 0); if (stsd != null) { //stsd: mp4a #if LOGGING log.Debug("Sample description atom found"); #endif Mp4Atom mp4a = stsd.Children[0]; //could set the audio codec here SetAudioCodecId(Mp4Atom.IntToType(mp4a.Type)); //log.debug("{}", ToStringBuilder.reflectionToString(mp4a)); #if LOGGING log.Debug(string.Format("Sample size: {0}", mp4a.SampleSize)); #endif int ats = mp4a.TimeScale; //skip invalid audio time scale if (ats > 0) { _audioTimeScale = ats * 1.0; } _audioChannels = mp4a.ChannelCount; #if LOGGING log.Debug(string.Format("Sample rate (audio time scale): {0}", _audioTimeScale)); #endif #if LOGGING log.Debug(string.Format("Channels: {0}", _audioChannels)); #endif //mp4a: esds if (mp4a.Children.Count > 0) { #if LOGGING log.Debug("Elementary stream descriptor atom found"); #endif Mp4Atom esds = mp4a.Children[0]; //log.debug("{}", ToStringBuilder.reflectionToString(esds)); Mp4Descriptor descriptor = esds.EsdDescriptor; //log.debug("{}", ToStringBuilder.reflectionToString(descriptor)); if (descriptor != null) { List <Mp4Descriptor> children = descriptor.Children; for (int e = 0; e < children.Count; e++) { Mp4Descriptor descr = children[e]; //log.debug("{}", ToStringBuilder.reflectionToString(descr)); if (descr.Children.Count > 0) { List <Mp4Descriptor> children2 = descr.Children; for (int e2 = 0; e2 < children2.Count; e2++) { Mp4Descriptor descr2 = children2[e2]; //log.debug("{}", ToStringBuilder.reflectionToString(descr2)); if (descr2.Type == Mp4Descriptor.MP4DecSpecificInfoDescriptorTag) { //we only want the MP4DecSpecificInfoDescriptorTag _audioDecoderBytes = descr2.DSID; //compare the bytes to get the aacaot/aottype //match first byte switch (_audioDecoderBytes[0]) { case 0x12: default: //AAC LC - 12 10 _audioCodecType = 1; break; case 0x0a: //AAC Main - 0A 10 _audioCodecType = 0; break; case 0x11: case 0x13: //AAC LC SBR - 11 90 & 13 xx _audioCodecType = 2; break; } //we want to break out of top level for loop e = 99; break; } } } } } } } //stsc - has Records Mp4Atom stsc = stbl.Lookup(Mp4Atom.TypeToInt("stsc"), 0); if (stsc != null) { #if LOGGING log.Debug("Sample to chunk atom found"); #endif _audioSamplesToChunks = stsc.Records; #if LOGGING log.Debug(string.Format("Record count: {0}", _audioSamplesToChunks.Count)); #endif Mp4Atom.Record rec = _audioSamplesToChunks[0]; #if LOGGING log.Debug(string.Format("Record data: Description index={0} Samples per chunk={1}", rec.SampleDescriptionIndex, rec.SamplesPerChunk)); #endif } //stsz - has Samples Mp4Atom stsz = stbl.Lookup(Mp4Atom.TypeToInt("stsz"), 0); if (stsz != null) { #if LOGGING log.Debug("Sample size atom found"); #endif _audioSamples = stsz.Samples; //vector full of integers #if LOGGING log.Debug(string.Format("Sample size: {0}", stsz.SampleSize)); #endif #if LOGGING log.Debug(string.Format("Sample count: {0}", _audioSamples.Count)); #endif } //stco - has Chunks Mp4Atom stco = stbl.Lookup(Mp4Atom.TypeToInt("stco"), 0); if (stco != null) { #if LOGGING log.Debug("Chunk offset atom found"); #endif //vector full of integers _audioChunkOffsets = stco.Chunks; #if LOGGING log.Debug(string.Format("Chunk count: {0}", _audioChunkOffsets.Count)); #endif } //stts - has TimeSampleRecords Mp4Atom stts = stbl.Lookup(Mp4Atom.TypeToInt("stts"), 0); if (stts != null) { #if LOGGING log.Debug("Time to sample atom found"); #endif List <Mp4Atom.TimeSampleRecord> records = stts.TimeToSamplesRecords; #if LOGGING log.Debug(string.Format("Record count: {0}", records.Count)); #endif Mp4Atom.TimeSampleRecord rec = records[0]; #if LOGGING log.Debug(string.Format("Record data: Consecutive samples={0} Duration={1}", rec.ConsecutiveSamples, rec.SampleDuration)); #endif //if we have 1 record then all samples have the same duration if (records.Count > 1) { //TODO: handle audio samples with varying durations #if LOGGING log.Debug("Audio samples have differing durations, audio playback may fail"); #endif } _audioSampleDuration = rec.SampleDuration; } } } } } } //real duration StringBuilder sb = new StringBuilder(); double clipTime = ((double)_duration / (double)_timeScale); #if LOGGING log.Debug(string.Format("Clip time: {0}", clipTime)); #endif int minutes = (int)(clipTime / 60); if (minutes > 0) { sb.Append(minutes); sb.Append('.'); } //formatter for seconds / millis //NumberFormat df = DecimalFormat.getInstance(); //df.setMaximumFractionDigits(2); //sb.append(df.format((clipTime % 60))); sb.Append(clipTime % 60); _formattedDuration = sb.ToString(); #if LOGGING log.Debug(string.Format("Time: {0}", _formattedDuration)); #endif break; case 1835295092: //mdat topAtoms++; long dataSize = 0L; Mp4Atom mdat = atom; dataSize = mdat.Size; //log.debug("{}", ToStringBuilder.reflectionToString(mdat)); _mdatOffset = _inputStream.Offset - dataSize; #if LOGGING log.Debug(string.Format("File size: {0} mdat size: {1}", _file.Length, dataSize)); #endif break; case 1718773093: //free case 2003395685: //wide break; default: #if LOGGING log.Warn(string.Format("Unexpected atom: {}", Mp4Atom.IntToType(atom.Type))); #endif break; } } //add the tag name (size) to the offsets _moovOffset += 8; _mdatOffset += 8; #if LOGGING log.Debug(string.Format("Offsets moov: {0} mdat: {1}", _moovOffset, _mdatOffset)); #endif } catch (Exception ex) { #if LOGGING log.Error("Exception decoding header / atoms", ex); #endif } }