1 /*
2  * Copyright (C) 2014 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 
17 package android.hardware.camera2.params;
18 
19 import static com.android.internal.util.Preconditions.*;
20 
21 import android.hardware.camera2.CameraCharacteristics;
22 import android.hardware.camera2.utils.HashCodeHelpers;
23 
24 import java.util.Arrays;
25 
26 /**
27  * Immutable class to store the input to output formats
28  * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP map} to be used for with
29  * camera image reprocessing.
30  *
31  * <p>
32  * The mapping of image formats that are supported by this camera device for input streams,
33  * to their corresponding output formats.</p>
34  *
35  * <p>
36  * Attempting to configure an input stream with output streams not listed as available in this map
37  * is not valid.
38  * </p>
39  *
40  * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP
41  * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
42  *
43  * <!-- hide this until we expose input streams through public API -->
44  * @hide
45  */
46 public final class ReprocessFormatsMap {
47     /**
48      * Create a new {@link ReprocessFormatsMap}
49      *
50      * <p>This value is encoded as a variable-size array-of-arrays.
51      * The inner array always contains {@code [format, length, ...]} where ... has length elements.
52      * An inner array is followed by another inner array if the total metadata entry size hasn't
53      * yet been exceeded.</p>
54      *
55      * <p>Entry must not be {@code null}. Empty array is acceptable.</p>
56      *
57      * <p>The entry array ownership is passed to this instance after construction; do not
58      * write to it afterwards.</p>
59      *
60      * @param entry Array of ints, not yet deserialized (not-null)
61      *
62      * @throws IllegalArgumentException
63      *              if the data was poorly formatted
64      *              (missing output format length or too few output formats)
65      *              or if any of the input/formats were not valid
66      * @throws NullPointerException
67      *              if entry was null
68      *
69      * @see StreamConfigurationMap#checkArgumentFormatInternal
70      *
71      * @hide
72      */
ReprocessFormatsMap(final int[] entry)73     public ReprocessFormatsMap(final int[] entry) {
74         checkNotNull(entry, "entry must not be null");
75 
76         int numInputs = 0;
77         int left = entry.length;
78         for (int i = 0; i < entry.length; ) {
79             int inputFormat = StreamConfigurationMap.checkArgumentFormatInternal(entry[i]);
80 
81             left--;
82             i++;
83 
84             if (left < 1) {
85                 throw new IllegalArgumentException(
86                         String.format("Input %x had no output format length listed", inputFormat));
87             }
88 
89             final int length = entry[i];
90             left--;
91             i++;
92 
93             for (int j = 0; j < length; ++j) {
94                 int outputFormat = entry[i + j];
95                 StreamConfigurationMap.checkArgumentFormatInternal(outputFormat);
96             }
97 
98             if (length > 0) {
99                 if (left < length) {
100                     throw new IllegalArgumentException(
101                             String.format(
102                                     "Input %x had too few output formats listed (actual: %d, " +
103                                     "expected: %d)", inputFormat, left, length));
104                 }
105 
106                 i += length;
107                 left -= length;
108             }
109 
110             numInputs++;
111         }
112 
113         mEntry = entry;
114         mInputCount = numInputs;
115     }
116 
117     /**
118      * Get a list of all input image formats that can be used to reprocess an input
119      * stream into an output stream.
120      *
121      * <p>Use this input format to look up the available output formats with {@link #getOutputs}.
122      * </p>
123      *
124      * @return an array of inputs (possibly empty, but never {@code null})
125      *
126      * @see ImageFormat
127      * @see #getOutputs
128      */
getInputs()129     public int[] getInputs() {
130         final int[] inputs = new int[mInputCount];
131 
132         int left = mEntry.length;
133         for (int i = 0, j = 0; i < mEntry.length; j++) {
134             final int format = mEntry[i];
135 
136             left--;
137             i++;
138 
139             if (left < 1) {
140                 throw new AssertionError(
141                         String.format("Input %x had no output format length listed", format));
142             }
143 
144             final int length = mEntry[i];
145             left--;
146             i++;
147 
148             if (length > 0) {
149                 if (left < length) {
150                     throw new AssertionError(
151                             String.format(
152                                     "Input %x had too few output formats listed (actual: %d, " +
153                                     "expected: %d)", format, left, length));
154                 }
155 
156                 i += length;
157                 left -= length;
158             }
159 
160             inputs[j] = format;
161         }
162 
163         return StreamConfigurationMap.imageFormatToPublic(inputs);
164     }
165 
166     /**
167      * Get the list of output formats that can be reprocessed into from the input {@code format}.
168      *
169      * <p>The input {@code format} must be one of the formats returned by {@link #getInputs}.</p>
170      *
171      * @param format an input format
172      *
173      * @return list of output image formats
174      *
175      * @see ImageFormat
176      * @see #getInputs
177      */
getOutputs(final int format)178     public int[] getOutputs(final int format) {
179 
180         int left = mEntry.length;
181         for (int i = 0; i < mEntry.length; ) {
182             final int inputFormat = mEntry[i];
183 
184             left--;
185             i++;
186 
187             if (left < 1) {
188                 throw new AssertionError(
189                         String.format("Input %x had no output format length listed", format));
190             }
191 
192             final int length = mEntry[i];
193             left--;
194             i++;
195 
196             if (length > 0) {
197                 if (left < length) {
198                     throw new AssertionError(
199                             String.format(
200                                     "Input %x had too few output formats listed (actual: %d, " +
201                                     "expected: %d)", format, left, length));
202                 }
203             }
204 
205             if (inputFormat == format) {
206                 int[] outputs = new int[length];
207 
208                 // Copying manually faster than System.arraycopy for small arrays
209                 for (int k = 0; k < length; ++k) {
210                     outputs[k] = mEntry[i + k];
211                 }
212 
213                 return StreamConfigurationMap.imageFormatToPublic(outputs);
214             }
215 
216             i += length;
217             left -= length;
218 
219         }
220 
221         throw new IllegalArgumentException(
222                 String.format("Input format %x was not one in #getInputs", format));
223     }
224 
225     /**
226      * Check if this {@link ReprocessFormatsMap} is equal to another
227      * {@link ReprocessFormatsMap}.
228      *
229      * <p>These two objects are only equal if and only if each of the respective elements is equal.
230      * </p>
231      *
232      * @return {@code true} if the objects were equal, {@code false} otherwise
233      */
234     @Override
equals(final Object obj)235     public boolean equals(final Object obj) {
236         if (obj == null) {
237             return false;
238         }
239         if (this == obj) {
240             return true;
241         }
242         if (obj instanceof ReprocessFormatsMap) {
243             final ReprocessFormatsMap other = (ReprocessFormatsMap) obj;
244             // Do not compare anything besides mEntry, since the rest of the values are derived
245             return Arrays.equals(mEntry, other.mEntry);
246         }
247         return false;
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
hashCode()254     public int hashCode() {
255         // Do not hash anything besides mEntry since the rest of the values are derived
256         return HashCodeHelpers.hashCode(mEntry);
257     }
258 
259     private final int[] mEntry;
260     /*
261      * Dependent fields: values are derived from mEntry
262      */
263     private final int mInputCount;
264 }
265