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
17package com.android.camera.util;
18
19import android.hardware.camera2.CameraMetadata;
20import android.hardware.camera2.CaptureRequest;
21import android.hardware.camera2.CaptureResult;
22import android.hardware.camera2.params.ColorSpaceTransform;
23import android.hardware.camera2.params.RggbChannelVector;
24import android.hardware.camera2.params.TonemapCurve;
25import android.util.Pair;
26import android.util.Rational;
27
28import com.android.camera.debug.Log;
29import com.android.camera.debug.Log.Tag;
30
31import java.io.BufferedWriter;
32import java.io.File;
33import java.io.FileWriter;
34import java.io.IOException;
35import java.io.StringWriter;
36import java.io.Writer;
37import java.lang.reflect.Array;
38import java.util.Arrays;
39import java.util.List;
40
41/**
42 * Can be used for debugging to output details about Camera2 capture request and
43 * responses.
44 */
45public class CaptureDataSerializer {
46    private static interface Writeable {
47        public void write(Writer writer) throws IOException;
48    }
49
50    private static final Tag TAG = new Tag("CaptureDataSerilzr");
51
52    /**
53     * Generate a human-readable string of the given capture request and return
54     * it.
55     */
56    public static String toString(String title, CaptureRequest metadata) {
57        StringWriter writer = new StringWriter();
58        dumpMetadata(title, metadata, writer);
59        return writer.toString();
60    }
61
62    /**
63     * Generate a human-readable string of the given capture request and write
64     * it to the given file.
65     */
66    public static void toFile(String title, CameraMetadata<?> metadata, File file) {
67        try {
68            // Will append if the file already exists.
69            FileWriter writer = new FileWriter(file, true);
70            if (metadata instanceof CaptureRequest) {
71                dumpMetadata(title, (CaptureRequest) metadata, writer);
72            } else if (metadata instanceof CaptureResult) {
73                dumpMetadata(title, (CaptureResult) metadata, writer);
74            } else {
75                writer.close();
76                throw new IllegalArgumentException("Cannot generate debug data from type "
77                        + metadata.getClass().getName());
78            }
79            writer.close();
80        } catch (IOException ex) {
81            Log.e(TAG, "Could not write capture data to file.", ex);
82        }
83    }
84
85    /**
86     * Writes the data about the marker and requests to the given folder for
87     * offline debugging.
88     */
89    private static void dumpMetadata(final String title, final CaptureRequest metadata,
90            Writer writer) {
91        Writeable writeable = new Writeable() {
92            @Override
93            public void write(Writer writer) throws IOException {
94                List<CaptureRequest.Key<?>> keys = metadata.getKeys();
95                writer.write(title + '\n');
96
97                // TODO: move to CameraMetadata#toString ?
98                for (CaptureRequest.Key<?> key : keys) {
99                    writer.write(String.format("    %s\n", key.getName()));
100                    writer.write(String.format("        %s\n",
101                            metadataValueToString(metadata.get(key))));
102                }
103            }
104        };
105        dumpMetadata(writeable, new BufferedWriter(writer));
106    }
107
108    /**
109     * Writes the data about the marker and requests to the given folder for
110     * offline debugging.
111     */
112    private static void dumpMetadata(final String title, final CaptureResult metadata,
113            Writer writer) {
114        Writeable writeable = new Writeable() {
115            @Override
116            public void write(Writer writer) throws IOException {
117                List<CaptureResult.Key<?>> keys = metadata.getKeys();
118                writer.write(String.format(title));
119
120                // TODO: move to CameraMetadata#toString ?
121                for (CaptureResult.Key<?> key : keys) {
122                    writer.write(String.format("    %s\n", key.getName()));
123                    writer.write(String.format("        %s\n",
124                            metadataValueToString(metadata.get(key))));
125                }
126            }
127        };
128        dumpMetadata(writeable, new BufferedWriter(writer));
129    }
130
131    private static String metadataValueToString(Object object) {
132        if (object == null) {
133            return "<null>";
134        }
135        if (object.getClass().isArray()) {
136            StringBuilder builder = new StringBuilder();
137            builder.append("[");
138
139            int length = Array.getLength(object);
140            for (int i = 0; i < length; ++i) {
141                Object item = Array.get(object, i);
142                builder.append(metadataValueToString(item));
143
144                if (i != length - 1) {
145                    builder.append(", ");
146                }
147            }
148            builder.append(']');
149
150            return builder.toString();
151        } else {
152            // These classes don't have a toString() method yet
153            // See: http://b/16899576
154            if (object instanceof RggbChannelVector) {
155                return toString((RggbChannelVector) object);
156            } else if (object instanceof ColorSpaceTransform) {
157                return toString((ColorSpaceTransform) object);
158            } else if (object instanceof TonemapCurve) {
159                return toString((TonemapCurve) object);
160            } else if (object instanceof Pair) {
161                return toString((Pair<?, ?>) object);
162            }
163            return object.toString();
164        }
165    }
166
167    private static void dumpMetadata(Writeable metadata, Writer writer) {
168        /**
169         * Save metadata to file, appending if another metadata is already in
170         * that file.
171         */
172        try {
173            metadata.write(writer);
174        } catch (IOException e) {
175            Log.e(TAG, "dumpMetadata - Failed to dump metadata", e);
176        } finally {
177            try {
178                if (writer != null) {
179                    writer.close();
180                }
181            } catch (IOException e) {
182                Log.e(TAG, "dumpMetadata - Failed to close writer.", e);
183            }
184        }
185    }
186
187    private static String toString(RggbChannelVector vector) {
188        StringBuilder str = new StringBuilder();
189        str.append("RggbChannelVector:");
190        str.append(" R:");
191        str.append(vector.getRed());
192        str.append(" G(even):");
193        str.append(vector.getGreenEven());
194        str.append(" G(odd):");
195        str.append(vector.getGreenOdd());
196        str.append(" B:");
197        str.append(vector.getBlue());
198
199        return str.toString();
200    }
201
202    private static String toString(ColorSpaceTransform transform) {
203        StringBuilder str = new StringBuilder();
204        Rational[] rationals = new Rational[9];
205        transform.copyElements(rationals, 0);
206        str.append("ColorSpaceTransform: ");
207        str.append(Arrays.toString(rationals));
208        return str.toString();
209    }
210
211    private static String toString(TonemapCurve curve) {
212        StringBuilder str = new StringBuilder();
213        str.append("TonemapCurve:");
214
215        float[] reds = new float[curve.getPointCount(TonemapCurve.CHANNEL_RED)
216                * TonemapCurve.POINT_SIZE];
217        curve.copyColorCurve(TonemapCurve.CHANNEL_RED, reds, 0);
218        float[] greens = new float[curve.getPointCount(TonemapCurve.CHANNEL_GREEN)
219                * TonemapCurve.POINT_SIZE];
220        curve.copyColorCurve(TonemapCurve.CHANNEL_GREEN, greens, 0);
221        float[] blues = new float[curve.getPointCount(TonemapCurve.CHANNEL_BLUE)
222                * TonemapCurve.POINT_SIZE];
223        curve.copyColorCurve(TonemapCurve.CHANNEL_BLUE, blues, 0);
224
225        str.append("\n\nReds: ");
226        str.append(Arrays.toString(reds));
227        str.append("\n\nGreens: ");
228        str.append(Arrays.toString(greens));
229        str.append("\n\nBlues: ");
230        str.append(Arrays.toString(blues));
231
232        return str.toString();
233    }
234
235    private static String toString(Pair<?, ?> pair) {
236        return "Pair: " + metadataValueToString(pair.first) + " / "
237                + metadataValueToString(pair.second);
238    }
239}
240