diff --git a/io/src/main/java/org/red5/io/isobmff/atom/HVC1Box.java b/io/src/main/java/org/red5/io/isobmff/atom/HVC1Box.java
new file mode 100644
index 0000000000000000000000000000000000000000..f79daef0a11666b89f6089d9c19539413c745c5d
--- /dev/null
+++ b/io/src/main/java/org/red5/io/isobmff/atom/HVC1Box.java
@@ -0,0 +1,12 @@
+package org.red5.io.isobmff.atom;
+
+import org.jcodec.containers.mp4.boxes.Header;
+import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
+
+public class HVC1Box extends VideoSampleEntry {
+
+    public HVC1Box() {
+        super(new Header("hvc1"));
+    }
+
+}
diff --git a/io/src/main/java/org/red5/io/isobmff/atom/ShortEsdsBox.java b/io/src/main/java/org/red5/io/isobmff/atom/ShortEsdsBox.java
new file mode 100644
index 0000000000000000000000000000000000000000..9df7080c1bed80798cf0be92e7ea0cc6b357f074
--- /dev/null
+++ b/io/src/main/java/org/red5/io/isobmff/atom/ShortEsdsBox.java
@@ -0,0 +1,105 @@
+package org.red5.io.isobmff.atom;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+import org.jcodec.codecs.mpeg4.es.DecoderConfig;
+import org.jcodec.codecs.mpeg4.es.DecoderSpecific;
+import org.jcodec.codecs.mpeg4.es.Descriptor;
+import org.jcodec.codecs.mpeg4.es.DescriptorParser;
+import org.jcodec.codecs.mpeg4.es.ES;
+import org.jcodec.codecs.mpeg4.es.NodeDescriptor;
+import org.jcodec.codecs.mpeg4.es.SL;
+import org.jcodec.containers.mp4.boxes.FullBox;
+import org.jcodec.containers.mp4.boxes.Header;
+
+public class ShortEsdsBox extends FullBox {
+
+    private ByteBuffer streamInfo;
+
+    private int objectType;
+
+    private int bufSize;
+
+    private int maxBitrate;
+
+    private int avgBitrate;
+
+    private int trackId;
+
+    public static String fourcc() {
+        return "esds";
+    }
+
+    public ShortEsdsBox(Header atom) {
+        super(atom);
+    }
+
+    @Override
+    protected void doWrite(ByteBuffer out) {
+        super.doWrite(out);
+        if (streamInfo != null && streamInfo.remaining() > 0) {
+            ArrayList<Descriptor> l = new ArrayList<Descriptor>();
+            ArrayList<Descriptor> l1 = new ArrayList<Descriptor>();
+            l1.add(new DecoderSpecific(streamInfo));
+            l.add(new DecoderConfig(objectType, bufSize, maxBitrate, avgBitrate, l1));
+            l.add(new SL());
+            new ES(trackId, l).write(out);
+        } else {
+            ArrayList<Descriptor> l = new ArrayList<Descriptor>();
+            l.add(new DecoderConfig(objectType, bufSize, maxBitrate, avgBitrate, new ArrayList<Descriptor>()));
+            l.add(new SL());
+            new ES(trackId, l).write(out);
+        }
+    }
+
+    @Override
+    public int estimateSize() {
+        return 64;
+    }
+
+    @Override
+    public void parse(ByteBuffer input) {
+        super.parse(input);
+        ES es = (ES) DescriptorParser.read(input);
+        trackId = es.getTrackId();
+        DecoderConfig decoderConfig = NodeDescriptor.findByTag(es, DecoderConfig.tag());
+        objectType = decoderConfig.getObjectType();
+        bufSize = decoderConfig.getBufSize();
+        maxBitrate = decoderConfig.getMaxBitrate();
+        avgBitrate = decoderConfig.getAvgBitrate();
+        DecoderSpecific decoderSpecific = NodeDescriptor.findByTag(decoderConfig, DecoderSpecific.tag());
+        if (decoderSpecific != null) {
+            streamInfo = decoderSpecific.getData();
+        }
+    }
+
+    public boolean hasStreamInfo() {
+        return streamInfo != null;
+    }
+
+    public ByteBuffer getStreamInfo() {
+        return streamInfo;
+    }
+
+    public int getObjectType() {
+        return objectType;
+    }
+
+    public int getBufSize() {
+        return bufSize;
+    }
+
+    public int getMaxBitrate() {
+        return maxBitrate;
+    }
+
+    public int getAvgBitrate() {
+        return avgBitrate;
+    }
+
+    public int getTrackId() {
+        return trackId;
+    }
+
+}
diff --git a/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java b/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java
index 0f4cd79342b640babd32b658b4ed0ea7eb65344d..1a4bf5208bc95b1cb40b240e79300166089fa9b3 100644
--- a/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java
+++ b/io/src/main/java/org/red5/io/mp4/impl/MP4Reader.java
@@ -25,16 +25,19 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.mina.core.buffer.IoBuffer;
+import org.jcodec.codecs.h264.mp4.AvcCBox;
 import org.jcodec.codecs.mpeg4.mp4.EsdsBox;
 import org.jcodec.common.io.NIOUtils;
 import org.jcodec.common.io.SeekableByteChannel;
 import org.jcodec.containers.mp4.MP4TrackType;
 import org.jcodec.containers.mp4.MP4Util;
 import org.jcodec.containers.mp4.MP4Util.Movie;
+import org.jcodec.containers.mp4.boxes.AVC1Box;
 import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
 import org.jcodec.containers.mp4.boxes.Box;
 import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
 import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
+import org.jcodec.containers.mp4.boxes.ColorExtension;
 import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox;
 import org.jcodec.containers.mp4.boxes.HandlerBox;
 import org.jcodec.containers.mp4.boxes.MediaBox;
@@ -43,17 +46,18 @@ import org.jcodec.containers.mp4.boxes.MediaInfoBox;
 import org.jcodec.containers.mp4.boxes.MovieBox;
 import org.jcodec.containers.mp4.boxes.MovieHeaderBox;
 import org.jcodec.containers.mp4.boxes.NodeBox;
+import org.jcodec.containers.mp4.boxes.PixelAspectExt;
 import org.jcodec.containers.mp4.boxes.SampleDescriptionBox;
 import org.jcodec.containers.mp4.boxes.SampleSizesBox;
 import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
+import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
 import org.jcodec.containers.mp4.boxes.SyncSamplesBox;
 import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
+import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
 import org.jcodec.containers.mp4.boxes.TrackHeaderBox;
 import org.jcodec.containers.mp4.boxes.TrakBox;
 import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
 import org.jcodec.containers.mp4.boxes.WaveExtension;
-import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
-import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
 import org.red5.io.IStreamableFile;
 import org.red5.io.ITag;
 import org.red5.io.ITagReader;
@@ -61,6 +65,8 @@ import org.red5.io.IoConstants;
 import org.red5.io.amf.Output;
 import org.red5.io.flv.IKeyFrameDataAnalyzer;
 import org.red5.io.flv.impl.Tag;
+import org.red5.io.isobmff.atom.HVC1Box;
+import org.red5.io.isobmff.atom.ShortEsdsBox;
 import org.red5.io.mp4.MP4Frame;
 import org.red5.io.utils.HexDump;
 import org.slf4j.Logger;
@@ -343,7 +349,7 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
                                                         "tag": "stsd",
                                                         "boxes": [
                                                             {
-                                                            "tag": "mp4a | mp4v | avc1 | hvc1",
+                                                            "tag": "mp4a | ac-3 | mp4v | avc1 | hvc1",
                                                             "boxes": [
                                                                 {
                                                                 "tag": "esds"
@@ -358,12 +364,27 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
                                                             audioCodecId = "mp4a";
                                                             processAudioSampleEntry((AudioSampleEntry) stbox, scale.get());
                                                             break;
+                                                        case "ac-3":
+                                                            audioCodecId = "ac-3";
+                                                            processAudioSampleEntry((AudioSampleEntry) stbox, scale.get());
+                                                            break;
                                                         case "mp4v":
                                                             videoCodecId = "mp4v";
                                                             processVideoSampleEntry((VideoSampleEntry) stbox, scale.get());
                                                             break;
                                                         case "avc1":
                                                             videoCodecId = "avc1";
+                                                            //AVC1Box avc1 = Box.asBox(AVC1Box.class, stbox);
+                                                            processVideoSampleEntry((VideoSampleEntry) stbox, scale.get());
+                                                            break;
+                                                        case "hev1":
+                                                            videoCodecId = "hev1";
+                                                            //HEV1Box hev1 = Box.asBox(HEV1Box.class, stbox);
+                                                            //processVideoSampleEntry((VideoSampleEntry) stbox, scale.get());
+                                                            break;
+                                                        case "hvc1":
+                                                            videoCodecId = "hvc1";
+                                                            //HVC1Box hvc1 = Box.asBox(HVC1Box.class, stbox);
                                                             processVideoSampleEntry((VideoSampleEntry) stbox, scale.get());
                                                             break;
                                                         default:
@@ -488,7 +509,8 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
                                             case "ctts": // ctts - (composition) time to sample
                                                 log.debug("Composition time to sample atom found");
                                                 CompositionOffsetsBox ctts = (CompositionOffsetsBox) sbox;
-                                                compositionTimes = List.of(ctts.getEntries());
+                                                compositionTimes = new LinkedList<>();
+                                                compositionTimes.addAll(List.of(ctts.getEntries()));
                                                 log.debug("Record count: {}", compositionTimes.size());
                                                 if (log.isTraceEnabled()) {
                                                     for (CompositionOffsetsBox.Entry rec : compositionTimes) {
@@ -572,13 +594,17 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
             log.debug("Audio sample entry box: {}", box);
             switch (box.getFourcc()) {
                 case "esds":
-                    if (box.estimateSize() > 0) {
-
-                    }
-                    EsdsBox esds = Box.asBox(EsdsBox.class, box);
+                    long esdsBodySize = box.getHeader().getBodySize();
+                    log.debug("esds body size: {}", esdsBodySize);
+                    // less than 28 bytes doesn't contain DecoderSpecific content
+                    ShortEsdsBox esds = Box.asBox(ShortEsdsBox.class, box);
                     log.debug("Process {} obj: {} avg bitrate: {} max bitrate: {}", esds.getFourcc(), esds.getObjectType(), esds.getAvgBitrate(), esds.getMaxBitrate());
                     // http://stackoverflow.com/questions/3987850/mp4-atom-how-to-discriminate-the-audio-codec-is-it-aac-or-mp3
-                    audioDecoderBytes = esds.getStreamInfo().array();
+                    if (esds.hasStreamInfo()) {
+                        audioDecoderBytes = esds.getStreamInfo().array();
+                    } else {
+                        audioDecoderBytes = EMPTY_AAC;
+                    }
                     log.debug("Audio config bytes: {}", HexDump.byteArrayToHexString(audioDecoderBytes));
                     // the first 5 (0-4) bits tell us about the coder used for aacaot/aottype http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio 0 - NULL 1 - AAC Main (a deprecated AAC profile
                     // from MPEG-2) 2 - AAC LC or backwards compatible HE-AAC 3 - AAC Scalable Sample Rate 4 - AAC LTP (a replacement for AAC Main, rarely used) 5 - HE-AAC explicitly signaled
@@ -617,14 +643,17 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
                     }
                     log.debug("Audio coder type: {} {} id: {}", audioCoderType, Integer.toBinaryString(audioCoderType), audioCodecId);
                     break;
+                case "dac3": // {"tag":"ac-3","boxes": [{"tag":"dac3"}]}
+                    //AC3SpecificBox dac3 = Box.asBox(AC3SpecificBox.class, box);
+                    break;
                 case "wave":
                     // check for decompression param atom
                     WaveExtension wave = Box.asBox(WaveExtension.class, box);
                     log.debug("wave atom found");
                     // wave/esds
-                    esds = wave.getBoxes().stream().filter(b -> b instanceof EsdsBox).map(b -> (EsdsBox) b).findFirst().orElse(null);
-                    if (esds != null) {
-                        log.debug("Process {} obj: {} avg bitrate: {} max bitrate: {}", esds.getFourcc(), esds.getObjectType(), esds.getAvgBitrate(), esds.getMaxBitrate());
+                    EsdsBox wesds = wave.getBoxes().stream().filter(b -> b instanceof EsdsBox).map(b -> (EsdsBox) b).findFirst().orElse(null);
+                    if (wesds != null) {
+                        log.debug("Process {} obj: {} avg bitrate: {} max bitrate: {}", wesds.getFourcc(), wesds.getObjectType(), wesds.getAvgBitrate(), wesds.getMaxBitrate());
                     } else {
                         log.debug("esds not found in wave");
                         // mp4a/esds
@@ -632,6 +661,9 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
                         //esds = mp4a.getBoxes(ESDescriptorBox.class).get(0);
                     }
                     break;
+                case "btrt":
+                    //BitRateBox btrt = Box.asBox(BitRateBox.class, box);
+                    break;
                 default:
                     log.warn("Unhandled sample desc extension: {}", box);
                     break;
@@ -661,37 +693,34 @@ public class MP4Reader implements IoConstants, ITagReader, IKeyFrameDataAnalyzer
                     videoDecoderBytes = esds.getStreamInfo().array();
                     log.debug("Video config bytes: {}", HexDump.byteArrayToHexString(videoDecoderBytes));
                     break;
-                /*
-                stsd child: {"tag":"avc1","boxes": [{"tag":"avcC"},{"tag":"btrt"}]}
-                Compressor:  frame count: 1
-                Video sample entry box: {"tag":"avcC"}
-                Unhandled sample desc extension: {"tag":"avcC"}
-                Video sample entry box: {"tag":"btrt"}
-                Unhandled sample desc extension: {"tag":"btrt"}
-
-
-                    case "avcC": // videoCodecId = "avc1"
-                    AvcConfigurationBox avc1 = vse.getBoxes(AvcConfigurationBox.class).get(0);
-                    avcLevel = avc1.getAvcLevelIndication();
-                    log.debug("AVC level: {}", avcLevel);
-                    avcProfile = avc1.getAvcProfileIndication();
-                    log.debug("AVC Profile: {}", avcProfile);
-                    AvcDecoderConfigurationRecord avcC = avc1.getavcDecoderConfigurationRecord();
-                    if (avcC != null) {
-                        long videoConfigContentSize = avcC.getContentSize();
-                        log.debug("AVCC size: {}", videoConfigContentSize);
-                        ByteBuffer byteBuffer = ByteBuffer.allocate((int) videoConfigContentSize);
-                        avc1.avcDecoderConfigurationRecord.getContent(byteBuffer);
-                        byteBuffer.flip();
-                        videoDecoderBytes = new byte[byteBuffer.limit()];
-                        byteBuffer.get(videoDecoderBytes);
-                    } else {
-                        // quicktime and ipods use a pixel aspect atom (pasp)
-                        // since we have no avcC check for this and avcC may be a child
-                        log.warn("avcC atom not found; we may need to modify this to support pasp atom");
-                    }
+                case "avcC": // videoCodecId = "avc1"
+                    //AVC1Box avc1 = Box.asBox(AVC1Box.class, vse);
+                    AvcCBox avcC = Box.asBox(AvcCBox.class, box);
+                    avcLevel = avcC.getLevel();
+                    avcProfile = avcC.getProfile();
+                    log.debug("Process {} level: {} nal len: {} profile: {} compat: {}", avcC.getFourcc(), avcLevel, avcC.getNalLengthSize(), avcProfile, avcC.getProfileCompat());
+                    avcC.getSpsList().forEach(sps -> log.debug("SPS: {}", sps));
+                    avcC.getPpsList().forEach(pps -> log.debug("PPS: {}", pps));
+                    break;
+                case "hvcC": // videoCodecId = "hvc1"
+                    // hvc1 size 682 offset 581
+                    //HVC1Box avc1 = Box.asBox(HVC1Box.class, vse);
+                    // hvcC size 574 offset 667
+                    //HvcCBox hvcC = Box.asBox(HvcCBox.class, box);
+                    //HvcCBox hvcC = Box.asBox(HvcCBox.class, box);
+                    break;
+                case "btrt":
+                    //BitRateBox btrt = Box.asBox(BitRateBox.class, box);
+                    break;
+                case "pasp":
+                    PixelAspectExt pasp = Box.asBox(PixelAspectExt.class, box);
+                    log.debug("Process {} hSpacing: {} vSpacing: {}", pasp.getFourcc(), pasp.gethSpacing(), pasp.getvSpacing());
+                    break;
+                case "colr": // color
+                    // colr size 18 offset 1241
+                    ColorExtension colr = Box.asBox(ColorExtension.class, box);
+                    log.debug("Process {} primaries: {} transfer: {} matrix: {}", colr.getFourcc(), colr.getPrimariesIndex(), colr.getTransferFunctionIndex(), colr.getMatrixIndex());
                     break;
-                */
                 default:
                     log.warn("Unhandled sample desc extension: {}", box);
                     break;
diff --git a/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java b/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java
index 2bf3652e10e5442a97d0063ca936f37d523469ff..d4c0f6a5179c9157127c3fecbaa1e9639dd868ca 100644
--- a/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java
+++ b/io/src/test/java/org/red5/io/mp4/impl/MP4ReaderTest.java
@@ -26,10 +26,12 @@ public class MP4ReaderTest extends TestCase {
     @Test
     public void testCtor() throws Exception {
         // use for the internal unit tests
-        //File file = new File("target/test-classes/fixtures/bbb.mp4");
-        File file = new File("/media/mondain/terrorbyte/Videos/bbb_sunflower_2160p_60fps_normal.mp4");
-        //File file = new File("target/test-classes/fixtures/sample.mp4");
-        //File file = new File("target/test-classes/fixtures/MOV1.MOV");
+        //File file = new File("target/test-classes/fixtures/bbb.mp4"); // non-avc1 h264 video / aac audio
+        File file = new File("target/test-classes/fixtures/mov_h264.mp4"); // avc1 h264 video / aac audio
+        //File file = new File("target/test-classes/fixtures/mov_h265.mp4"); // hev1 h265 video / aac audio
+        //File file = new File("/media/mondain/terrorbyte/Videos/bbb_sunflower_2160p_60fps_normal.mp4"); // h264 video / ac-3 audio
+        //File file = new File("target/test-classes/fixtures/sample.mp4"); // non-avc1 h264 video / aac audio
+        //File file = new File("target/test-classes/fixtures/MOV1.MOV"); // hcv1 h265 video / aac audio
         // test clips for issues/bugs
         // https://code.google.com/p/red5/issues/detail?id=141
         //File file = new File("target/test-classes/fixtures/test_480_aac.f4v");
diff --git a/io/src/test/resources/fixtures/ffmpeg b/io/src/test/resources/fixtures/ffmpeg
new file mode 100755
index 0000000000000000000000000000000000000000..a138f80301f58e657a51e48a4d06bb2d6fd49094
Binary files /dev/null and b/io/src/test/resources/fixtures/ffmpeg differ
diff --git a/io/src/test/resources/fixtures/mov_h264.mp4 b/io/src/test/resources/fixtures/mov_h264.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..c1d04bd619702c67db99dbc0a8c8817eba0ceddb
Binary files /dev/null and b/io/src/test/resources/fixtures/mov_h264.mp4 differ
diff --git a/io/src/test/resources/fixtures/mov_h265.mp4 b/io/src/test/resources/fixtures/mov_h265.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..98b1e129387b5d0e57ed5058e02b398641412caf
Binary files /dev/null and b/io/src/test/resources/fixtures/mov_h265.mp4 differ