001/*
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 *  The contents of this file are subject to the Mozilla Public License Version
005 *  1.1 (the "License"); you may not use this file except in compliance with
006 *  the License. You may obtain a copy of the License at
007 *  http://www.mozilla.org/MPL/
008 *
009 *  Software distributed under the License is distributed on an "AS IS" basis,
010 *  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 *  for the specific language governing rights and limitations under the
012 *  License.
013 *
014 *  The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 *  Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 *  The Initial Developer of the Original Code is
018 *  J4Care.
019 *  Portions created by the Initial Developer are Copyright (C) 2015-2017
020 *  the Initial Developer. All Rights Reserved.
021 *
022 *  Contributor(s):
023 *  See @authors listed below
024 *
025 *  Alternatively, the contents of this file may be used under the terms of
026 *  either the GNU General Public License Version 2 or later (the "GPL"), or
027 *  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 *  in which case the provisions of the GPL or the LGPL are applicable instead
029 *  of those above. If you wish to allow use of your version of this file only
030 *  under the terms of either the GPL or the LGPL, and not to allow others to
031 *  use your version of this file under the terms of the MPL, indicate your
032 *  decision by deleting the provisions above and replace them with the notice
033 *  and other provisions required by the GPL or the LGPL. If you do not delete
034 *  the provisions above, a recipient may use your version of this file under
035 *  the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 */
038
039package org.dcm4che3.imageio.codec.mpeg;
040
041import org.dcm4che3.data.Attributes;
042import org.dcm4che3.data.Tag;
043import org.dcm4che3.data.VR;
044
045import java.io.IOException;
046
047/**
048 * @author Gunter Zeilinger <gunterze@gmail.com>
049 * @since Apr 2017
050 */
051public class MPEGHeader {
052
053    private static final String[] ASPECT_RATIO_1_1 = { "1", "1" };
054    private static final String[] ASPECT_RATIO_4_3 = { "4", "3" };
055    private static final String[] ASPECT_RATIO_16_9 = { "16", "9" };
056    private static final String[] ASPECT_RATIO_221_100 = { "221", "100" };
057    private static final String[][] ASPECT_RATIOS = {
058            ASPECT_RATIO_1_1,
059            ASPECT_RATIO_4_3,
060            ASPECT_RATIO_16_9,
061            ASPECT_RATIO_221_100
062    };
063    private static int[] FPS = {
064        24, 1001,
065        24, 1000,
066        25, 1000,
067        30, 1001,
068        30, 1000,
069        50, 1000,
070        60, 1001,
071        60, 1000
072    };
073
074    private final byte[] data;
075    private final int seqHeaderOffset;
076
077    public MPEGHeader(byte[] data) throws IOException {
078        this.data = data;
079        int remaining = data.length;
080        int i = 0;
081        do {
082            while (remaining-- > 0 && data[i++] != 0);
083            if (remaining-- > 0 && data[i++] != 0)
084                continue;
085        } while (remaining > 8 && (data[i] != 1 || data[i+1] != (byte)0xb3));
086        seqHeaderOffset = remaining > 8 ? i+1 : -1;
087    }
088
089    /**
090     * Return corresponding Image Pixel Description Macro Attributes
091     * @param attrs target {@code Attributes} or {@code null}
092     * @param length MPEG stream length
093     * @return Image Pixel Description Macro Attributes
094     */
095    public Attributes toAttributes(Attributes attrs, long length) {
096        if (seqHeaderOffset == -1)
097            return null;
098
099        if (attrs == null)
100            attrs = new Attributes(15);
101
102        int off = seqHeaderOffset;
103        int x = ((data[off + 1] & 0xFF) << 4) | ((data[off + 2] & 0xF0) >> 4);
104        int y = ((data[off + 2] & 0x0F) << 8) | (data[off + 3] & 0xFF);
105        int aspectRatio = (data[off + 4] >> 4) & 0x0F;
106        int frameRate = data[off + 4] & 0x0F;
107        int bitRate = ((data[off + 5] & 0xFF) << 10) | ((data[off + 6] & 0xFF) << 2) | ((data[off + 7] & 0xC0) >> 6);
108        int numFrames = 9999;
109        if (frameRate > 0 && frameRate < 9) {
110            int frameRate2 = (frameRate - 1) << 1;
111            attrs.setInt(Tag.CineRate, VR.IS, FPS[frameRate2]);
112            attrs.setFloat(Tag.FrameTime, VR.DS, ((float) FPS[frameRate2 + 1]) / FPS[frameRate2]);
113            if (bitRate > 0)
114                numFrames = (int) (20 * length * FPS[frameRate2] / FPS[frameRate2 + 1] / bitRate);
115        }
116        attrs.setInt(Tag.SamplesPerPixel, VR.US, 3);
117        attrs.setString(Tag.PhotometricInterpretation, VR.CS, "YBR_PARTIAL_420");
118        attrs.setInt(Tag.PlanarConfiguration, VR.US, 0);
119        attrs.setInt(Tag.FrameIncrementPointer, VR.AT, Tag.FrameTime);
120        attrs.setInt(Tag.NumberOfFrames, VR.IS, numFrames);
121        attrs.setInt(Tag.Rows, VR.US, y);
122        attrs.setInt(Tag.Columns, VR.US, x);
123        if (aspectRatio > 0 && aspectRatio < 5)
124            attrs.setString(Tag.PixelAspectRatio, VR.IS, ASPECT_RATIOS[aspectRatio-1]);
125        attrs.setInt(Tag.BitsAllocated, VR.US, 8);
126        attrs.setInt(Tag.BitsStored, VR.US, 8);
127        attrs.setInt(Tag.HighBit, VR.US, 7);
128        attrs.setInt(Tag.PixelRepresentation, VR.US, 0);
129        attrs.setString(Tag.LossyImageCompression, VR.CS,  "01");
130        return attrs;
131    }
132}