1/*
2 * Copyright 2008 CoreMedia AG, Hamburg
3 *
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.coremedia.iso.boxes;
18
19import com.coremedia.iso.IsoTypeReader;
20import com.coremedia.iso.IsoTypeWriter;
21import com.googlecode.mp4parser.AbstractFullBox;
22
23import java.nio.ByteBuffer;
24import java.util.ArrayList;
25import java.util.Collections;
26import java.util.Iterator;
27import java.util.LinkedList;
28import java.util.List;
29
30import static com.googlecode.mp4parser.util.CastUtils.l2i;
31
32/**
33 * Samples within the media data are grouped into chunks. Chunks can be of different sizes, and the
34 * samples within a chunk can have different sizes. This table can be used to find the chunk that
35 * contains a sample, its position, and the associated sample description. Defined in ISO/IEC 14496-12.
36 */
37public class SampleToChunkBox extends AbstractFullBox {
38    List<Entry> entries = Collections.emptyList();
39
40    public static final String TYPE = "stsc";
41
42    public SampleToChunkBox() {
43        super(TYPE);
44    }
45
46    public List<Entry> getEntries() {
47        return entries;
48    }
49
50    public void setEntries(List<Entry> entries) {
51        this.entries = entries;
52    }
53
54    protected long getContentSize() {
55        return entries.size() * 12 + 8;
56    }
57
58    @Override
59    public void _parseDetails(ByteBuffer content) {
60        parseVersionAndFlags(content);
61
62        int entryCount = l2i(IsoTypeReader.readUInt32(content));
63        entries = new ArrayList<Entry>(entryCount);
64        for (int i = 0; i < entryCount; i++) {
65            entries.add(new Entry(
66                    IsoTypeReader.readUInt32(content),
67                    IsoTypeReader.readUInt32(content),
68                    IsoTypeReader.readUInt32(content)));
69        }
70    }
71
72    @Override
73    protected void getContent(ByteBuffer byteBuffer) {
74        writeVersionAndFlags(byteBuffer);
75        IsoTypeWriter.writeUInt32(byteBuffer, entries.size());
76        for (Entry entry : entries) {
77            IsoTypeWriter.writeUInt32(byteBuffer, entry.getFirstChunk());
78            IsoTypeWriter.writeUInt32(byteBuffer, entry.getSamplesPerChunk());
79            IsoTypeWriter.writeUInt32(byteBuffer, entry.getSampleDescriptionIndex());
80        }
81    }
82
83    public String toString() {
84        return "SampleToChunkBox[entryCount=" + entries.size() + "]";
85    }
86
87    /**
88     * Decompresses the list of entries and returns the number of samples per chunk for
89     * every single chunk.
90     *
91     * @param chunkCount overall number of chunks
92     * @return number of samples per chunk
93     */
94    public long[] blowup(int chunkCount) {
95        long[] numberOfSamples = new long[chunkCount];
96        int j = 0;
97        List<SampleToChunkBox.Entry> sampleToChunkEntries = new LinkedList<Entry>(entries);
98        Collections.reverse(sampleToChunkEntries);
99        Iterator<Entry> iterator = sampleToChunkEntries.iterator();
100        SampleToChunkBox.Entry currentEntry = iterator.next();
101
102        for (int i = numberOfSamples.length; i > 1; i--) {
103            numberOfSamples[i - 1] = currentEntry.getSamplesPerChunk();
104            if (i == currentEntry.getFirstChunk()) {
105                currentEntry = iterator.next();
106            }
107        }
108        numberOfSamples[0] = currentEntry.getSamplesPerChunk();
109        return numberOfSamples;
110    }
111
112    public static class Entry {
113        long firstChunk;
114        long samplesPerChunk;
115        long sampleDescriptionIndex;
116
117        public Entry(long firstChunk, long samplesPerChunk, long sampleDescriptionIndex) {
118            this.firstChunk = firstChunk;
119            this.samplesPerChunk = samplesPerChunk;
120            this.sampleDescriptionIndex = sampleDescriptionIndex;
121        }
122
123        public long getFirstChunk() {
124            return firstChunk;
125        }
126
127        public void setFirstChunk(long firstChunk) {
128            this.firstChunk = firstChunk;
129        }
130
131        public long getSamplesPerChunk() {
132            return samplesPerChunk;
133        }
134
135        public void setSamplesPerChunk(long samplesPerChunk) {
136            this.samplesPerChunk = samplesPerChunk;
137        }
138
139        public long getSampleDescriptionIndex() {
140            return sampleDescriptionIndex;
141        }
142
143        public void setSampleDescriptionIndex(long sampleDescriptionIndex) {
144            this.sampleDescriptionIndex = sampleDescriptionIndex;
145        }
146
147        @Override
148        public String toString() {
149            return "Entry{" +
150                    "firstChunk=" + firstChunk +
151                    ", samplesPerChunk=" + samplesPerChunk +
152                    ", sampleDescriptionIndex=" + sampleDescriptionIndex +
153                    '}';
154        }
155    }
156}
157