1 /*
2  * Copyright (C) 2016 The Android Open Source Project
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 package com.google.android.exoplayer2.util;
17 
18 import android.util.Pair;
19 import androidx.annotation.Nullable;
20 import com.google.android.exoplayer2.C;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.List;
24 
25 /** Provides utilities for handling various types of codec-specific data. */
26 public final class CodecSpecificDataUtil {
27 
28   private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
29 
30   /**
31    * Parses an ALAC AudioSpecificConfig (i.e. an <a
32    * href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
33    *
34    * @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse.
35    * @return A pair consisting of the sample rate in Hz and the channel count.
36    */
parseAlacAudioSpecificConfig(byte[] audioSpecificConfig)37   public static Pair<Integer, Integer> parseAlacAudioSpecificConfig(byte[] audioSpecificConfig) {
38     ParsableByteArray byteArray = new ParsableByteArray(audioSpecificConfig);
39     byteArray.setPosition(9);
40     int channelCount = byteArray.readUnsignedByte();
41     byteArray.setPosition(20);
42     int sampleRate = byteArray.readUnsignedIntToInt();
43     return Pair.create(sampleRate, channelCount);
44   }
45 
46   /**
47    * Returns initialization data for formats with MIME type {@link MimeTypes#APPLICATION_CEA708}.
48    *
49    * @param isWideAspectRatio Whether the CEA-708 closed caption service is formatted for displays
50    *     with 16:9 aspect ratio.
51    * @return Initialization data for formats with MIME type {@link MimeTypes#APPLICATION_CEA708}.
52    */
buildCea708InitializationData(boolean isWideAspectRatio)53   public static List<byte[]> buildCea708InitializationData(boolean isWideAspectRatio) {
54     return Collections.singletonList(isWideAspectRatio ? new byte[] {1} : new byte[] {0});
55   }
56 
57   /**
58    * Returns whether the CEA-708 closed caption service with the given initialization data is
59    * formatted for displays with 16:9 aspect ratio.
60    *
61    * @param initializationData The initialization data to parse.
62    * @return Whether the CEA-708 closed caption service is formatted for displays with 16:9 aspect
63    *     ratio.
64    */
parseCea708InitializationData(List<byte[]> initializationData)65   public static boolean parseCea708InitializationData(List<byte[]> initializationData) {
66     return initializationData.size() == 1
67         && initializationData.get(0).length == 1
68         && initializationData.get(0)[0] == 1;
69   }
70 
71   /**
72    * Builds an RFC 6381 AVC codec string using the provided parameters.
73    *
74    * @param profileIdc The encoding profile.
75    * @param constraintsFlagsAndReservedZero2Bits The constraint flags followed by the reserved zero
76    *     2 bits, all contained in the least significant byte of the integer.
77    * @param levelIdc The encoding level.
78    * @return An RFC 6381 AVC codec string built using the provided parameters.
79    */
buildAvcCodecString( int profileIdc, int constraintsFlagsAndReservedZero2Bits, int levelIdc)80   public static String buildAvcCodecString(
81       int profileIdc, int constraintsFlagsAndReservedZero2Bits, int levelIdc) {
82     return String.format(
83         "avc1.%02X%02X%02X", profileIdc, constraintsFlagsAndReservedZero2Bits, levelIdc);
84   }
85 
86   /**
87    * Constructs a NAL unit consisting of the NAL start code followed by the specified data.
88    *
89    * @param data An array containing the data that should follow the NAL start code.
90    * @param offset The start offset into {@code data}.
91    * @param length The number of bytes to copy from {@code data}
92    * @return The constructed NAL unit.
93    */
buildNalUnit(byte[] data, int offset, int length)94   public static byte[] buildNalUnit(byte[] data, int offset, int length) {
95     byte[] nalUnit = new byte[length + NAL_START_CODE.length];
96     System.arraycopy(NAL_START_CODE, 0, nalUnit, 0, NAL_START_CODE.length);
97     System.arraycopy(data, offset, nalUnit, NAL_START_CODE.length, length);
98     return nalUnit;
99   }
100 
101   /**
102    * Splits an array of NAL units.
103    *
104    * <p>If the input consists of NAL start code delimited units, then the returned array consists of
105    * the split NAL units, each of which is still prefixed with the NAL start code. For any other
106    * input, null is returned.
107    *
108    * @param data An array of data.
109    * @return The individual NAL units, or null if the input did not consist of NAL start code
110    *     delimited units.
111    */
112   @Nullable
splitNalUnits(byte[] data)113   public static byte[][] splitNalUnits(byte[] data) {
114     if (!isNalStartCode(data, 0)) {
115       // data does not consist of NAL start code delimited units.
116       return null;
117     }
118     List<Integer> starts = new ArrayList<>();
119     int nalUnitIndex = 0;
120     do {
121       starts.add(nalUnitIndex);
122       nalUnitIndex = findNalStartCode(data, nalUnitIndex + NAL_START_CODE.length);
123     } while (nalUnitIndex != C.INDEX_UNSET);
124     byte[][] split = new byte[starts.size()][];
125     for (int i = 0; i < starts.size(); i++) {
126       int startIndex = starts.get(i);
127       int endIndex = i < starts.size() - 1 ? starts.get(i + 1) : data.length;
128       byte[] nal = new byte[endIndex - startIndex];
129       System.arraycopy(data, startIndex, nal, 0, nal.length);
130       split[i] = nal;
131     }
132     return split;
133   }
134 
135   /**
136    * Finds the next occurrence of the NAL start code from a given index.
137    *
138    * @param data The data in which to search.
139    * @param index The first index to test.
140    * @return The index of the first byte of the found start code, or {@link C#INDEX_UNSET}.
141    */
142   private static int findNalStartCode(byte[] data, int index) {
143     int endIndex = data.length - NAL_START_CODE.length;
144     for (int i = index; i <= endIndex; i++) {
145       if (isNalStartCode(data, i)) {
146         return i;
147       }
148     }
149     return C.INDEX_UNSET;
150   }
151 
152   /**
153    * Tests whether there exists a NAL start code at a given index.
154    *
155    * @param data The data.
156    * @param index The index to test.
157    * @return Whether there exists a start code that begins at {@code index}.
158    */
159   private static boolean isNalStartCode(byte[] data, int index) {
160     if (data.length - index <= NAL_START_CODE.length) {
161       return false;
162     }
163     for (int j = 0; j < NAL_START_CODE.length; j++) {
164       if (data[index + j] != NAL_START_CODE[j]) {
165         return false;
166       }
167     }
168     return true;
169   }
170 
171   private CodecSpecificDataUtil() {}
172 }
173