Home | History | Annotate | Download | only in tracks
      1 package com.googlecode.mp4parser.authoring.tracks;
      2 
      3 import com.coremedia.iso.boxes.*;
      4 import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
      5 import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
      6 import com.googlecode.mp4parser.authoring.AbstractTrack;
      7 import com.googlecode.mp4parser.authoring.TrackMetaData;
      8 import com.googlecode.mp4parser.h264.model.PictureParameterSet;
      9 import com.googlecode.mp4parser.h264.model.SeqParameterSet;
     10 import com.googlecode.mp4parser.h264.read.CAVLCReader;
     11 
     12 import java.io.ByteArrayInputStream;
     13 import java.io.IOException;
     14 import java.io.InputStream;
     15 import java.nio.ByteBuffer;
     16 import java.util.ArrayList;
     17 import java.util.Date;
     18 import java.util.LinkedList;
     19 import java.util.List;
     20 import java.util.logging.Logger;
     21 
     22 /**
     23  * The <code>H264TrackImpl</code> creates a <code>Track</code> from an H.264
     24  * Annex B file.
     25  */
     26 public class H264TrackImpl extends AbstractTrack {
     27     private static final Logger LOG = Logger.getLogger(H264TrackImpl.class.getName());
     28 
     29     TrackMetaData trackMetaData = new TrackMetaData();
     30     SampleDescriptionBox sampleDescriptionBox;
     31 
     32     private ReaderWrapper reader;
     33     private List<ByteBuffer> samples;
     34     boolean readSamples = false;
     35 
     36     List<TimeToSampleBox.Entry> stts;
     37     List<CompositionTimeToSample.Entry> ctts;
     38     List<SampleDependencyTypeBox.Entry> sdtp;
     39     List<Integer> stss;
     40 
     41     SeqParameterSet seqParameterSet = null;
     42     PictureParameterSet pictureParameterSet = null;
     43     LinkedList<byte[]> seqParameterSetList = new LinkedList<byte[]>();
     44     LinkedList<byte[]> pictureParameterSetList = new LinkedList<byte[]>();
     45 
     46     private int width;
     47     private int height;
     48     private int timescale;
     49     private int frametick;
     50     private int currentScSize;
     51     private int prevScSize;
     52 
     53     private SEIMessage seiMessage;
     54     int frameNrInGop = 0;
     55     private boolean determineFrameRate = true;
     56     private String lang = "und";
     57 
     58     public H264TrackImpl(InputStream inputStream, String lang, long timescale) throws IOException {
     59         this.lang = lang;
     60         if (timescale > 1000) {
     61             timescale = timescale; //e.g. 23976
     62             frametick = 1000;
     63             determineFrameRate = false;
     64         } else {
     65             throw new IllegalArgumentException("Timescale must be specified in milliseconds!");
     66         }
     67         parse(inputStream);
     68     }
     69 
     70     public H264TrackImpl(InputStream inputStream, String lang) throws IOException {
     71         this.lang = lang;
     72         parse(inputStream);
     73     }
     74 
     75     public H264TrackImpl(InputStream inputStream) throws IOException {
     76         parse(inputStream);
     77     }
     78 
     79     private void parse(InputStream inputStream) throws IOException {
     80         this.reader = new ReaderWrapper(inputStream);
     81         stts = new LinkedList<TimeToSampleBox.Entry>();
     82         ctts = new LinkedList<CompositionTimeToSample.Entry>();
     83         sdtp = new LinkedList<SampleDependencyTypeBox.Entry>();
     84         stss = new LinkedList<Integer>();
     85 
     86         samples = new LinkedList<ByteBuffer>();
     87         if (!readSamples()) {
     88             throw new IOException();
     89         }
     90 
     91         if (!readVariables()) {
     92             throw new IOException();
     93         }
     94 
     95         sampleDescriptionBox = new SampleDescriptionBox();
     96         VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1");
     97         visualSampleEntry.setDataReferenceIndex(1);
     98         visualSampleEntry.setDepth(24);
     99         visualSampleEntry.setFrameCount(1);
    100         visualSampleEntry.setHorizresolution(72);
    101         visualSampleEntry.setVertresolution(72);
    102         visualSampleEntry.setWidth(width);
    103         visualSampleEntry.setHeight(height);
    104         visualSampleEntry.setCompressorname("AVC Coding");
    105 
    106         AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox();
    107 
    108         avcConfigurationBox.setSequenceParameterSets(seqParameterSetList);
    109         avcConfigurationBox.setPictureParameterSets(pictureParameterSetList);
    110         avcConfigurationBox.setAvcLevelIndication(seqParameterSet.level_idc);
    111         avcConfigurationBox.setAvcProfileIndication(seqParameterSet.profile_idc);
    112         avcConfigurationBox.setBitDepthLumaMinus8(seqParameterSet.bit_depth_luma_minus8);
    113         avcConfigurationBox.setBitDepthChromaMinus8(seqParameterSet.bit_depth_chroma_minus8);
    114         avcConfigurationBox.setChromaFormat(seqParameterSet.chroma_format_idc.getId());
    115         avcConfigurationBox.setConfigurationVersion(1);
    116         avcConfigurationBox.setLengthSizeMinusOne(3);
    117         avcConfigurationBox.setProfileCompatibility(seqParameterSetList.get(0)[1]);
    118 
    119         visualSampleEntry.addBox(avcConfigurationBox);
    120         sampleDescriptionBox.addBox(visualSampleEntry);
    121 
    122         trackMetaData.setCreationTime(new Date());
    123         trackMetaData.setModificationTime(new Date());
    124         trackMetaData.setLanguage(lang);
    125         trackMetaData.setTimescale(timescale);
    126         trackMetaData.setWidth(width);
    127         trackMetaData.setHeight(height);
    128     }
    129 
    130     public SampleDescriptionBox getSampleDescriptionBox() {
    131         return sampleDescriptionBox;
    132     }
    133 
    134     public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
    135         return stts;
    136     }
    137 
    138     public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
    139         return ctts;
    140     }
    141 
    142     public long[] getSyncSamples() {
    143         long[] returns = new long[stss.size()];
    144         for (int i = 0; i < stss.size(); i++) {
    145             returns[i] = stss.get(i);
    146         }
    147         return returns;
    148     }
    149 
    150     public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
    151         return sdtp;
    152     }
    153 
    154     public TrackMetaData getTrackMetaData() {
    155         return trackMetaData;
    156     }
    157 
    158     public String getHandler() {
    159         return "vide";
    160     }
    161 
    162     public List<ByteBuffer> getSamples() {
    163         return samples;
    164     }
    165 
    166     public AbstractMediaHeaderBox getMediaHeaderBox() {
    167         return new VideoMediaHeaderBox();
    168     }
    169 
    170     public SubSampleInformationBox getSubsampleInformationBox() {
    171         return null;
    172     }
    173 
    174     private boolean readVariables() {
    175         width = (seqParameterSet.pic_width_in_mbs_minus1 + 1) * 16;
    176         int mult = 2;
    177         if (seqParameterSet.frame_mbs_only_flag) {
    178             mult = 1;
    179         }
    180         height = 16 * (seqParameterSet.pic_height_in_map_units_minus1 + 1) * mult;
    181         if (seqParameterSet.frame_cropping_flag) {
    182             int chromaArrayType = 0;
    183             if (seqParameterSet.residual_color_transform_flag == false) {
    184                 chromaArrayType = seqParameterSet.chroma_format_idc.getId();
    185             }
    186             int cropUnitX = 1;
    187             int cropUnitY = mult;
    188             if (chromaArrayType != 0) {
    189                 cropUnitX = seqParameterSet.chroma_format_idc.getSubWidth();
    190                 cropUnitY = seqParameterSet.chroma_format_idc.getSubHeight() * mult;
    191             }
    192 
    193             width -= cropUnitX * (seqParameterSet.frame_crop_left_offset + seqParameterSet.frame_crop_right_offset);
    194             height -= cropUnitY * (seqParameterSet.frame_crop_top_offset + seqParameterSet.frame_crop_bottom_offset);
    195         }
    196         return true;
    197     }
    198 
    199     private boolean findNextStartcode() throws IOException {
    200         byte[] test = new byte[]{-1, -1, -1, -1};
    201 
    202         int c;
    203         while ((c = reader.read()) != -1) {
    204             test[0] = test[1];
    205             test[1] = test[2];
    206             test[2] = test[3];
    207             test[3] = (byte) c;
    208             if (test[0] == 0 && test[1] == 0 && test[2] == 0 && test[3] == 1) {
    209                 prevScSize = currentScSize;
    210                 currentScSize = 4;
    211                 return true;
    212             }
    213             if (test[0] == 0 && test[1] == 0 && test[2] == 1) {
    214                 prevScSize = currentScSize;
    215                 currentScSize = 3;
    216                 return true;
    217             }
    218         }
    219         return false;
    220     }
    221 
    222     private enum NALActions {
    223         IGNORE, BUFFER, STORE, END
    224     }
    225 
    226     private boolean readSamples() throws IOException {
    227         if (readSamples) {
    228             return true;
    229         }
    230 
    231         readSamples = true;
    232 
    233 
    234         findNextStartcode();
    235         reader.mark();
    236         long pos = reader.getPos();
    237 
    238         ArrayList<byte[]> buffered = new ArrayList<byte[]>();
    239 
    240         int frameNr = 0;
    241 
    242         while (findNextStartcode()) {
    243             long newpos = reader.getPos();
    244             int size = (int) (newpos - pos - prevScSize);
    245             reader.reset();
    246             byte[] data = new byte[size ];
    247             reader.read(data);
    248             int type = data[0];
    249             int nal_ref_idc = (type >> 5) & 3;
    250             int nal_unit_type = type & 0x1f;
    251             LOG.fine("Found startcode at " + (pos -4)  + " Type: " + nal_unit_type + " ref idc: " + nal_ref_idc + " (size " + size + ")");
    252             NALActions action = handleNALUnit(nal_ref_idc, nal_unit_type, data);
    253             switch (action) {
    254                 case IGNORE:
    255                     break;
    256 
    257                 case BUFFER:
    258                     buffered.add(data);
    259                     break;
    260 
    261                 case STORE:
    262                     int stdpValue = 22;
    263                     frameNr++;
    264                     buffered.add(data);
    265                     ByteBuffer bb = createSample(buffered);
    266                     boolean IdrPicFlag = false;
    267                     if (nal_unit_type == 5) {
    268                         stdpValue += 16;
    269                         IdrPicFlag = true;
    270                     }
    271                     ByteArrayInputStream bs = cleanBuffer(buffered.get(buffered.size() - 1));
    272                     SliceHeader sh = new SliceHeader(bs, seqParameterSet, pictureParameterSet, IdrPicFlag);
    273                     if (sh.slice_type == SliceHeader.SliceType.B) {
    274                         stdpValue += 4;
    275                     }
    276                     LOG.fine("Adding sample with size " + bb.capacity() + " and header " + sh);
    277                     buffered.clear();
    278                     samples.add(bb);
    279                     stts.add(new TimeToSampleBox.Entry(1, frametick));
    280                     if (nal_unit_type == 5) { // IDR Picture
    281                         stss.add(frameNr);
    282                     }
    283                     if (seiMessage.n_frames == 0) {
    284                         frameNrInGop = 0;
    285                     }
    286                     int offset = 0;
    287                     if (seiMessage.clock_timestamp_flag) {
    288                         offset = seiMessage.n_frames - frameNrInGop;
    289                     } else if (seiMessage.removal_delay_flag) {
    290                         offset = seiMessage.dpb_removal_delay / 2;
    291                     }
    292                     ctts.add(new CompositionTimeToSample.Entry(1, offset * frametick));
    293                     sdtp.add(new SampleDependencyTypeBox.Entry(stdpValue));
    294                     frameNrInGop++;
    295                     break;
    296 
    297                 case END:
    298                     return true;
    299 
    300 
    301             }
    302             pos = newpos;
    303             reader.seek(currentScSize);
    304             reader.mark();
    305         }
    306         return true;
    307     }
    308 
    309     private ByteBuffer createSample(List<byte[]> buffers) {
    310         int outsize = 0;
    311         for (int i = 0; i < buffers.size(); i++) {
    312             outsize += buffers.get(i).length + 4;
    313         }
    314         byte[] output = new byte[outsize];
    315 
    316         ByteBuffer bb = ByteBuffer.wrap(output);
    317         for (int i = 0; i < buffers.size(); i++) {
    318             bb.putInt(buffers.get(i).length);
    319             bb.put(buffers.get(i));
    320         }
    321         bb.rewind();
    322         return bb;
    323     }
    324 
    325     private ByteArrayInputStream cleanBuffer(byte[] data) {
    326         byte[] output = new byte[data.length];
    327         int inPos = 0;
    328         int outPos = 0;
    329         while (inPos < data.length) {
    330             if (data[inPos] == 0 && data[inPos + 1] == 0 && data[inPos + 2] == 3) {
    331                 output[outPos] = 0;
    332                 output[outPos + 1] = 0;
    333                 inPos += 3;
    334                 outPos += 2;
    335             } else {
    336                 output[outPos] = data[inPos];
    337                 inPos++;
    338                 outPos++;
    339             }
    340         }
    341         return new ByteArrayInputStream(output, 0, outPos);
    342     }
    343 
    344     private NALActions handleNALUnit(int nal_ref_idc, int nal_unit_type, byte[] data) throws IOException {
    345         NALActions action;
    346         switch (nal_unit_type) {
    347             case 1:
    348             case 2:
    349             case 3:
    350             case 4:
    351             case 5:
    352                 action = NALActions.STORE; // Will only work in single slice per frame mode!
    353                 break;
    354 
    355             case 6:
    356                 seiMessage = new SEIMessage(cleanBuffer(data), seqParameterSet);
    357                 action = NALActions.BUFFER;
    358                 break;
    359 
    360             case 9:
    361 //                printAccessUnitDelimiter(data);
    362                 int type = data[1] >> 5;
    363                 LOG.fine("Access unit delimiter type: " + type);
    364                 action = NALActions.BUFFER;
    365                 break;
    366 
    367 
    368             case 7:
    369                 if (seqParameterSet == null) {
    370                     ByteArrayInputStream is = cleanBuffer(data);
    371                     is.read();
    372                     seqParameterSet = SeqParameterSet.read(is);
    373                     seqParameterSetList.add(data);
    374                     configureFramerate();
    375                 }
    376                 action = NALActions.IGNORE;
    377                 break;
    378 
    379             case 8:
    380                 if (pictureParameterSet == null) {
    381                     ByteArrayInputStream is = new ByteArrayInputStream(data);
    382                     is.read();
    383                     pictureParameterSet = PictureParameterSet.read(is);
    384                     pictureParameterSetList.add(data);
    385                 }
    386                 action = NALActions.IGNORE;
    387                 break;
    388 
    389             case 10:
    390             case 11:
    391                 action = NALActions.END;
    392                 break;
    393 
    394             default:
    395                 System.err.println("Unknown NAL unit type: " + nal_unit_type);
    396                 action = NALActions.IGNORE;
    397 
    398         }
    399 
    400         return action;
    401     }
    402 
    403     private void configureFramerate() {
    404         if (determineFrameRate) {
    405             if (seqParameterSet.vuiParams != null) {
    406                 timescale = seqParameterSet.vuiParams.time_scale >> 1; // Not sure why, but I found this in several places, and it works...
    407                 frametick = seqParameterSet.vuiParams.num_units_in_tick;
    408                 if (timescale == 0 || frametick == 0) {
    409                     System.err.println("Warning: vuiParams contain invalid values: time_scale: " + timescale + " and frame_tick: " + frametick + ". Setting frame rate to 25fps");
    410                     timescale = 90000;
    411                     frametick = 3600;
    412                 }
    413             } else {
    414                 System.err.println("Warning: Can't determine frame rate. Guessing 25 fps");
    415                 timescale = 90000;
    416                 frametick = 3600;
    417             }
    418         }
    419     }
    420 
    421     public void printAccessUnitDelimiter(byte[] data) {
    422         LOG.fine("Access unit delimiter: " + (data[1] >> 5));
    423     }
    424 
    425     public static class SliceHeader {
    426 
    427         public enum SliceType {
    428             P, B, I, SP, SI
    429         }
    430 
    431         public int first_mb_in_slice;
    432         public SliceType slice_type;
    433         public int pic_parameter_set_id;
    434         public int colour_plane_id;
    435         public int frame_num;
    436         public boolean field_pic_flag = false;
    437         public boolean bottom_field_flag = false;
    438         public int idr_pic_id;
    439         public int pic_order_cnt_lsb;
    440         public int delta_pic_order_cnt_bottom;
    441 
    442         public SliceHeader(InputStream is, SeqParameterSet sps, PictureParameterSet pps, boolean IdrPicFlag) throws IOException {
    443             is.read();
    444             CAVLCReader reader = new CAVLCReader(is);
    445             first_mb_in_slice = reader.readUE("SliceHeader: first_mb_in_slice");
    446             switch (reader.readUE("SliceHeader: slice_type")) {
    447                 case 0:
    448                 case 5:
    449                     slice_type = SliceType.P;
    450                     break;
    451 
    452                 case 1:
    453                 case 6:
    454                     slice_type = SliceType.B;
    455                     break;
    456 
    457                 case 2:
    458                 case 7:
    459                     slice_type = SliceType.I;
    460                     break;
    461 
    462                 case 3:
    463                 case 8:
    464                     slice_type = SliceType.SP;
    465                     break;
    466 
    467                 case 4:
    468                 case 9:
    469                     slice_type = SliceType.SI;
    470                     break;
    471 
    472             }
    473             pic_parameter_set_id = reader.readUE("SliceHeader: pic_parameter_set_id");
    474             if (sps.residual_color_transform_flag) {
    475                 colour_plane_id = reader.readU(2, "SliceHeader: colour_plane_id");
    476             }
    477             frame_num = reader.readU(sps.log2_max_frame_num_minus4 + 4, "SliceHeader: frame_num");
    478 
    479             if (!sps.frame_mbs_only_flag) {
    480                 field_pic_flag = reader.readBool("SliceHeader: field_pic_flag");
    481                 if (field_pic_flag) {
    482                     bottom_field_flag = reader.readBool("SliceHeader: bottom_field_flag");
    483                 }
    484             }
    485             if (IdrPicFlag) {
    486                 idr_pic_id = reader.readUE("SliceHeader: idr_pic_id");
    487                 if (sps.pic_order_cnt_type == 0) {
    488                     pic_order_cnt_lsb = reader.readU(sps.log2_max_pic_order_cnt_lsb_minus4 + 4, "SliceHeader: pic_order_cnt_lsb");
    489                     if (pps.pic_order_present_flag && !field_pic_flag) {
    490                         delta_pic_order_cnt_bottom = reader.readSE("SliceHeader: delta_pic_order_cnt_bottom");
    491                     }
    492                 }
    493             }
    494         }
    495 
    496         @Override
    497         public String toString() {
    498             return "SliceHeader{" +
    499                     "first_mb_in_slice=" + first_mb_in_slice +
    500                     ", slice_type=" + slice_type +
    501                     ", pic_parameter_set_id=" + pic_parameter_set_id +
    502                     ", colour_plane_id=" + colour_plane_id +
    503                     ", frame_num=" + frame_num +
    504                     ", field_pic_flag=" + field_pic_flag +
    505                     ", bottom_field_flag=" + bottom_field_flag +
    506                     ", idr_pic_id=" + idr_pic_id +
    507                     ", pic_order_cnt_lsb=" + pic_order_cnt_lsb +
    508                     ", delta_pic_order_cnt_bottom=" + delta_pic_order_cnt_bottom +
    509                     '}';
    510         }
    511     }
    512 
    513     private class ReaderWrapper {
    514         private InputStream inputStream;
    515         private long pos = 0;
    516 
    517         private long markPos = 0;
    518 
    519 
    520         private ReaderWrapper(InputStream inputStream) {
    521             this.inputStream = inputStream;
    522         }
    523 
    524         int read() throws IOException {
    525             pos++;
    526             return inputStream.read();
    527         }
    528 
    529         long read(byte[] data) throws IOException {
    530             long read = inputStream.read(data);
    531             pos += read;
    532             return read;
    533         }
    534 
    535         long seek(int dist) throws IOException {
    536             long seeked = inputStream.skip(dist);
    537             pos += seeked;
    538             return seeked;
    539         }
    540 
    541         public long getPos() {
    542             return pos;
    543         }
    544 
    545         public void mark() {
    546             int i = 1048576;
    547             LOG.fine("Marking with " + i + " at " + pos);
    548             inputStream.mark(i);
    549             markPos = pos;
    550         }
    551 
    552 
    553         public void reset() throws IOException {
    554             long diff = pos - markPos;
    555             LOG.fine("Resetting to " + markPos + " (pos is " + pos + ") which makes the buffersize " + diff);
    556             inputStream.reset();
    557             pos = markPos;
    558         }
    559     }
    560 
    561     public class SEIMessage {
    562 
    563         int payloadType = 0;
    564         int payloadSize = 0;
    565 
    566         boolean removal_delay_flag;
    567         int cpb_removal_delay;
    568         int dpb_removal_delay;
    569 
    570         boolean clock_timestamp_flag;
    571         int pic_struct;
    572         int ct_type;
    573         int nuit_field_based_flag;
    574         int counting_type;
    575         int full_timestamp_flag;
    576         int discontinuity_flag;
    577         int cnt_dropped_flag;
    578         int n_frames;
    579         int seconds_value;
    580         int minutes_value;
    581         int hours_value;
    582         int time_offset_length;
    583         int time_offset;
    584 
    585         SeqParameterSet sps;
    586 
    587         public SEIMessage(InputStream is, SeqParameterSet sps) throws IOException {
    588             this.sps = sps;
    589             is.read();
    590             int datasize = is.available();
    591             int read = 0;
    592             while (read < datasize) {
    593                 payloadType = 0;
    594                 payloadSize = 0;
    595                 int last_payload_type_bytes = is.read();
    596                 read++;
    597                 while (last_payload_type_bytes == 0xff) {
    598                     payloadType += last_payload_type_bytes;
    599                     last_payload_type_bytes = is.read();
    600                     read++;
    601                 }
    602                 payloadType += last_payload_type_bytes;
    603                 int last_payload_size_bytes = is.read();
    604                 read++;
    605 
    606                 while (last_payload_size_bytes == 0xff) {
    607                     payloadSize += last_payload_size_bytes;
    608                     last_payload_size_bytes = is.read();
    609                     read++;
    610                 }
    611                 payloadSize += last_payload_size_bytes;
    612                 if (datasize - read >= payloadSize) {
    613                     if (payloadType == 1) { // pic_timing is what we are interested in!
    614                         if (sps.vuiParams != null && (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null || sps.vuiParams.pic_struct_present_flag)) {
    615                             byte[] data = new byte[payloadSize];
    616                             is.read(data);
    617                             read += payloadSize;
    618                             CAVLCReader reader = new CAVLCReader(new ByteArrayInputStream(data));
    619                             if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) {
    620                                 removal_delay_flag = true;
    621                                 cpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.cpb_removal_delay_length_minus1 + 1, "SEI: cpb_removal_delay");
    622                                 dpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.dpb_output_delay_length_minus1 + 1, "SEI: dpb_removal_delay");
    623                             } else {
    624                                 removal_delay_flag = false;
    625                             }
    626                             if (sps.vuiParams.pic_struct_present_flag) {
    627                                 pic_struct = reader.readU(4, "SEI: pic_struct");
    628                                 int numClockTS;
    629                                 switch (pic_struct) {
    630                                     case 0:
    631                                     case 1:
    632                                     case 2:
    633                                     default:
    634                                         numClockTS = 1;
    635                                         break;
    636 
    637                                     case 3:
    638                                     case 4:
    639                                     case 7:
    640                                         numClockTS = 2;
    641                                         break;
    642 
    643                                     case 5:
    644                                     case 6:
    645                                     case 8:
    646                                         numClockTS = 3;
    647                                         break;
    648                                 }
    649                                 for (int i = 0; i < numClockTS; i++) {
    650                                     clock_timestamp_flag = reader.readBool("pic_timing SEI: clock_timestamp_flag[" + i + "]");
    651                                     if (clock_timestamp_flag) {
    652                                         ct_type = reader.readU(2, "pic_timing SEI: ct_type");
    653                                         nuit_field_based_flag = reader.readU(1, "pic_timing SEI: nuit_field_based_flag");
    654                                         counting_type = reader.readU(5, "pic_timing SEI: counting_type");
    655                                         full_timestamp_flag = reader.readU(1, "pic_timing SEI: full_timestamp_flag");
    656                                         discontinuity_flag = reader.readU(1, "pic_timing SEI: discontinuity_flag");
    657                                         cnt_dropped_flag = reader.readU(1, "pic_timing SEI: cnt_dropped_flag");
    658                                         n_frames = reader.readU(8, "pic_timing SEI: n_frames");
    659                                         if (full_timestamp_flag == 1) {
    660                                             seconds_value = reader.readU(6, "pic_timing SEI: seconds_value");
    661                                             minutes_value = reader.readU(6, "pic_timing SEI: minutes_value");
    662                                             hours_value = reader.readU(5, "pic_timing SEI: hours_value");
    663                                         } else {
    664                                             if (reader.readBool("pic_timing SEI: seconds_flag")) {
    665                                                 seconds_value = reader.readU(6, "pic_timing SEI: seconds_value");
    666                                                 if (reader.readBool("pic_timing SEI: minutes_flag")) {
    667                                                     minutes_value = reader.readU(6, "pic_timing SEI: minutes_value");
    668                                                     if (reader.readBool("pic_timing SEI: hours_flag")) {
    669                                                         hours_value = reader.readU(5, "pic_timing SEI: hours_value");
    670                                                     }
    671                                                 }
    672                                             }
    673                                         }
    674                                         if (true) {
    675                                             if (sps.vuiParams.nalHRDParams != null) {
    676                                                 time_offset_length = sps.vuiParams.nalHRDParams.time_offset_length;
    677                                             } else if (sps.vuiParams.vclHRDParams != null) {
    678                                                 time_offset_length = sps.vuiParams.vclHRDParams.time_offset_length;
    679                                             } else {
    680                                                 time_offset_length = 24;
    681                                             }
    682                                             time_offset = reader.readU(24, "pic_timing SEI: time_offset");
    683                                         }
    684                                     }
    685                                 }
    686                             }
    687 
    688                         } else {
    689                             for (int i = 0; i < payloadSize; i++) {
    690                                 is.read();
    691                                 read++;
    692                             }
    693                         }
    694                     } else {
    695                         for (int i = 0; i < payloadSize; i++) {
    696                             is.read();
    697                             read++;
    698                         }
    699                     }
    700                 } else {
    701                     read = datasize;
    702                 }
    703                 LOG.fine(this.toString());
    704             }
    705         }
    706 
    707         @Override
    708         public String toString() {
    709             String out = "SEIMessage{" +
    710                     "payloadType=" + payloadType +
    711                     ", payloadSize=" + payloadSize;
    712             if (payloadType == 1) {
    713                 if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) {
    714 
    715                     out += ", cpb_removal_delay=" + cpb_removal_delay +
    716                             ", dpb_removal_delay=" + dpb_removal_delay;
    717                 }
    718                 if (sps.vuiParams.pic_struct_present_flag) {
    719                     out += ", pic_struct=" + pic_struct;
    720                     if (clock_timestamp_flag) {
    721                         out += ", ct_type=" + ct_type +
    722                                 ", nuit_field_based_flag=" + nuit_field_based_flag +
    723                                 ", counting_type=" + counting_type +
    724                                 ", full_timestamp_flag=" + full_timestamp_flag +
    725                                 ", discontinuity_flag=" + discontinuity_flag +
    726                                 ", cnt_dropped_flag=" + cnt_dropped_flag +
    727                                 ", n_frames=" + n_frames +
    728                                 ", seconds_value=" + seconds_value +
    729                                 ", minutes_value=" + minutes_value +
    730                                 ", hours_value=" + hours_value +
    731                                 ", time_offset_length=" + time_offset_length +
    732                                 ", time_offset=" + time_offset;
    733                     }
    734                 }
    735             }
    736             out += '}';
    737             return out;
    738         }
    739     }
    740 }
    741